qunitx-cli 0.5.7 → 0.5.8
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/.dockerignore +15 -0
- package/README.md +29 -0
- package/deno.json +5 -0
- package/deno.lock +6 -2
- package/lib/commands/help.js +1 -0
- package/lib/setup/default-project-config-values.js +2 -1
- package/lib/setup/file-watcher.js +51 -44
- package/lib/setup/fs-tree.js +14 -8
- package/lib/utils/parse-cli-flags.js +7 -0
- package/package.json +1 -2
- package/lib/setup/recursive-lookup.d.ts +0 -7
package/.dockerignore
ADDED
package/README.md
CHANGED
|
@@ -133,6 +133,34 @@ qunitx some-test.js --debug
|
|
|
133
133
|
qunitx some-test.ts
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
## Configuration
|
|
137
|
+
|
|
138
|
+
All CLI flags can also be set in `package.json` under the `qunitx` key, so you don't have to repeat them on every invocation:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"qunitx": {
|
|
143
|
+
"inputs": ["test/**/*-test.js", "test/**/*-test.ts"],
|
|
144
|
+
"extensions": ["js", "ts"],
|
|
145
|
+
"output": "tmp",
|
|
146
|
+
"timeout": 20000,
|
|
147
|
+
"failFast": false,
|
|
148
|
+
"port": 1234
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Key | Default | Description |
|
|
154
|
+
|-----|---------|-------------|
|
|
155
|
+
| `inputs` | `[]` | Glob patterns, file paths, or directories to use as test entry points. Merged with any paths given on the CLI. |
|
|
156
|
+
| `extensions` | `["js", "ts"]` | File extensions tracked for test discovery (directory scans) and watch-mode rebuild triggers. Add `"mjs"`, `"cjs"`, or any other extension your project uses. |
|
|
157
|
+
| `output` | `"tmp"` | Directory where compiled test bundles are written. |
|
|
158
|
+
| `timeout` | `20000` | Maximum milliseconds to wait for the full test suite before timing out. |
|
|
159
|
+
| `failFast` | `false` | Stop the run after the first failing test. |
|
|
160
|
+
| `port` | `1234` | Preferred HTTP server port. qunitx auto-selects a free port if this one is taken. |
|
|
161
|
+
|
|
162
|
+
CLI flags always override `package.json` values when both are present.
|
|
163
|
+
|
|
136
164
|
## CLI Reference
|
|
137
165
|
|
|
138
166
|
```
|
|
@@ -144,6 +172,7 @@ Options:
|
|
|
144
172
|
--debug Print the server URL; pipe browser console to stdout
|
|
145
173
|
--timeout=<ms> Max ms to wait for the suite to finish [default: 20000]
|
|
146
174
|
--output=<dir> Directory for compiled test assets [default: ./tmp]
|
|
175
|
+
--extensions=<...> Comma-separated file extensions to track [default: js,ts]
|
|
147
176
|
--before=<file> Script to run (and optionally await) before tests start
|
|
148
177
|
--after=<file> Script to run (and optionally await) after tests finish
|
|
149
178
|
--port=<n> HTTP server port (auto-selects a free port if taken)
|
package/deno.json
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"recursive-lookup": "npm:recursive-lookup",
|
|
12
12
|
"ws": "npm:ws"
|
|
13
13
|
},
|
|
14
|
+
"tasks": {
|
|
15
|
+
"bench": "deno bench --allow-all benches/esbuild.bench.ts benches/server.bench.ts benches/tap.bench.ts benches/e2e.bench.ts",
|
|
16
|
+
"bench:check": "deno bench --allow-all --json benches/esbuild.bench.ts benches/server.bench.ts benches/tap.bench.ts benches/e2e.bench.ts | deno run --allow-all scripts/check-benchmarks.ts",
|
|
17
|
+
"bench:update": "deno bench --allow-all --json benches/esbuild.bench.ts benches/server.bench.ts benches/tap.bench.ts benches/e2e.bench.ts | deno run --allow-all scripts/check-benchmarks.ts --save"
|
|
18
|
+
},
|
|
14
19
|
"lint": {
|
|
15
20
|
"rules": {
|
|
16
21
|
"exclude": ["no-process-global", "no-node-globals", "no-window"]
|
package/deno.lock
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "5",
|
|
3
3
|
"specifiers": {
|
|
4
|
+
"jsr:@std/fmt@*": "1.0.9",
|
|
4
5
|
"npm:@types/js-yaml@*": "4.0.9",
|
|
5
6
|
"npm:@types/picomatch@*": "4.0.2",
|
|
6
7
|
"npm:@types/ws@*": "8.18.1",
|
|
@@ -24,10 +25,14 @@
|
|
|
24
25
|
"npm:qunit@^2.25.0": "2.25.0",
|
|
25
26
|
"npm:qunitx@1": "1.0.0",
|
|
26
27
|
"npm:recursive-lookup@*": "1.1.0",
|
|
27
|
-
"npm:recursive-lookup@1.1.0": "1.1.0",
|
|
28
28
|
"npm:ws@*": "8.19.0",
|
|
29
29
|
"npm:ws@^8.19.0": "8.19.0"
|
|
30
30
|
},
|
|
31
|
+
"jsr": {
|
|
32
|
+
"@std/fmt@1.0.9": {
|
|
33
|
+
"integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
31
36
|
"npm": {
|
|
32
37
|
"@babel/code-frame@7.29.0": {
|
|
33
38
|
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
|
@@ -1333,7 +1338,6 @@
|
|
|
1333
1338
|
"npm:puppeteer@^24.38.0",
|
|
1334
1339
|
"npm:qunit@^2.25.0",
|
|
1335
1340
|
"npm:qunitx@1",
|
|
1336
|
-
"npm:recursive-lookup@1.1.0",
|
|
1337
1341
|
"npm:ws@^8.19.0"
|
|
1338
1342
|
]
|
|
1339
1343
|
}
|
package/lib/commands/help.js
CHANGED
|
@@ -23,6 +23,7 @@ ${color('--timeout')} : change default timeout per test case
|
|
|
23
23
|
${color('--output')} : folder to distribute built qunitx html and js that a webservers can run[default: tmp]
|
|
24
24
|
${color('--failFast')} : run the target file or folders with immediate abort if a single test fails
|
|
25
25
|
${color('--port')} : HTTP server port (auto-selects a free port if the given port is taken)[default: 1234]
|
|
26
|
+
${color('--extensions')} : comma-separated file extensions to track for discovery and watch-mode rebuilds[default: js,ts]
|
|
26
27
|
${color('--before')} : run a script before the tests(i.e start a new web server before tests)
|
|
27
28
|
${color('--after')} : run a script after the tests(i.e save test results to a file)
|
|
28
29
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
/** Default qunitx config values: build output directory, test timeout (ms), fail-fast flag,
|
|
1
|
+
/** Default qunitx config values: build output directory, test timeout (ms), fail-fast flag, HTTP server port, and tracked file extensions. */
|
|
2
2
|
export default {
|
|
3
3
|
output: 'tmp',
|
|
4
4
|
timeout: 20000,
|
|
5
5
|
failFast: false,
|
|
6
6
|
port: 1234,
|
|
7
|
+
extensions: ['js', 'ts'],
|
|
7
8
|
};
|
|
@@ -6,52 +6,14 @@ import kleur from 'kleur';
|
|
|
6
6
|
* @returns {object}
|
|
7
7
|
*/
|
|
8
8
|
export default function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFunc) {
|
|
9
|
-
const extensions = ['js', 'ts'];
|
|
9
|
+
const extensions = config.extensions || ['js', 'ts'];
|
|
10
10
|
const fileWatchers = testFileLookupPaths.reduce((watcher, watchPath) => {
|
|
11
11
|
return Object.assign(watcher, {
|
|
12
|
-
[watchPath]: chokidar
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'#',
|
|
18
|
-
kleur
|
|
19
|
-
.magenta()
|
|
20
|
-
.bold('=================================================================='),
|
|
21
|
-
);
|
|
22
|
-
console.log('#', getEventColor(event), path.split(config.projectRoot)[1]);
|
|
23
|
-
console.log(
|
|
24
|
-
'#',
|
|
25
|
-
kleur
|
|
26
|
-
.magenta()
|
|
27
|
-
.bold('=================================================================='),
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (!global.chokidarBuild) {
|
|
31
|
-
global.chokidarBuild = true;
|
|
32
|
-
|
|
33
|
-
const result = extensions.some((extension) => path.endsWith(extension))
|
|
34
|
-
? onEventFunc(event, path)
|
|
35
|
-
: null;
|
|
36
|
-
|
|
37
|
-
if (!(result instanceof Promise)) {
|
|
38
|
-
global.chokidarBuild = false;
|
|
39
|
-
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
result
|
|
44
|
-
.then(() => {
|
|
45
|
-
onFinishFunc ? onFinishFunc(event, path) : null;
|
|
46
|
-
})
|
|
47
|
-
.catch(() => {
|
|
48
|
-
// TODO: make an index.html to display the error
|
|
49
|
-
// error type has to be derived from the error!
|
|
50
|
-
})
|
|
51
|
-
.finally(() => (global.chokidarBuild = false));
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}),
|
|
12
|
+
[watchPath]: chokidar
|
|
13
|
+
.watch(watchPath, { ignoreInitial: true })
|
|
14
|
+
.on('all', (event, filePath) =>
|
|
15
|
+
handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFinishFunc),
|
|
16
|
+
),
|
|
55
17
|
});
|
|
56
18
|
}, {});
|
|
57
19
|
|
|
@@ -65,6 +27,51 @@ export default function setupFileWatchers(testFileLookupPaths, config, onEventFu
|
|
|
65
27
|
};
|
|
66
28
|
}
|
|
67
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Routes a chokidar event to fsTree mutation and optional rebuild trigger.
|
|
32
|
+
* `unlinkDir` bypasses the extension filter so deleted directories always clean up fsTree.
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
35
|
+
export function handleWatchEvent(config, extensions, event, filePath, onEventFunc, onFinishFunc) {
|
|
36
|
+
const isFileEvent = extensions.some((ext) => filePath.endsWith(`.${ext}`));
|
|
37
|
+
|
|
38
|
+
if (!isFileEvent && event !== 'unlinkDir') return;
|
|
39
|
+
|
|
40
|
+
mutateFSTree(config.fsTree, event, filePath);
|
|
41
|
+
|
|
42
|
+
console.log(
|
|
43
|
+
'#',
|
|
44
|
+
kleur.magenta().bold('=================================================================='),
|
|
45
|
+
);
|
|
46
|
+
console.log('#', getEventColor(event), filePath.split(config.projectRoot)[1]);
|
|
47
|
+
console.log(
|
|
48
|
+
'#',
|
|
49
|
+
kleur.magenta().bold('=================================================================='),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!config._building) {
|
|
53
|
+
config._building = true;
|
|
54
|
+
|
|
55
|
+
const result = onEventFunc(event, filePath);
|
|
56
|
+
|
|
57
|
+
if (!(result instanceof Promise)) {
|
|
58
|
+
config._building = false;
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
result
|
|
64
|
+
.then(() => {
|
|
65
|
+
onFinishFunc ? onFinishFunc(event, filePath) : null;
|
|
66
|
+
})
|
|
67
|
+
.catch(() => {
|
|
68
|
+
// TODO: make an index.html to display the error
|
|
69
|
+
// error type has to be derived from the error!
|
|
70
|
+
})
|
|
71
|
+
.finally(() => (config._building = false));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
/**
|
|
69
76
|
* Mutates `fsTree` in place based on a chokidar file-system event.
|
|
70
77
|
* @returns {void}
|
package/lib/setup/fs-tree.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
// @deno-types="npm:@types/picomatch"
|
|
3
4
|
import picomatch from 'picomatch';
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
async function readDirRecursive(dir, filter) {
|
|
7
|
+
const entries = await fs.readdir(dir, { recursive: true, withFileTypes: true });
|
|
8
|
+
return entries
|
|
9
|
+
.filter((e) => e.isFile() && filter(e.name))
|
|
10
|
+
.map((e) => path.join(e.parentPath, e.name));
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* Resolves an array of file paths, directories, or glob patterns into a flat `{ absolutePath: null }` map.
|
|
9
15
|
* @returns {Promise<object>}
|
|
10
16
|
*/
|
|
11
|
-
export default async function buildFSTree(fileAbsolutePaths,
|
|
12
|
-
const targetExtensions = ['js', 'ts'];
|
|
17
|
+
export default async function buildFSTree(fileAbsolutePaths, config = {}) {
|
|
18
|
+
const targetExtensions = config.extensions || ['js', 'ts'];
|
|
13
19
|
const fsTree = {};
|
|
14
20
|
|
|
15
21
|
await Promise.all(
|
|
@@ -20,8 +26,8 @@ export default async function buildFSTree(fileAbsolutePaths, _config = {}) {
|
|
|
20
26
|
|
|
21
27
|
try {
|
|
22
28
|
if (glob.isGlob) {
|
|
23
|
-
const fileNames = await
|
|
24
|
-
return targetExtensions.some((extension) =>
|
|
29
|
+
const fileNames = await readDirRecursive(glob.base, (name) => {
|
|
30
|
+
return targetExtensions.some((extension) => name.endsWith(`.${extension}`));
|
|
25
31
|
});
|
|
26
32
|
|
|
27
33
|
fileNames.forEach((fileName) => {
|
|
@@ -35,8 +41,8 @@ export default async function buildFSTree(fileAbsolutePaths, _config = {}) {
|
|
|
35
41
|
if (entry.isFile()) {
|
|
36
42
|
fsTree[fileAbsolutePath] = null;
|
|
37
43
|
} else if (entry.isDirectory()) {
|
|
38
|
-
const fileNames = await
|
|
39
|
-
return targetExtensions.some((extension) =>
|
|
44
|
+
const fileNames = await readDirRecursive(glob.base, (name) => {
|
|
45
|
+
return targetExtensions.some((extension) => name.endsWith(`.${extension}`));
|
|
40
46
|
});
|
|
41
47
|
|
|
42
48
|
fileNames.forEach((fileName) => {
|
|
@@ -26,6 +26,13 @@ export default function parseCliFlags(projectRoot) {
|
|
|
26
26
|
return result;
|
|
27
27
|
} else if (arg.startsWith('--port')) {
|
|
28
28
|
return Object.assign(result, { port: Number(arg.split('=')[1]) });
|
|
29
|
+
} else if (arg.startsWith('--extensions')) {
|
|
30
|
+
return Object.assign(result, {
|
|
31
|
+
extensions: arg
|
|
32
|
+
.split('=')[1]
|
|
33
|
+
.split(',')
|
|
34
|
+
.map((e) => e.trim()),
|
|
35
|
+
});
|
|
29
36
|
} else if (arg.startsWith('--before')) {
|
|
30
37
|
return Object.assign(result, { before: parseModule(arg.split('=')[1]) });
|
|
31
38
|
} else if (arg.startsWith('--after')) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qunitx-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.8",
|
|
5
5
|
"description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
6
6
|
"main": "cli.js",
|
|
7
7
|
"author": "Izel Nakri",
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"kleur": "^4.1.5",
|
|
51
51
|
"picomatch": "^4.0.3",
|
|
52
52
|
"puppeteer": "^24.38.0",
|
|
53
|
-
"recursive-lookup": "1.1.0",
|
|
54
53
|
"ws": "^8.19.0"
|
|
55
54
|
},
|
|
56
55
|
"devDependencies": {
|