Skip to content

Commit

Permalink
Core: Add support for flat preconfig via environment/global variables
Browse files Browse the repository at this point in the history
* 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 `<div class=posts>`, 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/
  • Loading branch information
Krinkle committed May 26, 2024
1 parent 9aa3110 commit eef8b5c
Show file tree
Hide file tree
Showing 20 changed files with 333 additions and 39 deletions.
3 changes: 2 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
6 changes: 3 additions & 3 deletions build/prep-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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(
Expand All @@ -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))
);
});
}
Expand Down
2 changes: 1 addition & 1 deletion docs/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion docs/api/config/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Internal object representing the currently running test.
<table>
<tr>
<th>type</th>
<td markdown="span">`object` (read-only)</td>
<td markdown="span">`undefined` or `object` (read-only)</td>
</tr>
</table>

Expand Down
123 changes: 109 additions & 14 deletions docs/api/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,135 @@
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
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>QUnit</title>
<link rel="stylesheet" href="/lib/qunit/qunit.css">
</head>
<body>
<div id="qunit"></div>
<script src="/lib/qunit/qunit.js"></script>
<script>
QUnit.config.reorder = false;
</script>
<script src="/src/app.js"></script>
<script src="/test/bootstrap.js"></script>
<script src="/test/app.test.js"></script>
</body>
```

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
<script>
qunit_config_hidepassed = true;
qunit_config_seed = 'd84af39036';
qunit_config_testtimeout = 1000;
</script>
```

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
}
};

// 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.<br/>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.<br/>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.
4 changes: 1 addition & 3 deletions docsearch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
"start_urls": [
"https://qunitjs.com"
],
"stop_content": [
"<meta name=\"docsearch:amethyst_pagetype_navigation\""
],
"selectors": {
"lvl0": "h1",
"lvl1": "h2:not(.screen-reader-text)",
Expand All @@ -19,6 +16,7 @@
"token_separators": ["_", "-", "."]
},
"selectors_exclude": [
".posts",
"aside.sidebar",
".toc-wrapper"
],
Expand Down
76 changes: 75 additions & 1 deletion src/core/config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { globalThis, localSessionStorage } from '../globals';
import { globalThis, process, localSessionStorage } from '../globals';
import { extend } from './utilities';

/**
Expand All @@ -10,17 +10,26 @@ const config = {
// HTML Reporter: Modify document.title when suite is done
altertitle: true,

// TODO: Move here from /src/core.js in QUnit 3.
// autostart: true,

// HTML Reporter: collapse every test except the first failing test
// If false, all failing tests will be expanded
collapse: true,

// TODO: Make explicit in QUnit 3.
// current: undefined,

// whether or not to fail when there are zero tests
// defaults to `true`
failOnZeroTests: true,

// Select by pattern or case-insensitive substring match against "moduleName: testName"
filter: undefined,

// TODO: Make explicit in QUnit 3.
// fixture: undefined,

// Depth up-to which object will be dumped
maxDepth: 5,

Expand All @@ -40,11 +49,22 @@ const config = {
// By default, scroll to top of the page when suite is done
scrolltop: true,

// TODO: Make explicit in QUnit 3.
// seed: undefined,

// The storage module to use for reordering tests
storage: localSessionStorage,

testId: undefined,

// The updateRate controls how often QUnit will yield the main thread
// between tests. This is mainly for the benefit of the HTML Reporter,
// so that the browser can visually paint DOM changes with test results.
// This also helps avoid causing browsers to prompt a warning about
// long-running scripts.
// TODO: Move here from /src/core.js in QUnit 3.
// updateRate: 1000,

// HTML Reporter: List of URL parameters that are given visual controls
urlConfig: [],

Expand Down Expand Up @@ -102,6 +122,10 @@ const config = {
// Internal: ProcessingQueue singleton, created in /src/core.js
pq: null,

// Internal: Created in /src/core.js
// TODO: Move definitions here in QUnit 3.0.
// started: 0,

// Internal state
blocking: true,
callbacks: {},
Expand All @@ -110,6 +134,56 @@ const config = {
stats: { all: 0, bad: 0, testCount: 0 }
};

function readFlatPreconfigBoolean (val, dest) {
if (typeof val === 'boolean' || (typeof val === 'string' && val !== '')) {
config[dest] = (val === true || val === 'true');
}
}

function readFlatPreconfigNumber (val, dest) {
if (typeof val === 'number' || (typeof val === 'string' && /^[0-9]+$/.test(val))) {
config[dest] = +val;
}
}

function readFlatPreconfigString (val, dest) {
if (typeof val === 'string' && val !== '') {
config[dest] = val;
}
}

function readFlatPreconfigStringArray (val, dest) {
if (typeof val === 'string' && val !== '') {
config[dest] = [val];
}
}

function readFlatPreconfig (obj) {
readFlatPreconfigBoolean(obj.qunit_config_altertitle, 'altertitle');
readFlatPreconfigBoolean(obj.qunit_config_autostart, 'autostart');
readFlatPreconfigBoolean(obj.qunit_config_collapse, 'collapse');
readFlatPreconfigBoolean(obj.qunit_config_failonzerotests, 'failOnZeroTests');
readFlatPreconfigString(obj.qunit_config_filter, 'filter');
readFlatPreconfigString(obj.qunit_config_fixture, 'fixture');
readFlatPreconfigBoolean(obj.qunit_config_hidepassed, 'hidepassed');
readFlatPreconfigNumber(obj.qunit_config_maxdepth, 'maxDepth');
readFlatPreconfigString(obj.qunit_config_module, 'module');
readFlatPreconfigStringArray(obj.qunit_config_moduleid, 'moduleId');
readFlatPreconfigBoolean(obj.qunit_config_noglobals, 'noglobals');
readFlatPreconfigBoolean(obj.qunit_config_notrycatch, 'notrycatch');
readFlatPreconfigBoolean(obj.qunit_config_reorder, 'reorder');
readFlatPreconfigBoolean(obj.qunit_config_requireexpects, 'requireExpects');
readFlatPreconfigBoolean(obj.qunit_config_scrolltop, 'scrolltop');
readFlatPreconfigString(obj.qunit_config_seed, 'seed');
readFlatPreconfigStringArray(obj.qunit_config_testid, 'testId');
readFlatPreconfigNumber(obj.qunit_config_testtimeout, 'testTimeout');
}

if (process && 'env' in process) {
readFlatPreconfig(process.env);
}
readFlatPreconfig(globalThis);

// Apply a predefined QUnit.config object
//
// Ignore QUnit.config if it is a QUnit distribution instead of preconfig.
Expand Down
3 changes: 0 additions & 3 deletions src/core/processing-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ class ProcessingQueue {
if (this.taskQueue.length && !config.blocking) {
const elapsedTime = performance.now() - start;

// The updateRate ensures that a user interface (HTML Reporter) can be updated
// at least once every second. This can also prevent browsers from prompting
// a warning about long running scripts.
if (!setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
const task = this.taskQueue.shift();
Promise.resolve(task()).then(() => {
Expand Down
8 changes: 6 additions & 2 deletions src/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/html-runner/fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
5 changes: 5 additions & 0 deletions src/html-runner/urlparams.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || []);
Expand Down
Loading

0 comments on commit eef8b5c

Please sign in to comment.