diff --git a/.eslintrc.json b/.eslintrc.json
index f758a8d45..6b35b9e9b 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -14,6 +14,7 @@
"coverage/**",
"demos/*/qunit/**",
"demos/*/coverage/**",
+ "demos/*/tmp/**",
"demos/*/package-lock.json",
"docs/.jekyll-cache/**",
"docs/_site/**",
@@ -190,14 +191,14 @@
}
},
{
- "files": ["demos/**/*.js"],
+ "files": ["demos/**/*.js", "demos/**/*.mjs"],
"env": {
"browser": true,
"es2017": true,
"node": true
},
"parserOptions": {
- "ecmaVersion": 2018
+ "ecmaVersion": 2022
},
"rules": {
"compat/compat": "off"
diff --git a/.gitignore b/.gitignore
index c5c2d9ded..d7bd0d7bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
/coverage/
/demos/*/package-lock.json
/demos/*/node_modules/
+/demos/*/tmp/
/docs/.jekyll-cache/
/docs/_site/
/docs/Gemfile.lock
diff --git a/demos/bundlers.js b/demos/bundlers.js
new file mode 100644
index 000000000..8f6292bfa
--- /dev/null
+++ b/demos/bundlers.js
@@ -0,0 +1,116 @@
+const cp = require('child_process');
+const path = require('path');
+const DIR = path.join(__dirname, 'bundlers');
+
+function normalize (str) {
+ return str
+ .replace(/^localhost:\d+/g, 'localhost:8000')
+ .replace(/\b\d+ms\b/g, '42ms');
+}
+
+QUnit.module('bundlers', {
+ before: async (assert) => {
+ assert.timeout(60_000);
+
+ cp.execSync('npm install --no-audit --update-notifier=false', { cwd: DIR, encoding: 'utf8' });
+
+ await import('./bundlers/build.mjs');
+ }
+});
+
+QUnit.test('test', assert => {
+ const expected = `Running "connect:all" (connect) task
+Started connect web server on http://localhost:8000
+
+Running "qunit:all" (qunit) task
+Testing http://localhost:8000/tmp/test-import-default.es.html .OK
+>> passed test "import default > example"
+Testing http://localhost:8000/tmp/test-import-default.iife.html .OK
+>> passed test "import default > example"
+Testing http://localhost:8000/tmp/test-import-default.umd.html .OK
+>> passed test "import default > example"
+Testing http://localhost:8000/tmp/test-import-default.webpack.html .OK
+>> passed test "import default > example"
+Testing http://localhost:8000/tmp/test-import-indirect.es.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "import-indirect > import-default"
+>> passed test "import-indirect > import-named"
+>> passed test "import-indirect > require-default"
+Testing http://localhost:8000/tmp/test-import-indirect.iife.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "import-indirect > import-default"
+>> passed test "import-indirect > import-named"
+>> passed test "import-indirect > require-default"
+Testing http://localhost:8000/tmp/test-import-indirect.umd.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "import-indirect > import-default"
+>> passed test "import-indirect > import-named"
+>> passed test "import-indirect > require-default"
+Testing http://localhost:8000/tmp/test-import-indirect.webpack.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "import-indirect > import-default"
+>> passed test "import-indirect > import-named"
+>> passed test "import-indirect > require-default"
+Testing http://localhost:8000/tmp/test-import-named.es.html .OK
+>> passed test "import named { module,test } > example"
+Testing http://localhost:8000/tmp/test-import-named.iife.html .OK
+>> passed test "import named { module,test } > example"
+Testing http://localhost:8000/tmp/test-import-named.umd.html .OK
+>> passed test "import named { module,test } > example"
+Testing http://localhost:8000/tmp/test-import-named.webpack.html .OK
+>> passed test "import named { module,test } > example"
+Testing http://localhost:8000/tmp/test-require-default.es.html .OK
+>> passed test "require default > example"
+Testing http://localhost:8000/tmp/test-require-default.iife.html .OK
+>> passed test "require default > example"
+Testing http://localhost:8000/tmp/test-require-default.umd.html .OK
+>> passed test "require default > example"
+Testing http://localhost:8000/tmp/test-require-default.webpack.html .OK
+>> passed test "require default > example"
+Testing http://localhost:8000/tmp/test-require-indirect.es.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "require-indirect > import-default"
+>> passed test "require-indirect > import-named"
+>> passed test "require-indirect > require-default"
+Testing http://localhost:8000/tmp/test-require-indirect.iife.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "require-indirect > import-default"
+>> passed test "require-indirect > import-named"
+>> passed test "require-indirect > require-default"
+Testing http://localhost:8000/tmp/test-require-indirect.umd.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "require-indirect > import-default"
+>> passed test "require-indirect > import-named"
+>> passed test "require-indirect > require-default"
+Testing http://localhost:8000/tmp/test-require-indirect.webpack.html ......OK
+>> passed test "import default > example"
+>> passed test "import named { module,test } > example"
+>> passed test "require default > example"
+>> passed test "require-indirect > import-default"
+>> passed test "require-indirect > import-named"
+>> passed test "require-indirect > require-default"
+>> 60 tests completed in 42ms, with 0 failed, 0 skipped, and 0 todo.
+
+Done.`;
+
+ const actual = cp.execSync('node_modules/.bin/grunt test', {
+ cwd: DIR,
+ env: { PATH: process.env.PATH },
+ encoding: 'utf8'
+ });
+ assert.equal(normalize(actual).trim(), expected);
+});
diff --git a/demos/bundlers/Gruntfile.js b/demos/bundlers/Gruntfile.js
new file mode 100644
index 000000000..e679d83bc
--- /dev/null
+++ b/demos/bundlers/Gruntfile.js
@@ -0,0 +1,39 @@
+/* eslint-env node */
+module.exports = function (grunt) {
+ grunt.loadNpmTasks('grunt-contrib-connect');
+ grunt.loadNpmTasks('grunt-contrib-qunit');
+
+ grunt.initConfig({
+ connect: {
+ all: {
+ options: {
+ useAvailablePort: true,
+ base: '.'
+ }
+ }
+ },
+ qunit: {
+ options: {
+ },
+ all: ['tmp/test-*.html']
+ }
+ });
+
+ grunt.event.once('connect.all.listening', function (_host, port) {
+ grunt.config('qunit.options.httpBase', `http://localhost:${port}`);
+ // console.log(grunt.config()); // DEBUG
+ });
+
+ let results = [];
+ grunt.event.on('qunit.on.testEnd', function (test) {
+ results.push(
+ `>> ${test.status} test "${test.fullName.join(' > ')}"`
+ );
+ });
+ grunt.event.on('qunit.on.runEnd', function () {
+ grunt.log.writeln(results.join('\n'));
+ results = [];
+ });
+
+ grunt.registerTask('test', ['connect', 'qunit']);
+};
diff --git a/demos/bundlers/README.md b/demos/bundlers/README.md
new file mode 100644
index 000000000..16fe22b74
--- /dev/null
+++ b/demos/bundlers/README.md
@@ -0,0 +1,17 @@
+# QUnit ♥️ Rollup & Webpack
+
+See also and .
+
+```bash
+npm run build
+npm test
+```
+
+```
+Running "qunit:all" (qunit) task
+Testing http://localhost:8000/test.html .OK
+>> passed test "example"
+>> 1 test completed in 0ms, with 0 failed, 0 skipped, and 0 todo.
+
+Done.
+```
diff --git a/demos/bundlers/build.mjs b/demos/bundlers/build.mjs
new file mode 100644
index 000000000..a14bc4e54
--- /dev/null
+++ b/demos/bundlers/build.mjs
@@ -0,0 +1,139 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import url from 'node:url';
+
+import { rollup } from 'rollup';
+import resolve from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import webpack from 'webpack';
+
+const dirname = path.dirname(url.fileURLToPath(import.meta.url));
+const tmpDir = path.join(dirname, 'tmp');
+
+const inputs = [
+ `${dirname}/test/import-default.js`,
+ `${dirname}/test/import-named.js`,
+ `${dirname}/test/import-indirect.js`,
+ `${dirname}/test/require-default.cjs`,
+ `${dirname}/test/require-indirect.cjs`
+];
+
+const htmlTemplate = `
+
+
+{{title}}
+
+{{scriptTag}}
+
+
+
+
+
+`;
+
+// Rollup configuration
+const rollupOutputs = [
+ {
+ dir: tmpDir,
+ entryFileNames: '[name].[format].js',
+ format: 'es'
+ },
+ {
+ dir: tmpDir,
+ entryFileNames: '[name].[format].js',
+ format: 'cjs'
+ },
+ {
+ dir: tmpDir,
+ entryFileNames: '[name].[format].js',
+ format: 'iife'
+ },
+ {
+ dir: tmpDir,
+ entryFileNames: '[name].[format].js',
+ format: 'umd',
+ name: 'UNUSED'
+ }
+];
+async function * buildRollup () {
+ const plugins = [commonjs(), resolve()];
+
+ for (const input of inputs) {
+ const bundle = await rollup({
+ input,
+ plugins,
+ // Ignore "output.name" warning for require-default.iife.js
+ onwarn: () => {}
+ });
+
+ for (const outputOptions of rollupOutputs) {
+ const { output } = await bundle.write(outputOptions);
+ const fileName = output[0].fileName;
+ yield fileName;
+ }
+ }
+}
+
+// https://webpack.js.org/api/node/#webpack
+async function * buildWebpack () {
+ for (const input of inputs) {
+ const config = {
+ entry: input,
+ output: {
+ filename: path.basename(input).replace(/\.(cjs|js)$/, '.webpack.js'),
+ path: tmpDir
+ }
+ };
+ await new Promise((resolve, reject) => {
+ webpack(config, (err, stats) => {
+ if (err || stats.hasErrors()) {
+ reject(err);
+ return;
+ }
+ resolve();
+ });
+ });
+ yield config.output.filename;
+ }
+}
+
+await (async function main () {
+ // Clean up
+ fs.rmSync(tmpDir, { force: true, recursive: true });
+
+ const gRollup = buildRollup();
+ const gWebpack = buildWebpack();
+
+ for await (const fileName of gRollup) {
+ console.log('... built ' + fileName);
+
+ if (!fileName.endsWith('.cjs.js')) {
+ const html = htmlTemplate
+ .replace('{{title}}', fileName)
+ .replace('{{scriptTag}}', (
+ fileName.endsWith('.es.js')
+ ? ``
+ : ``
+ ));
+
+ fs.writeFileSync(
+ `${tmpDir}/test-${fileName.replace('.js', '')}.html`,
+ html
+ );
+ }
+ }
+ for await (const fileName of gWebpack) {
+ console.log('... built ' + fileName);
+
+ const html = htmlTemplate
+ .replace('{{title}}', fileName)
+ .replace('{{scriptTag}}', (
+ ``
+ ));
+
+ fs.writeFileSync(
+ `${tmpDir}/test-${fileName.replace('.js', '')}.html`,
+ html
+ );
+ }
+}());
diff --git a/demos/bundlers/favicon.ico b/demos/bundlers/favicon.ico
new file mode 100644
index 000000000..e69de29bb
diff --git a/demos/bundlers/package.json b/demos/bundlers/package.json
new file mode 100644
index 000000000..d9c9e28b8
--- /dev/null
+++ b/demos/bundlers/package.json
@@ -0,0 +1,17 @@
+{
+ "private": true,
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^26.0.1",
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "grunt": "1.6.1",
+ "grunt-contrib-connect": "^5.0.0",
+ "grunt-contrib-qunit": "10.1.1",
+ "qunit": "file:../..",
+ "rollup": "^4.18.0",
+ "webpack": "^5.92.0"
+ },
+ "scripts": {
+ "build": "node build.mjs",
+ "test": "grunt test"
+ }
+}
diff --git a/demos/bundlers/test/.eslintrc.json b/demos/bundlers/test/.eslintrc.json
new file mode 100644
index 000000000..74ae1d3a3
--- /dev/null
+++ b/demos/bundlers/test/.eslintrc.json
@@ -0,0 +1,9 @@
+{
+ "env": {
+ "es2020": true
+ },
+ "parserOptions": {
+ "ecmaVersion": 2020,
+ "sourceType": "module"
+ }
+}
diff --git a/demos/bundlers/test/import-default.js b/demos/bundlers/test/import-default.js
new file mode 100644
index 000000000..05c11e773
--- /dev/null
+++ b/demos/bundlers/test/import-default.js
@@ -0,0 +1,12 @@
+import QUnit from 'qunit';
+import { add } from './src.js';
+
+(globalThis.TEST_OBJECTS || (globalThis.TEST_OBJECTS = {})).i_d = QUnit;
+
+QUnit._hello_i_d = QUnit.assert._hello_i_d = 'import-default';
+
+QUnit.module('import default', function () {
+ QUnit.test('example', function (assert) {
+ assert.equal(add(2, 3), 5);
+ });
+});
diff --git a/demos/bundlers/test/import-indirect.js b/demos/bundlers/test/import-indirect.js
new file mode 100644
index 000000000..7da35c35e
--- /dev/null
+++ b/demos/bundlers/test/import-indirect.js
@@ -0,0 +1,26 @@
+/* global TEST_OBJECTS */
+import './import-default.js';
+import './import-named.js';
+import './require-default.cjs';
+
+import QUnit from 'qunit';
+
+QUnit.module('import-indirect');
+
+QUnit.test('import-default', function (assert) {
+ assert.strictEqual(TEST_OBJECTS.i_d, QUnit, 'identity');
+ assert.strictEqual(QUnit._hello_i_d, 'import-default', 'extend QUnit');
+ assert.strictEqual(assert._hello_i_d, 'import-default', 'extend assert');
+});
+
+QUnit.test('import-named', function (assert) {
+ assert.strictEqual(TEST_OBJECTS.i_n, QUnit, 'identity');
+ assert.strictEqual(QUnit._hello_i_n, 'import-named', 'extend QUnit');
+ assert.strictEqual(assert._hello_i_n, 'import-named', 'extend assert');
+});
+
+QUnit.test('require-default', function (assert) {
+ assert.strictEqual(TEST_OBJECTS.r_d, QUnit, 'identity');
+ assert.strictEqual(QUnit._hello_r_d, 'require-default', 'extend QUnit');
+ assert.strictEqual(assert._hello_r_d, 'require-default', 'extend assert');
+});
diff --git a/demos/bundlers/test/import-named.js b/demos/bundlers/test/import-named.js
new file mode 100644
index 000000000..ff8cfc3cb
--- /dev/null
+++ b/demos/bundlers/test/import-named.js
@@ -0,0 +1,12 @@
+import { QUnit, assert, module, test } from 'qunit';
+import { add } from './src.js';
+
+(globalThis.TEST_OBJECTS || (globalThis.TEST_OBJECTS = {})).i_n = QUnit;
+
+QUnit._hello_i_n = assert._hello_i_n = 'import-named';
+
+module('import named { module,test }', function () {
+ test('example', function (assert) {
+ assert.equal(add(2, 3), 5);
+ });
+});
diff --git a/demos/bundlers/test/package.json b/demos/bundlers/test/package.json
new file mode 100644
index 000000000..3dbc1ca59
--- /dev/null
+++ b/demos/bundlers/test/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/demos/bundlers/test/require-default.cjs b/demos/bundlers/test/require-default.cjs
new file mode 100644
index 000000000..d09b40c21
--- /dev/null
+++ b/demos/bundlers/test/require-default.cjs
@@ -0,0 +1,12 @@
+const QUnit = require('qunit');
+const { add } = require('./src.js');
+
+(globalThis.TEST_OBJECTS || (globalThis.TEST_OBJECTS = {})).r_d = QUnit;
+
+QUnit._hello_r_d = QUnit.assert._hello_r_d = 'require-default';
+
+QUnit.module('require default', function () {
+ QUnit.test('example', function (assert) {
+ assert.equal(add(2, 3), 5);
+ });
+});
diff --git a/demos/bundlers/test/require-indirect.cjs b/demos/bundlers/test/require-indirect.cjs
new file mode 100644
index 000000000..85aa74873
--- /dev/null
+++ b/demos/bundlers/test/require-indirect.cjs
@@ -0,0 +1,26 @@
+/* global TEST_OBJECTS */
+const QUnit = require('qunit');
+
+require('./import-default.js');
+require('./import-named.js');
+require('./require-default.cjs');
+
+QUnit.module('require-indirect');
+
+QUnit.test('import-default', function (assert) {
+ assert.strictEqual(TEST_OBJECTS.i_d, QUnit, 'identity');
+ assert.strictEqual(QUnit._hello_i_d, 'import-default', 'extend QUnit');
+ assert.strictEqual(assert._hello_i_d, 'import-default', 'extend assert');
+});
+
+QUnit.test('import-named', function (assert) {
+ assert.strictEqual(TEST_OBJECTS.i_n, QUnit, 'identity');
+ assert.strictEqual(QUnit._hello_i_n, 'import-named', 'extend QUnit');
+ assert.strictEqual(assert._hello_i_n, 'import-named', 'extend assert');
+});
+
+QUnit.test('require-default', function (assert) {
+ assert.strictEqual(TEST_OBJECTS.r_d, QUnit, 'identity');
+ assert.strictEqual(QUnit._hello_r_d, 'require-default', 'extend QUnit');
+ assert.strictEqual(assert._hello_r_d, 'require-default', 'extend assert');
+});
diff --git a/demos/bundlers/test/src.js b/demos/bundlers/test/src.js
new file mode 100644
index 000000000..3b9c0b421
--- /dev/null
+++ b/demos/bundlers/test/src.js
@@ -0,0 +1,3 @@
+export function add (a, b) {
+ return a + b;
+}
diff --git a/demos/grunt-contrib-qunit.js b/demos/grunt-contrib-qunit.js
index f92cf691e..9e441d306 100644
--- a/demos/grunt-contrib-qunit.js
+++ b/demos/grunt-contrib-qunit.js
@@ -2,10 +2,14 @@ const cp = require('child_process');
const path = require('path');
const DIR = path.join(__dirname, 'grunt-contrib-qunit');
+// Fast re-runs
+process.env.npm_config_prefer_offline = 'true';
+process.env.npm_config_update_notifier = 'false';
+process.env.npm_config_audit = 'false';
+
QUnit.module('grunt-contrib-qunit', {
before: () => {
- // Let this be quick for re-runs
- cp.execSync('npm install --prefer-offline --no-audit --update-notifier=false', { cwd: DIR, encoding: 'utf8' });
+ cp.execSync('npm install', { cwd: DIR, encoding: 'utf8' });
}
});
@@ -29,15 +33,10 @@ QUnit.test.each('failing tests', {
>> at file:fail-uncaught.html:16`]
}, (assert, [command, expected]) => {
try {
+ // This will use env CI, CHROMIUM_FLAGS, and PUPPETEER_DOWNLOAD_PATH
const ret = cp.execSync('node_modules/.bin/grunt qunit:' + command, {
cwd: DIR,
- encoding: 'utf8',
- env: {
- CHROMIUM_FLAGS: process.env.CHROMIUM_FLAGS,
- CI: process.env.CI,
- PATH: process.env.PATH,
- PUPPETEER_DOWNLOAD_PATH: process.env.PUPPETEER_DOWNLOAD_PATH
- }
+ encoding: 'utf8'
});
assert.equal(ret, null);
} catch (e) {
diff --git a/demos/karma-qunit.js b/demos/karma-qunit.js
index a9ebf5edf..9e8504b59 100644
--- a/demos/karma-qunit.js
+++ b/demos/karma-qunit.js
@@ -11,9 +11,14 @@ function normalize (str) {
.replace(/^\s+/gm, ' ');
}
+// Fast re-runs
+process.env.npm_config_prefer_offline = 'true';
+process.env.npm_config_update_notifier = 'false';
+process.env.npm_config_audit = 'false';
+
QUnit.module('karma-qunit', {
before: () => {
- cp.execSync('npm install --prefer-offline --no-audit --omit=dev --legacy-peer-deps --update-notifier=false', { cwd: DIR, encoding: 'utf8' });
+ cp.execSync('npm install --omit=dev --legacy-peer-deps', { cwd: DIR, encoding: 'utf8' });
}
});
@@ -40,11 +45,10 @@ Firefox: Executed 1 of 1 SUCCESS`, { KARMA_QUNIT_CONFIG: '1' }
try {
ret = cp.execSync('npm test', {
cwd: DIR,
- env: {
- PATH: process.env.PATH,
+ env: Object.assign({}, process.env, {
KARMA_FILES: file,
...env
- },
+ }),
encoding: 'utf8'
});
} catch (e) {
@@ -73,10 +77,9 @@ Firefox example FAILED
try {
const ret = cp.execSync('npm test', {
cwd: DIR,
- env: {
- PATH: process.env.PATH,
+ env: Object.assign({}, process.env, {
KARMA_FILES: file
- },
+ }),
encoding: 'utf8'
});
assert.equal(ret, null);
diff --git a/demos/nyc.js b/demos/nyc.js
index 834ad996b..664c755ec 100644
--- a/demos/nyc.js
+++ b/demos/nyc.js
@@ -2,9 +2,14 @@ const cp = require('child_process');
const path = require('path');
const DIR = path.join(__dirname, 'nyc');
+// Fast re-runs
+process.env.npm_config_prefer_offline = 'true';
+process.env.npm_config_update_notifier = 'false';
+process.env.npm_config_audit = 'false';
+
QUnit.module('nyc', {
before: () => {
- cp.execSync('npm install --prefer-offline --no-audit --update-notifier=false', { cwd: DIR, encoding: 'utf8' });
+ cp.execSync('npm install', { cwd: DIR, encoding: 'utf8' });
}
});
@@ -29,6 +34,12 @@ All files | 85.71 | 100 | 50 | 85.71 |
--------------|---------|----------|---------|---------|-------------------
`.trim();
- const actual = cp.execSync('npm test', { cwd: DIR, env: { PATH: process.env.PATH }, encoding: 'utf8' });
+ const actual = cp.execSync('npm test', {
+ cwd: DIR,
+ env: Object.assign({}, process.env, {
+ FORCE_COLOR: '0'
+ }),
+ encoding: 'utf8'
+ });
assert.pushResult({ result: actual.includes(expected), actual, expected });
});
diff --git a/demos/testem.js b/demos/testem.js
index 2b1c971bf..4ca4d4365 100644
--- a/demos/testem.js
+++ b/demos/testem.js
@@ -9,10 +9,14 @@ function normalize (str) {
.replace(/(\d+ ms)/g, '0 ms');
}
+// Fast re-runs
+process.env.npm_config_prefer_offline = 'true';
+process.env.npm_config_update_notifier = 'false';
+process.env.npm_config_audit = 'false';
+
QUnit.module('testem', {
before: () => {
- // Let this be quick for re-runs
- cp.execSync('npm install --prefer-offline --no-audit --update-notifier=false', { cwd: DIR, encoding: 'utf8' });
+ cp.execSync('npm install', { cwd: DIR, encoding: 'utf8' });
}
});