From eef8b5c0e43eccd2e7b97476a323a01fd7ae647e Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Wed, 6 Mar 2024 01:31:16 +0000 Subject: [PATCH] Core: Add support for flat preconfig via environment/global variables * With the expansion of the /api/config/ page, we now index this page's content instead of excluding it as a "group navigation" page the way we did before. This is made possible by Amethyst v1.0.3, which wraps the generated listings of page links and excerpts on these category pages into `
`, allowing us to exclude that from search indexes, rather than the pages as a whole. This means that the content unique to the category page itself, still gets indexed. https://github.com/qunitjs/jekyll-theme-amethyst/commits/v1.0.3/ --- Gruntfile.js | 3 +- build/prep-release.js | 6 +- docs/Gemfile | 2 +- docs/api/config/current.md | 2 +- docs/api/config/index.md | 123 ++++++++++++++++-- docsearch.config.json | 4 +- src/core/config.js | 76 ++++++++++- src/core/processing-queue.js | 3 - src/globals.js | 8 +- src/html-runner/fixture.js | 1 + src/html-runner/urlparams.js | 5 + test/cli/cli-main.js | 30 +++++ test/cli/fixtures/preconfig-flat.js | 21 +++ test/integration/karma-qunit.js | 38 +++++- test/integration/karma-qunit/karma.conf.js | 10 +- test/integration/karma-qunit/pass-config.js | 4 + test/preconfig-flat.html | 18 +++ test/preconfig-flat.js | 14 ++ ...econfigured.html => preconfig-object.html} | 4 +- .../{preconfigured.js => preconfig-object.js} | 0 20 files changed, 333 insertions(+), 39 deletions(-) create mode 100644 test/cli/fixtures/preconfig-flat.js create mode 100644 test/integration/karma-qunit/pass-config.js create mode 100644 test/preconfig-flat.html create mode 100644 test/preconfig-flat.js rename test/{preconfigured.html => preconfig-object.html} (79%) rename test/{preconfigured.js => preconfig-object.js} (100%) diff --git a/Gruntfile.js b/Gruntfile.js index 1f143f02b..876b5955c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -85,7 +85,8 @@ module.exports = function (grunt) { 'test/only-each.html', 'test/overload.html', 'test/performance-mark.html', - 'test/preconfigured.html', + 'test/preconfig-flat.html', + 'test/preconfig-object.html', 'test/reorder.html', 'test/reorderError1.html', 'test/reorderError2.html', diff --git a/build/prep-release.js b/build/prep-release.js index 1795504b0..c7db3a21b 100644 --- a/build/prep-release.js +++ b/build/prep-release.js @@ -24,7 +24,7 @@ function versionDeprecatedString (version) { } function formatChangelogColumn (version) { - return `| [QUnit ${version}](https://github.com/qunitjs/qunit/releases/tag/${version}) |`; + return `[QUnit ${version}](https://github.com/qunitjs/qunit/releases/tag/${version})`; } const Repo = { @@ -35,7 +35,7 @@ const Repo = { { const UNRELEASED_ADDED = versionAddedString('unreleased'); const UNRELEASED_DEP = versionDeprecatedString('unreleased'); - const UNRELEASED_CHANGELOG = '| UNRELEASED |'; + const UNRELEASED_CONTENT = /\bUNRELEASED\b/g; // Silence error from grep, which exits non-zero if no results. const results = parseLineResults(cp.execSync( @@ -48,7 +48,7 @@ const Repo = { doc .replace(UNRELEASED_ADDED, versionAddedString(version)) .replace(UNRELEASED_DEP, versionDeprecatedString(version)) - .replace(UNRELEASED_CHANGELOG, formatChangelogColumn(version)) + .replace(UNRELEASED_CONTENT, formatChangelogColumn(version)) ); }); } diff --git a/docs/Gemfile b/docs/Gemfile index 15a7e54a1..9cef3e983 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -2,4 +2,4 @@ source "https://rubygems.org" ruby RUBY_VERSION # To apply changes, run `bundle update`. -gem "jekyll-theme-amethyst", "1.0.2", group: :jekyll_plugins +gem "jekyll-theme-amethyst", "1.0.3", group: :jekyll_plugins diff --git a/docs/api/config/current.md b/docs/api/config/current.md index 2944aa4a9..dfe084112 100644 --- a/docs/api/config/current.md +++ b/docs/api/config/current.md @@ -15,7 +15,7 @@ Internal object representing the currently running test. - +
type`object` (read-only)`undefined` or `object` (read-only)
diff --git a/docs/api/config/index.md b/docs/api/config/index.md index 299ae8b2f..f59ccb2ce 100644 --- a/docs/api/config/index.md +++ b/docs/api/config/index.md @@ -2,24 +2,118 @@ layout: group group: config title: QUnit.config +excerpt: General configuration options for QUnit. +amethyst: + # Override inherited "pagetype: navigation" to enable TypeSense indexing + pagetype: custom + robots: index redirect_from: - "/QUnit.config/" - "/config/" - "/config/QUnit.config/" --- -General configuration options for QUnit. +## Order -## Preconfiguring QUnit +Configurations are read in the following order: -If you load QUnit asynchronously or otherwise need to configure QUnit before it is loaded, you can predefine the configuration by creating a global variable `QUnit` with a `config` property. +1. Default values +2. Flat preconfig +3. Object preconfig +4. Runner options (URL parameters in the HTML Reporter, or CLI options in the QUnit CLI) +5. Set `QUnit.config` from your own inline or bootstrap script. -The config values specified here will be carried over to the real `QUnit.config` object. Any other properties of this object will be ignored. +## Set configuration +You can configure the test run via the `QUnit.config` object. In the HTML Runner, you can set the configuration from any script after qunit.js: + +```html + + + + QUnit + + + +
+ + + + + + +``` + +If you have custom plugins or want to re-use your configuration across multiple HTML test suites, you can also configure your project from an external `/test/bootstrap.js` script. Make sure to place this script before your other test files. + +When using the [QUnit CLI](https://qunitjs.com/cli/), you can setup your project and configure QUnit via [`--require`](https://qunitjs.com/cli/#--require). + +```bash +qunit --require ./test/bootstrap.js +``` ```js -// Implicit global -// Supported everywhere, including old browsers. (But not ES strict mode.) -QUnit = { +// test/bootstrap.js +QUnit.config.noglobals = true; +QUnit.config.notrycatch = true; + +const MyApp = require('../'); +MyApp.setAccount('TestUser'); +``` + +## Preconfiguration + +Preconfiguration allows you to define QUnit configuration via global variables or environment variables, before QUnit itself is loaded. + +This is the recommended approach for general purpose test runners and integrations, as it provides a reliable way to override the default configuration. Ths can be especially useful if the end-user may control the loading of QUnit, possibly asynchronously, or through custom means, where it would be difficult to inject your overrides at the "right" time (after QUnit loads, but before any user code). + +Preconfig allows integrations to declare configuration without needing to embed, wrap, adapt, modify, or otherwise control the loading of QUnit itself. This in turn allows integrations to easily support use of standalone HTML test suites, that developers can also run and debug in the browser directly, without depending on or needing to install and set up any specific integration system. + +### Flat preconfig + +*Version added: UNRELEASED*. + +Flat preconfig allows multiple integrations to seemlessly collaborate, without the risk of projects accidentally unsetting an override (as is the case with Object preconfig). + +In the browser context (HTML Runner), global variables that start with `qunit_config_` may override the default value of a configuration option. The following inline script (before loading QUnit), is equivalent to setting `QUnit.config.hidepassed = true; QUnit.config.seed = 'd84af39036'; QUnit.config.testTimeout = 1000;` + +```html + +``` + +For the [QUnit CLI](../../cli.md) and other Node.js runners, the same can also be done via environment variables. Environment variables must be set in the shell before running the `qunit` or `node` command. The variable names are case-sensitive and must be in lowercase. You may set boolean configuration variables using the string `true` or `false`. + +```bash +export qunit_config_noglobals=true +export qunit_config_filter=foo +export qunit_config_testtimeout=1000 + +qunit test.js +``` + +Or: + +```bash +qunit_config_filter=foo qunit_config_testtimeout=1000 qunit test.js +``` + +Configuration options that are read-only, internal/undocumented, or that require an object value (such as [`QUnit.config.storage`](./storage.md)) cannot be set via environment variables. Options that require an array of strings will be converted to an array holding the given string. + +### Object preconfig + +*Version added: [2.1.0](https://github.com/qunitjs/qunit/releases/tag/2.1.0)*. + +You can create a `QUnit` global variable with a `config` property, before QUnit itself is loaded. Any properties on this `QUnit.config` placeholder object will be carried over and applied to the real `QUnit.config` object once it exists. Any additional properties on the placeholder are ignored. + +```js +// Isomorphic global +// For modern browsers, SpiderMonkey, and Node.js (including strict mode). +globalThis.QUnit = { config: { autostart: false, maxDepth: 12 @@ -27,15 +121,16 @@ QUnit = { }; // Browser global -// For all browsers (including strict mode and old browsers) -window.QUnit = { /* .. */ }; +// Supported in all browsers (including old browsers, and strict mode). +window.QUnit = { /* config: .. */ }; -// Isomorphic global -// For modern browsers, SpiderMonkey, and Node.js (incl. strict mode). -globalThis.QUnit = { /* .. */ }; +// Implicit global +// Supported everywhere, including old browsers. (But not ES strict mode.) +QUnit = { /* config: .. */ }; ``` ### Changelog -| [QUnit 2.18.1](https://github.com/qunitjs/qunit/releases/tag/2.18.1) | Preconfig support added for SpiderMonkey and other environments.
Previously, it was limited to the browser environment. -| [QUnit 2.1.0](https://github.com/qunitjs/qunit/releases/tag/2.1.0) | Preconfig feature introduced. +| UNRELEASED | Added flat preconfig. +| [QUnit 2.18.1](https://github.com/qunitjs/qunit/releases/tag/2.18.1) | Added object preconfig support for Node.js, SpiderMonkey, and other environments.
Previously, it was limited to the browser environment. +| [QUnit 2.1.0](https://github.com/qunitjs/qunit/releases/tag/2.1.0) | Introduce object preconfig feature. diff --git a/docsearch.config.json b/docsearch.config.json index 452591cbc..f873752b2 100644 --- a/docsearch.config.json +++ b/docsearch.config.json @@ -3,9 +3,6 @@ "start_urls": [ "https://qunitjs.com" ], - "stop_content": [ - " { diff --git a/src/globals.js b/src/globals.js index eb63daf9c..396b6ff4b 100644 --- a/src/globals.js +++ b/src/globals.js @@ -40,11 +40,15 @@ function getGlobalThis () { // to change getGlobalThis and use the same (generated) variable name there. const g = getGlobalThis(); export { g as globalThis }; -export const window = g.window; + +// These optional globals are undefined in one or more environments: +// modern browser, old browser, Node.js, SpiderMonkey. +// Calling code must check these for truthy-ness before use. export const console = g.console; export const setTimeout = g.setTimeout; export const clearTimeout = g.clearTimeout; - +export const process = g.process; +export const window = g.window; export const document = window && window.document; export const navigator = window && window.navigator; diff --git a/src/html-runner/fixture.js b/src/html-runner/fixture.js index e4c5807de..8876b85ff 100644 --- a/src/html-runner/fixture.js +++ b/src/html-runner/fixture.js @@ -12,6 +12,7 @@ import { window, document } from '../globals'; // Stores fixture HTML for resetting later function storeFixture () { // Avoid overwriting user-defined values + // TODO: Change to negative null/undefined check once declared in /src/config.js if (hasOwn.call(config, 'fixture')) { return; } diff --git a/src/html-runner/urlparams.js b/src/html-runner/urlparams.js index f71e51c21..4b2d180a1 100644 --- a/src/html-runner/urlparams.js +++ b/src/html-runner/urlparams.js @@ -9,8 +9,13 @@ import { window } from '../globals'; } const urlParams = getUrlParams(); + + // TODO: Move to /src/core/ in QUnit 3 + // TODO: Document this as public API (read-only) QUnit.urlParams = urlParams; + // TODO: Move to /src/core/config.js in QUnit 3, + // in accordance with /docs/api/config.index.md#order QUnit.config.filter = urlParams.filter; QUnit.config.module = urlParams.module; QUnit.config.moduleId = [].concat(urlParams.moduleId || []); diff --git a/test/cli/cli-main.js b/test/cli/cli-main.js index 92b7de7b9..314d58384 100644 --- a/test/cli/cli-main.js +++ b/test/cli/cli-main.js @@ -21,6 +21,36 @@ QUnit.module('CLI Main', () => { } ); + QUnit.test('preconfig-flat', async assert => { + const command = ['qunit', 'preconfig-flat.js']; + const execution = await execute(command, { + env: { + qunit_config_filter: '!foobar', + qunit_config_seed: 'dummyfirstyes', + qunit_config_testtimeout: '7' + } + }); + assert.equal(execution.snapshot, `TAP version 13 +ok 1 dummy +not ok 2 slow + --- + message: Test took longer than 7ms; test timed out. + severity: failed + actual : null + expected: undefined + stack: | + at internal + ... +ok 3 config +1..3 +# pass 2 +# skip 0 +# todo 0 +# fail 1 + +# exit code: 1`); + }); + QUnit.test('callbacks', async assert => { const expected = `CALLBACK: begin1 CALLBACK: begin2 diff --git a/test/cli/fixtures/preconfig-flat.js b/test/cli/fixtures/preconfig-flat.js new file mode 100644 index 000000000..7a1a07871 --- /dev/null +++ b/test/cli/fixtures/preconfig-flat.js @@ -0,0 +1,21 @@ +QUnit.test('config', function (assert) { + assert.strictEqual(QUnit.config.maxDepth, 5, 'maxDepth default'); + assert.strictEqual(QUnit.config.filter, '!foobar', 'filter override'); + assert.strictEqual(QUnit.config.seed, 'dummyfirstyes', 'seed override'); + assert.strictEqual(QUnit.config.testTimeout, 7, 'testTimeout override'); +}); + +QUnit.test('dummy', function (assert) { + // have at least two tests so that output demonstrates + // the effect of the seed on test order, + assert.true(true); +}); + +QUnit.test('slow', function (assert) { + assert.true(true); + return new Promise(resolve => setTimeout(resolve, 1000)); +}); + +QUnit.test('foobar', function (assert) { + assert.false(true, 'foobar test skipped by filter'); +}); diff --git a/test/integration/karma-qunit.js b/test/integration/karma-qunit.js index 2cc635aa4..9b17f35a6 100644 --- a/test/integration/karma-qunit.js +++ b/test/integration/karma-qunit.js @@ -17,18 +17,42 @@ QUnit.module('karma-qunit', { } }); -QUnit.test('passing test', assert => { - const expected = ` +QUnit.test.each('passing test', { + basic: ['', ` INFO [karma-server]: Karma server started at INFO [launcher]: Launching browsers FirefoxHeadless with concurrency unlimited INFO [launcher]: Starting browser FirefoxHeadless INFO [Firefox]: Connected on socket ... -Firefox: Executed 3 of 3 SUCCESS -`.trim(); - const actual = normalize( - cp.execSync('npm test', { cwd: DIR, env: { PATH: process.env.PATH }, encoding: 'utf8' }) - ); +Firefox: Executed 3 of 3 SUCCESS` + ], + config: ['pass-config.js', ` +INFO [karma-server]: Karma server started at +INFO [launcher]: Launching browsers FirefoxHeadless with concurrency unlimited +INFO [launcher]: Starting browser FirefoxHeadless +INFO [Firefox]: Connected on socket +. +Firefox: Executed 1 of 1 SUCCESS`, { KARMA_QUNIT_CONFIG: '1' } + ] +}, (assert, [file, expected, env = {}]) => { + expected = expected.trim(); + let ret; + try { + ret = cp.execSync('npm test', { + cwd: DIR, + env: { + PATH: process.env.PATH, + KARMA_FILES: file, + ...env + }, + encoding: 'utf8' + }); + } catch (e) { + const actual = normalize(e.stdout); + assert.pushResult({ result: false, actual, expected }); + return; + } + const actual = normalize(ret); assert.pushResult({ result: actual.includes(expected), actual, expected }); }); diff --git a/test/integration/karma-qunit/karma.conf.js b/test/integration/karma-qunit/karma.conf.js index ea8df3d8a..2d9a5f5fa 100644 --- a/test/integration/karma-qunit/karma.conf.js +++ b/test/integration/karma-qunit/karma.conf.js @@ -5,9 +5,17 @@ module.exports = function (config) { browsers: [ 'FirefoxHeadless' ], + client: process.env.KARMA_QUNIT_CONFIG + ? { + qunit: { + testTimeout: 1991, + fooBar: 'xyz' + } + } + : {}, frameworks: ['qunit'], files: [ - process.env.KARMA_FILES || 'pass-*.js' + process.env.KARMA_FILES || 'pass-basic.js' ], autoWatch: false, singleRun: true, diff --git a/test/integration/karma-qunit/pass-config.js b/test/integration/karma-qunit/pass-config.js new file mode 100644 index 000000000..68073a040 --- /dev/null +++ b/test/integration/karma-qunit/pass-config.js @@ -0,0 +1,4 @@ +QUnit.test('set config', function (assert) { + assert.strictEqual(QUnit.config.testTimeout, 1991, 'testTimeout'); + assert.strictEqual(QUnit.config.fooBar, 'xyz', 'fooBar'); +}); diff --git a/test/preconfig-flat.html b/test/preconfig-flat.html new file mode 100644 index 000000000..d20e8d72a --- /dev/null +++ b/test/preconfig-flat.html @@ -0,0 +1,18 @@ + + + + + preconfig-flat + + + + + + +
+ + diff --git a/test/preconfig-flat.js b/test/preconfig-flat.js new file mode 100644 index 000000000..9650839c2 --- /dev/null +++ b/test/preconfig-flat.js @@ -0,0 +1,14 @@ +/* eslint-env browser */ +QUnit.module('QUnit.config [preconfigured]'); + +QUnit.test('config', function (assert) { + assert.strictEqual(QUnit.config.maxDepth, 5, 'maxDepth default'); + assert.false(QUnit.config.autostart, 'autostart override'); + assert.strictEqual(QUnit.config.seed, 'd84af39036', 'seed override'); +}); + +window.addEventListener('load', function () { + setTimeout(function () { + QUnit.start(); + }, 1); +}); diff --git a/test/preconfigured.html b/test/preconfig-object.html similarity index 79% rename from test/preconfigured.html rename to test/preconfig-object.html index 1766aba60..d2a144195 100644 --- a/test/preconfigured.html +++ b/test/preconfig-object.html @@ -2,7 +2,7 @@ - preconfigured + preconfig-object - +
diff --git a/test/preconfigured.js b/test/preconfig-object.js similarity index 100% rename from test/preconfigured.js rename to test/preconfig-object.js