qunitx-cli 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.lock +0 -4
- package/lib/commands/generate.js +2 -2
- package/lib/commands/help.js +3 -3
- package/lib/commands/run/tests-in-browser.js +2 -2
- package/lib/commands/run.js +4 -4
- package/lib/setup/file-watcher.js +6 -6
- package/lib/setup/keyboard-events.js +2 -5
- package/lib/tap/display-test-result.js +2 -3
- package/lib/tap/dump-yaml.js +70 -0
- package/lib/utils/color.js +53 -0
- package/lib/utils/run-user-module.js +2 -2
- package/package.json +1 -3
- package/.dockerignore +0 -15
package/deno.lock
CHANGED
|
@@ -14,9 +14,7 @@
|
|
|
14
14
|
"npm:esbuild@~0.27.3": "0.27.4",
|
|
15
15
|
"npm:express@^5.2.1": "5.2.1",
|
|
16
16
|
"npm:js-yaml@*": "4.1.1",
|
|
17
|
-
"npm:js-yaml@^4.1.1": "4.1.1",
|
|
18
17
|
"npm:kleur@*": "4.1.5",
|
|
19
|
-
"npm:kleur@^4.1.5": "4.1.5",
|
|
20
18
|
"npm:picomatch@*": "4.0.3",
|
|
21
19
|
"npm:picomatch@^4.0.3": "4.0.3",
|
|
22
20
|
"npm:prettier@^3.8.1": "3.8.1",
|
|
@@ -1331,8 +1329,6 @@
|
|
|
1331
1329
|
"npm:cors@^2.8.6",
|
|
1332
1330
|
"npm:esbuild@~0.27.3",
|
|
1333
1331
|
"npm:express@^5.2.1",
|
|
1334
|
-
"npm:js-yaml@^4.1.1",
|
|
1335
|
-
"npm:kleur@^4.1.5",
|
|
1336
1332
|
"npm:picomatch@^4.0.3",
|
|
1337
1333
|
"npm:prettier@^3.8.1",
|
|
1338
1334
|
"npm:puppeteer@^24.38.0",
|
package/lib/commands/generate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
import
|
|
2
|
+
import { green } from '../utils/color.js';
|
|
3
3
|
import findProjectRoot from '../utils/find-project-root.js';
|
|
4
4
|
import pathExists from '../utils/path-exists.js';
|
|
5
5
|
import readBoilerplate from '../utils/read-boilerplate.js';
|
|
@@ -29,5 +29,5 @@ export default async function generateTestFiles() {
|
|
|
29
29
|
await fs.mkdir(targetFolderPaths.join('/'), { recursive: true });
|
|
30
30
|
await fs.writeFile(path, testJSContent.replace('{{moduleName}}', moduleName));
|
|
31
31
|
|
|
32
|
-
console.log(
|
|
32
|
+
console.log(green(`${path} written`));
|
|
33
33
|
}
|
package/lib/commands/help.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { blue, magenta } from '../utils/color.js';
|
|
2
2
|
import pkg from '../../package.json' with { type: 'json' };
|
|
3
3
|
|
|
4
|
-
const highlight = (text) =>
|
|
5
|
-
const color = (text) =>
|
|
4
|
+
const highlight = (text) => magenta().bold(text);
|
|
5
|
+
const color = (text) => blue(text);
|
|
6
6
|
|
|
7
7
|
/** Prints qunitx-cli usage information to stdout. */
|
|
8
8
|
export default function displayHelpOutput() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
import
|
|
2
|
+
import { blue } from '../../utils/color.js';
|
|
3
3
|
import esbuild from 'esbuild';
|
|
4
4
|
import timeCounter from '../../utils/time-counter.js';
|
|
5
5
|
import runUserModule from '../../utils/run-user-module.js';
|
|
@@ -141,7 +141,7 @@ async function runTestInsideHTMLFile(filePath, { page, server, browser }, config
|
|
|
141
141
|
let QUNIT_RESULT;
|
|
142
142
|
let targetError;
|
|
143
143
|
try {
|
|
144
|
-
console.log('#',
|
|
144
|
+
console.log('#', blue(`QUnitX running: http://localhost:${config.port}${filePath}`));
|
|
145
145
|
|
|
146
146
|
const testsDone = new Promise((resolve) => {
|
|
147
147
|
config._testRunDone = resolve;
|
package/lib/commands/run.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import { normalize } from 'node:path';
|
|
3
3
|
import { availableParallelism } from 'node:os';
|
|
4
4
|
import Puppeteer from 'puppeteer';
|
|
5
|
-
import
|
|
5
|
+
import { blue, yellow } from '../utils/color.js';
|
|
6
6
|
import runTestsInBrowser, { buildTestBundle } from './run/tests-in-browser.js';
|
|
7
7
|
import setupBrowser from '../setup/browser.js';
|
|
8
8
|
import fileWatcher from '../setup/file-watcher.js';
|
|
@@ -170,7 +170,7 @@ async function buildCachedContent(config, htmlPaths) {
|
|
|
170
170
|
} else {
|
|
171
171
|
console.log(
|
|
172
172
|
'#',
|
|
173
|
-
|
|
173
|
+
yellow(
|
|
174
174
|
`WARNING: Static html file with no {{content}} detected. Therefore ignoring ${filePath}`,
|
|
175
175
|
),
|
|
176
176
|
);
|
|
@@ -225,11 +225,11 @@ function splitIntoGroups(files, groupCount) {
|
|
|
225
225
|
function logWatcherAndKeyboardShortcutInfo(config, _server) {
|
|
226
226
|
console.log(
|
|
227
227
|
'#',
|
|
228
|
-
|
|
228
|
+
blue(`Watching files... You can browse the tests on http://localhost:${config.port} ...`),
|
|
229
229
|
);
|
|
230
230
|
console.log(
|
|
231
231
|
'#',
|
|
232
|
-
|
|
232
|
+
blue(
|
|
233
233
|
`Shortcuts: Press "qq" to abort running tests, "qa" to run all the tests, "qf" to run last failing test, "ql" to repeat last test`,
|
|
234
234
|
),
|
|
235
235
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chokidar from 'chokidar';
|
|
2
|
-
import
|
|
2
|
+
import { green, magenta, red, yellow } from '../utils/color.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Starts chokidar watchers for each lookup path and calls `onEventFunc` on JS/TS file changes, debounced via a global flag.
|
|
@@ -41,12 +41,12 @@ export function handleWatchEvent(config, extensions, event, filePath, onEventFun
|
|
|
41
41
|
|
|
42
42
|
console.log(
|
|
43
43
|
'#',
|
|
44
|
-
|
|
44
|
+
magenta().bold('=================================================================='),
|
|
45
45
|
);
|
|
46
46
|
console.log('#', getEventColor(event), filePath.split(config.projectRoot)[1]);
|
|
47
47
|
console.log(
|
|
48
48
|
'#',
|
|
49
|
-
|
|
49
|
+
magenta().bold('=================================================================='),
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
if (!config._building) {
|
|
@@ -90,10 +90,10 @@ export function mutateFSTree(fsTree, event, path) {
|
|
|
90
90
|
|
|
91
91
|
function getEventColor(event) {
|
|
92
92
|
if (event === 'change') {
|
|
93
|
-
return
|
|
93
|
+
return yellow('CHANGED:');
|
|
94
94
|
} else if (event === 'add' || event === 'addDir') {
|
|
95
|
-
return
|
|
95
|
+
return green('ADDED:');
|
|
96
96
|
} else if (event === 'unlink' || event === 'unlinkDir') {
|
|
97
|
-
return
|
|
97
|
+
return red('REMOVED:');
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { blue } from '../utils/color.js';
|
|
2
2
|
import listenToKeyboardKey from '../utils/listen-to-keyboard-key.js';
|
|
3
3
|
import runTestsInBrowser from '../commands/run/tests-in-browser.js';
|
|
4
4
|
|
|
@@ -16,10 +16,7 @@ export default function setupKeyboardEvents(config, cachedContent, connections)
|
|
|
16
16
|
abortBrowserQUnit(config, connections);
|
|
17
17
|
|
|
18
18
|
if (!config.lastFailedTestFiles) {
|
|
19
|
-
console.log(
|
|
20
|
-
'#',
|
|
21
|
-
kleur.blue(`QUnitX: No tests failed so far, so repeating the last test run`),
|
|
22
|
-
);
|
|
19
|
+
console.log('#', blue(`QUnitX: No tests failed so far, so repeating the last test run`));
|
|
23
20
|
return runTestsInBrowser(config, cachedContent, connections, config.lastRanTestFiles);
|
|
24
21
|
}
|
|
25
22
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import yaml from 'js-yaml';
|
|
1
|
+
import dumpYaml from './dump-yaml.js';
|
|
3
2
|
import indentString from '../utils/indent-string.js';
|
|
4
3
|
|
|
5
4
|
// tape TAP output: ['operator', 'stack', 'at', 'expected', 'actual']
|
|
@@ -32,7 +31,7 @@ export default function TAPDisplayTestResult(COUNTER, details) {
|
|
|
32
31
|
console.log(' ---');
|
|
33
32
|
console.log(
|
|
34
33
|
indentString(
|
|
35
|
-
|
|
34
|
+
dumpYaml({
|
|
36
35
|
name: `Assertion #${index + 1}`, // TODO: check what happens on runtime errors
|
|
37
36
|
actual: assertion.actual
|
|
38
37
|
? JSON.parse(JSON.stringify(assertion.actual, getCircularReplacer()))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML serializer for TAP assertion failure blocks.
|
|
3
|
+
* Handles the fixed schema: { name, actual, expected, message, stack, at }.
|
|
4
|
+
* Values for actual/expected are pre-sanitized via JSON.parse(JSON.stringify(...)).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Single compiled regex covering all cases where a plain YAML scalar would be misread:
|
|
8
|
+
// - YAML reserved words (null, true, false, ~, yes, no, on, off — case-insensitive)
|
|
9
|
+
// - Starts with a YAML indicator: { [ ! | > ' " % @ `
|
|
10
|
+
// - Block indicators that need a space: - ? : at start of string
|
|
11
|
+
// - Document separator: ---
|
|
12
|
+
// - Looks like a number (integer, float, hex, octal, scientific)
|
|
13
|
+
// - Timestamp-like strings that YAML 1.1 auto-casts to Date
|
|
14
|
+
// - Contains ': ' (key–value) or '#' anywhere (comment)
|
|
15
|
+
// - Empty string
|
|
16
|
+
// Also covers single-letter YAML 1.1 booleans: y/Y → true, n/N → false
|
|
17
|
+
const NEEDS_QUOTING =
|
|
18
|
+
/^$|^(null|true|false|~|yes|no|on|off|y|n)$|^[{[!|>'"#%@`]|^[-?:](\s|$)|^---|^[-+]?(\d|\.\d)|^\d{4}-\d{2}-\d{2}|: |#/i;
|
|
19
|
+
|
|
20
|
+
function needsQuoting(str) {
|
|
21
|
+
return NEEDS_QUOTING.test(str);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function dumpString(str, indent) {
|
|
25
|
+
if (str === '') return "''";
|
|
26
|
+
if (str.includes('\n')) {
|
|
27
|
+
// Block scalar |- (strip trailing newline), each line indented by current indent + 2
|
|
28
|
+
return '|-\n' + str.replace(/^/gm, `${indent} `);
|
|
29
|
+
}
|
|
30
|
+
if (needsQuoting(str)) return `'${str.replace(/'/g, "''")}'`;
|
|
31
|
+
return str;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function dumpValue(value, indent) {
|
|
35
|
+
if (value === null || value === undefined) return 'null';
|
|
36
|
+
if (typeof value === 'boolean' || typeof value === 'number') return String(value);
|
|
37
|
+
if (typeof value === 'string') return dumpString(value, indent);
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
if (value.length === 0) return '[]';
|
|
40
|
+
const next = `${indent} `;
|
|
41
|
+
return '\n' + value.map((v) => `${next}- ${dumpValue(v, next)}`).join('\n');
|
|
42
|
+
}
|
|
43
|
+
// Plain object
|
|
44
|
+
const entries = Object.entries(value);
|
|
45
|
+
if (entries.length === 0) return '{}';
|
|
46
|
+
const next = `${indent} `;
|
|
47
|
+
return '\n' + entries.map(([k, v]) => `${next}${k}: ${dumpValue(v, next)}`).join('\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Emits `key: value\n` or `key:\n ...\n` — no trailing space before block scalars.
|
|
51
|
+
function yamlLine(key, value) {
|
|
52
|
+
const v = dumpValue(value, '');
|
|
53
|
+
return v[0] === '\n' ? `${key}:${v}\n` : `${key}: ${v}\n`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Serializes the fixed TAP assertion object to a YAML string.
|
|
58
|
+
* Uses a template literal (no Object.entries overhead) for the known top-level keys.
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
export default function dumpYaml({ name, actual, expected, message, stack, at }) {
|
|
62
|
+
return (
|
|
63
|
+
`name: ${dumpString(name, '')}\n` +
|
|
64
|
+
yamlLine('actual', actual) +
|
|
65
|
+
yamlLine('expected', expected) +
|
|
66
|
+
yamlLine('message', message) +
|
|
67
|
+
yamlLine('stack', stack) +
|
|
68
|
+
yamlLine('at', at)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ANSI color helpers. Respects NO_COLOR, NODE_DISABLE_COLORS, FORCE_COLOR, and TTY
|
|
3
|
+
* detection — same logic as kleur.
|
|
4
|
+
*
|
|
5
|
+
* Use `createColors(enabled)` in tests to exercise both enabled and disabled branches directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function createColors(enabled) {
|
|
9
|
+
const c = (open, close) => (text) =>
|
|
10
|
+
enabled ? `\x1b[${open}m${text}\x1b[${close}m` : String(text);
|
|
11
|
+
|
|
12
|
+
const red = c(31, 39);
|
|
13
|
+
const green = c(32, 39);
|
|
14
|
+
const yellow = c(33, 39);
|
|
15
|
+
const blue = c(34, 39);
|
|
16
|
+
|
|
17
|
+
/** `magenta(text)` — colored text. `magenta()` — chainable: `.bold(text)`. */
|
|
18
|
+
const magenta = (text) => {
|
|
19
|
+
if (text !== undefined) return enabled ? `\x1b[35m${text}\x1b[39m` : String(text);
|
|
20
|
+
return { bold: (t) => (enabled ? `\x1b[35m\x1b[1m${t}\x1b[22m\x1b[39m` : String(t)) };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return { red, green, yellow, blue, magenta };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const enabled =
|
|
27
|
+
!process.env.NODE_DISABLE_COLORS &&
|
|
28
|
+
process.env.NO_COLOR == null &&
|
|
29
|
+
process.env.TERM !== 'dumb' &&
|
|
30
|
+
((process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== '0') || !!process.stdout?.isTTY);
|
|
31
|
+
|
|
32
|
+
const _c = createColors(enabled);
|
|
33
|
+
|
|
34
|
+
/** ANSI red text. */
|
|
35
|
+
export function red(text) {
|
|
36
|
+
return _c.red(text);
|
|
37
|
+
}
|
|
38
|
+
/** ANSI green text. */
|
|
39
|
+
export function green(text) {
|
|
40
|
+
return _c.green(text);
|
|
41
|
+
}
|
|
42
|
+
/** ANSI yellow text. */
|
|
43
|
+
export function yellow(text) {
|
|
44
|
+
return _c.yellow(text);
|
|
45
|
+
}
|
|
46
|
+
/** ANSI blue text. */
|
|
47
|
+
export function blue(text) {
|
|
48
|
+
return _c.blue(text);
|
|
49
|
+
}
|
|
50
|
+
/** ANSI magenta text. Call without arguments to chain: `magenta().bold(text)`. */
|
|
51
|
+
export function magenta(text) {
|
|
52
|
+
return _c.magenta(text);
|
|
53
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { red } from './color.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Dynamically imports `modulePath` and calls its default export with `params`; exits with code 1 on error.
|
|
@@ -15,7 +15,7 @@ export default async function runUserModule(modulePath, params, scriptPosition)
|
|
|
15
15
|
: null;
|
|
16
16
|
}
|
|
17
17
|
} catch (error) {
|
|
18
|
-
console.log('#',
|
|
18
|
+
console.log('#', red(`QUnitX ${scriptPosition} script failed:`));
|
|
19
19
|
console.trace(error);
|
|
20
20
|
console.error(error);
|
|
21
21
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qunitx-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
6
6
|
"main": "cli.js",
|
|
7
7
|
"author": "Izel Nakri",
|
|
@@ -46,8 +46,6 @@
|
|
|
46
46
|
"cheerio": "^1.2.0",
|
|
47
47
|
"chokidar": "^5.0.0",
|
|
48
48
|
"esbuild": "^0.27.3",
|
|
49
|
-
"js-yaml": "^4.1.1",
|
|
50
|
-
"kleur": "^4.1.5",
|
|
51
49
|
"picomatch": "^4.0.3",
|
|
52
50
|
"puppeteer": "^24.38.0",
|
|
53
51
|
"ws": "^8.19.0"
|