diff --git a/docs/api/config/index.md b/docs/api/config/index.md
index 137e9ed3f..d5491f4a9 100644
--- a/docs/api/config/index.md
+++ b/docs/api/config/index.md
@@ -2,23 +2,117 @@
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/"
---
-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 Node.js, 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_noglobals=true 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
@@ -26,15 +120,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 b739f1b63..60dd0308e 100644
--- a/test/integration/karma-qunit.js
+++ b/test/integration/karma-qunit.js
@@ -19,18 +19,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