qunitx-cli 0.21.3 → 0.22.1
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/README.md +80 -15
- package/dist/cli.js +94 -25
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -14,8 +14,9 @@ output to the terminal.
|
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- Runs `.js
|
|
18
|
-
- TypeScript
|
|
17
|
+
- Runs `.js`, `.ts`, `.jsx`, and `.tsx` test files in headless Chrome, Firefox, or WebKit (Playwright + esbuild)
|
|
18
|
+
- TypeScript and JSX work with zero configuration — esbuild handles transpilation, including the React 17+ automatic JSX runtime
|
|
19
|
+
- Bring your own esbuild plugins through `package.json` for `.vue`, `.svelte`, and other custom loaders
|
|
19
20
|
- Inline source maps for accurate stack traces pointing to original source files
|
|
20
21
|
- Streams TAP-formatted output to the terminal in real time
|
|
21
22
|
- Concurrent mode (default) splits test files across all CPU cores for fast parallel runs
|
|
@@ -165,29 +166,93 @@ All CLI flags can also be set in `package.json` under the `qunitx` key, so you d
|
|
|
165
166
|
"qunitx": {
|
|
166
167
|
"inputs": ["test/**/*-test.js", "test/**/*-test.ts"],
|
|
167
168
|
"htmlPaths": ["test/tests.html"],
|
|
168
|
-
"extensions": ["js", "ts"],
|
|
169
|
+
"extensions": ["js", "ts", "jsx", "tsx"],
|
|
169
170
|
"output": "tmp",
|
|
170
171
|
"timeout": 20000,
|
|
171
172
|
"failFast": false,
|
|
172
173
|
"port": 1234,
|
|
173
|
-
"browser": "chromium"
|
|
174
|
+
"browser": "chromium",
|
|
175
|
+
"plugins": []
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
```
|
|
177
179
|
|
|
178
|
-
| Key | Default
|
|
179
|
-
| ------------ |
|
|
180
|
-
| `inputs` | `[]`
|
|
181
|
-
| `htmlPaths` | `[]`
|
|
182
|
-
| `extensions` | `["js", "ts"]`
|
|
183
|
-
| `output` | `"tmp"`
|
|
184
|
-
| `timeout` | `20000`
|
|
185
|
-
| `failFast` | `false`
|
|
186
|
-
| `port` | `1234`
|
|
187
|
-
| `browser` | `"chromium"`
|
|
180
|
+
| Key | Default | Description |
|
|
181
|
+
| ------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
182
|
+
| `inputs` | `[]` | Glob patterns, file paths, or directories to use as test entry points. Merged with any paths given on the CLI. |
|
|
183
|
+
| `htmlPaths` | `[]` | Optional HTML templates to run tests inside. Any listed `.html` file that contains `{{qunitxScript}}` or other handlebars-style tokens is treated as a test runner template. |
|
|
184
|
+
| `extensions` | `["js", "ts", "jsx", "tsx"]` | File extensions tracked for test discovery (directory scans) and watch-mode rebuild triggers. Add `"mjs"`, `"cjs"`, or any other extension your project uses. |
|
|
185
|
+
| `output` | `"tmp"` | Directory where compiled test bundles are written. |
|
|
186
|
+
| `timeout` | `20000` | Maximum milliseconds to wait for the full test suite before timing out. |
|
|
187
|
+
| `failFast` | `false` | Stop the run after the first failing test. |
|
|
188
|
+
| `port` | `1234` | Preferred HTTP server port. qunitx auto-selects a free port if this one is taken. |
|
|
189
|
+
| `browser` | `"chromium"` | Browser engine to use: `"chromium"`, `"firefox"`, or `"webkit"`. Overridden by `--browser` on the CLI. |
|
|
190
|
+
| `plugins` | `[]` | esbuild plugin specifiers loaded from your `node_modules` and applied to the test bundle. See [esbuild plugins](#esbuild-plugins). |
|
|
188
191
|
|
|
189
192
|
CLI flags always override `package.json` values when both are present.
|
|
190
193
|
|
|
194
|
+
## JSX / TSX
|
|
195
|
+
|
|
196
|
+
`.jsx` and `.tsx` files are picked up automatically — no configuration needed. The bundle uses esbuild's automatic JSX runtime so React 17+ "no `import React`" code just works:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// test/button-test.tsx
|
|
200
|
+
import { module, test } from 'qunitx';
|
|
201
|
+
import { flushSync } from 'react-dom';
|
|
202
|
+
import { createRoot } from 'react-dom/client';
|
|
203
|
+
import { Button } from '../src/button.tsx';
|
|
204
|
+
|
|
205
|
+
module('Button', (hooks) => {
|
|
206
|
+
let container;
|
|
207
|
+
hooks.beforeEach(() => {
|
|
208
|
+
container = document.createElement('div');
|
|
209
|
+
document.body.appendChild(container);
|
|
210
|
+
});
|
|
211
|
+
hooks.afterEach(() => container.remove());
|
|
212
|
+
|
|
213
|
+
test('renders the label', (assert) => {
|
|
214
|
+
flushSync(() => createRoot(container).render(<Button label="Save" />));
|
|
215
|
+
assert.equal(container.querySelector('button').textContent, 'Save');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Vue, Preact, Solid, and other JSX dialects work via a one-line override at the top of each file:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
/** @jsxImportSource vue */
|
|
224
|
+
import { createApp } from 'vue';
|
|
225
|
+
// ...JSX uses vue/jsx-runtime instead of react/jsx-runtime
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
You can also set `compilerOptions.jsxImportSource` in your `tsconfig.json` to apply the override across a directory.
|
|
229
|
+
|
|
230
|
+
## esbuild plugins
|
|
231
|
+
|
|
232
|
+
For file formats esbuild does not handle natively (e.g. `.vue` SFCs, `.svelte`), declare plugin specifiers in `package.json#qunitx.plugins`. qunitx dynamic-imports each one from your project's `node_modules` and passes it to the build:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"qunitx": {
|
|
237
|
+
"extensions": ["js", "ts", "jsx", "tsx", "vue"],
|
|
238
|
+
"plugins": [
|
|
239
|
+
"esbuild-plugin-vue-next",
|
|
240
|
+
["esbuild-svelte", { "compilerOptions": { "css": "injected" } }]
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Each entry is one of:
|
|
247
|
+
|
|
248
|
+
| Form | Behavior |
|
|
249
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
250
|
+
| `"<package-name>"` | Imports the package. If the default export is a function, it's called with no arguments to produce the plugin; otherwise the export is used as the plugin. |
|
|
251
|
+
| `["<package-name>", <options>]` | Same, but the factory is called with `<options>` as its only argument. Use this form to pass plugin-specific configuration. |
|
|
252
|
+
| `"./relative/plugin.js"` | Loads a plugin you wrote yourself. Resolved against the project root (where your `package.json` lives). |
|
|
253
|
+
|
|
254
|
+
Don't forget to add the plugin's file extension(s) to `qunitx.extensions` so directory scans and watch-mode rebuilds pick them up.
|
|
255
|
+
|
|
191
256
|
### Environment variables
|
|
192
257
|
|
|
193
258
|
| Variable | Description |
|
|
@@ -218,7 +283,7 @@ Options:
|
|
|
218
283
|
--debug Print the server URL; pipe browser console to stdout
|
|
219
284
|
--timeout=<ms> Max ms to wait for the suite to finish [default: 20000]
|
|
220
285
|
--output=<dir> Directory for compiled test assets [default: ./tmp]
|
|
221
|
-
--extensions=<...> Comma-separated file extensions to track [default: js,ts]
|
|
286
|
+
--extensions=<...> Comma-separated file extensions to track [default: js,ts,jsx,tsx]
|
|
222
287
|
--before=<file> Script to run (and optionally await) before tests start
|
|
223
288
|
--after=<file> Script to run (and optionally await) after tests finish
|
|
224
289
|
--open, -o Open output in the test browser as soon as the bundle is ready
|
package/dist/cli.js
CHANGED
|
@@ -377,6 +377,21 @@ var init_color = __esm({
|
|
|
377
377
|
}
|
|
378
378
|
});
|
|
379
379
|
|
|
380
|
+
// lib/setup/default-project-config-values.ts
|
|
381
|
+
var defaultProjectConfigValues;
|
|
382
|
+
var init_default_project_config_values = __esm({
|
|
383
|
+
"lib/setup/default-project-config-values.ts"() {
|
|
384
|
+
defaultProjectConfigValues = {
|
|
385
|
+
output: "tmp",
|
|
386
|
+
timeout: 2e4,
|
|
387
|
+
failFast: false,
|
|
388
|
+
port: 1234,
|
|
389
|
+
extensions: ["js", "ts", "jsx", "tsx"],
|
|
390
|
+
browser: "chromium"
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
380
395
|
// lib/utils/read-template.ts
|
|
381
396
|
import fs3 from "node:fs/promises";
|
|
382
397
|
import { dirname, join as join2 } from "node:path";
|
|
@@ -1987,10 +2002,10 @@ var init_browser = __esm({
|
|
|
1987
2002
|
// lib/utils/open-output-in-browser.ts
|
|
1988
2003
|
import { spawn as spawn2 } from "node:child_process";
|
|
1989
2004
|
import path6 from "node:path";
|
|
1990
|
-
import { pathToFileURL } from "node:url";
|
|
2005
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
1991
2006
|
async function openOutputInBrowser(config) {
|
|
1992
2007
|
try {
|
|
1993
|
-
const outputFile = config.watch ? `http://localhost:${config.port}` :
|
|
2008
|
+
const outputFile = config.watch ? `http://localhost:${config.port}` : pathToFileURL2(path6.join(path6.resolve(config.projectRoot, config.output), "index.html")).href;
|
|
1994
2009
|
if (typeof config.open === "string") {
|
|
1995
2010
|
spawnDetached(config.open, [outputFile]);
|
|
1996
2011
|
return;
|
|
@@ -2036,10 +2051,10 @@ var init_time_counter = __esm({
|
|
|
2036
2051
|
});
|
|
2037
2052
|
|
|
2038
2053
|
// lib/utils/run-user-module.ts
|
|
2039
|
-
import { pathToFileURL as
|
|
2054
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
2040
2055
|
async function runUserModule(modulePath, params, scriptPosition) {
|
|
2041
2056
|
try {
|
|
2042
|
-
const func = await import(
|
|
2057
|
+
const func = await import(pathToFileURL3(modulePath).href);
|
|
2043
2058
|
if (func) {
|
|
2044
2059
|
func.default ? await func.default(params) : typeof func === "function" ? await func(params) : null;
|
|
2045
2060
|
}
|
|
@@ -2140,6 +2155,11 @@ async function buildTestBundle(config, cachedContent) {
|
|
|
2140
2155
|
legalComments: "none",
|
|
2141
2156
|
target: esbuildTarget(config.browser),
|
|
2142
2157
|
sourcemap,
|
|
2158
|
+
// jsx: 'automatic' is a no-op for .ts/.js files (extension-gated by esbuild) and emits
|
|
2159
|
+
// `import { jsx } from 'react/jsx-runtime'` for .tsx/.jsx files. Per-file overrides via
|
|
2160
|
+
// tsconfig's `jsxImportSource` or a `@jsxImportSource <pkg>` pragma cover Vue/Preact/Solid.
|
|
2161
|
+
jsx: "automatic",
|
|
2162
|
+
plugins: config.plugins,
|
|
2143
2163
|
// Signal the runtime that all test modules are registered. The runtime's maybeStart()
|
|
2144
2164
|
// waits for both this event and the WebSocket 'open' event before calling QUnit.start().
|
|
2145
2165
|
// Dispatching from the bundle (rather than from a script onload attr) is reliable across
|
|
@@ -2345,7 +2365,10 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
|
2345
2365
|
in: `group-entry-${slotIndex}`,
|
|
2346
2366
|
out: `group-${slotIndex}`
|
|
2347
2367
|
})),
|
|
2348
|
-
|
|
2368
|
+
// groupEntryPlugin must run first — it owns the virtual entry-point modules every other
|
|
2369
|
+
// plugin sees. User plugins follow and apply to the resolved test files just like in
|
|
2370
|
+
// single-group mode (`buildTestBundle`).
|
|
2371
|
+
plugins: [groupEntryPlugin, ...groupConfigs[0].plugins ?? []],
|
|
2349
2372
|
nodePaths: ANCESTOR_NODE_MODULES,
|
|
2350
2373
|
bundle: true,
|
|
2351
2374
|
logLevel: "silent",
|
|
@@ -2357,6 +2380,7 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
|
2357
2380
|
target: esbuildTarget(browser),
|
|
2358
2381
|
sourcemap,
|
|
2359
2382
|
write: false,
|
|
2383
|
+
jsx: "automatic",
|
|
2360
2384
|
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
2361
2385
|
};
|
|
2362
2386
|
const hasSmallOutput = (result) => (result.outputFiles ?? []).some(
|
|
@@ -2425,6 +2449,8 @@ function buildFilteredTests(filteredTests, outputPath, config) {
|
|
|
2425
2449
|
legalComments: "none",
|
|
2426
2450
|
target: esbuildTarget(config.browser),
|
|
2427
2451
|
sourcemap,
|
|
2452
|
+
jsx: "automatic",
|
|
2453
|
+
plugins: config.plugins,
|
|
2428
2454
|
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
2429
2455
|
},
|
|
2430
2456
|
needsDisk
|
|
@@ -2620,7 +2646,8 @@ import fs10 from "node:fs";
|
|
|
2620
2646
|
import { readdir, stat, lstat } from "node:fs/promises";
|
|
2621
2647
|
import path8 from "node:path";
|
|
2622
2648
|
function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFunc) {
|
|
2623
|
-
const extensions = config.extensions ||
|
|
2649
|
+
const extensions = config.extensions || defaultProjectConfigValues.extensions;
|
|
2650
|
+
config._lastBuildEndMs ??= Date.now();
|
|
2624
2651
|
const readyPromises = [];
|
|
2625
2652
|
const parentWatchers = [];
|
|
2626
2653
|
const rescanTimers = [];
|
|
@@ -2815,6 +2842,7 @@ async function rescanDirectoryForDelta(watchPath, config, extensions, onEventFun
|
|
|
2815
2842
|
const presentPaths = /* @__PURE__ */ new Set();
|
|
2816
2843
|
const presentDirs = /* @__PURE__ */ new Set();
|
|
2817
2844
|
presentDirs.add(watchPath);
|
|
2845
|
+
const trackedToRecheck = [];
|
|
2818
2846
|
for (const entry of entries) {
|
|
2819
2847
|
if (entry.isDirectory()) {
|
|
2820
2848
|
presentDirs.add(path8.join(entry.parentPath, entry.name));
|
|
@@ -2828,8 +2856,22 @@ async function rescanDirectoryForDelta(watchPath, config, extensions, onEventFun
|
|
|
2828
2856
|
if (!(entryPath in config.fsTree)) {
|
|
2829
2857
|
if (entry.isSymbolicLink()) trackSymlinkFn?.(entryPath);
|
|
2830
2858
|
handleWatchEvent(config, extensions, "add", entryPath, onEventFunc, onFinishFunc);
|
|
2859
|
+
} else if (config._lastBuildEndMs) {
|
|
2860
|
+
trackedToRecheck.push(entryPath);
|
|
2831
2861
|
}
|
|
2832
2862
|
}
|
|
2863
|
+
const buildEndMs = config._lastBuildEndMs ?? 0;
|
|
2864
|
+
await Promise.all(
|
|
2865
|
+
trackedToRecheck.map(async (filePath) => {
|
|
2866
|
+
try {
|
|
2867
|
+
const { mtimeMs } = await stat(filePath);
|
|
2868
|
+
if (mtimeMs > buildEndMs) {
|
|
2869
|
+
handleWatchEvent(config, extensions, "change", filePath, onEventFunc, onFinishFunc);
|
|
2870
|
+
}
|
|
2871
|
+
} catch {
|
|
2872
|
+
}
|
|
2873
|
+
})
|
|
2874
|
+
);
|
|
2833
2875
|
const watchPrefix = watchPath + path8.sep;
|
|
2834
2876
|
const firedDirPrefixes = [];
|
|
2835
2877
|
for (const trackedPath of Object.keys(config.fsTree)) {
|
|
@@ -2884,6 +2926,7 @@ var CHANGE_DEDUPE_MS, SYMLINK_POLL_INTERVAL_MS, OVERLAYFS_RENAME_RETRY_MS, RESCA
|
|
|
2884
2926
|
var init_file_watcher = __esm({
|
|
2885
2927
|
"lib/setup/file-watcher.ts"() {
|
|
2886
2928
|
init_color();
|
|
2929
|
+
init_default_project_config_values();
|
|
2887
2930
|
CHANGE_DEDUPE_MS = 10;
|
|
2888
2931
|
SYMLINK_POLL_INTERVAL_MS = 500;
|
|
2889
2932
|
OVERLAYFS_RENAME_RETRY_MS = 50;
|
|
@@ -3004,7 +3047,8 @@ __export(run_exports, {
|
|
|
3004
3047
|
run: () => run
|
|
3005
3048
|
});
|
|
3006
3049
|
import fs12 from "node:fs/promises";
|
|
3007
|
-
import { normalize } from "node:path";
|
|
3050
|
+
import { join as join3, normalize } from "node:path";
|
|
3051
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
3008
3052
|
import { availableParallelism } from "node:os";
|
|
3009
3053
|
async function run(config) {
|
|
3010
3054
|
const browserPromise = config.watch ? null : launchBrowser(config);
|
|
@@ -3296,7 +3340,7 @@ async function addCachedContentMainHTML(projectRoot, cachedContent) {
|
|
|
3296
3340
|
} else {
|
|
3297
3341
|
const html = await readTemplate("setup/tests.hbs");
|
|
3298
3342
|
cachedContent.mainHTML = { filePath: `${projectRoot}/test/tests.html`, html };
|
|
3299
|
-
cachedContent.assets.add(
|
|
3343
|
+
cachedContent.assets.add(join3(resolveQunitxRoot(projectRoot), "vendor/qunit.css"));
|
|
3300
3344
|
}
|
|
3301
3345
|
return cachedContent;
|
|
3302
3346
|
}
|
|
@@ -3369,6 +3413,12 @@ function normalizeInternalAssetPathFromHTML(projectRoot, assetPath, htmlPath) {
|
|
|
3369
3413
|
const currentDirectory = htmlPath ? htmlPath.split("/").slice(0, -1).join("/") : projectRoot;
|
|
3370
3414
|
return assetPath.startsWith("./") ? normalize(`${currentDirectory}/${assetPath.slice(2)}`) : normalize(`${currentDirectory}/${assetPath}`);
|
|
3371
3415
|
}
|
|
3416
|
+
function resolveQunitxRoot(projectRoot) {
|
|
3417
|
+
const mainEntry = createRequire2(`${projectRoot}/package.json`).resolve("qunitx");
|
|
3418
|
+
const match = /^(.*[\\/]qunitx)[\\/]/.exec(mainEntry);
|
|
3419
|
+
if (!match) throw new Error(`Could not derive qunitx root from ${mainEntry}`);
|
|
3420
|
+
return match[1];
|
|
3421
|
+
}
|
|
3372
3422
|
var WATCH_NAV_TIMEOUT_MS, PAGE_CLOSE_GRACE_MS, STDOUT_FLUSH_GRACE_MS, KEEP_ALIVE_INTERVAL_MS, EXIT_CODE_SIGTERM;
|
|
3373
3423
|
var init_run = __esm({
|
|
3374
3424
|
"lib/commands/run.ts"() {
|
|
@@ -3408,7 +3458,7 @@ init_color();
|
|
|
3408
3458
|
var package_default = {
|
|
3409
3459
|
name: "qunitx-cli",
|
|
3410
3460
|
type: "module",
|
|
3411
|
-
version: "0.
|
|
3461
|
+
version: "0.22.1",
|
|
3412
3462
|
description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
3413
3463
|
author: "Izel Nakri",
|
|
3414
3464
|
license: "MIT",
|
|
@@ -3465,8 +3515,11 @@ var package_default = {
|
|
|
3465
3515
|
devDependencies: {
|
|
3466
3516
|
"js-yaml": "^4.1.1",
|
|
3467
3517
|
prettier: "^3.8.3",
|
|
3468
|
-
qunitx: "^1.2.
|
|
3469
|
-
|
|
3518
|
+
qunitx: "^1.2.9",
|
|
3519
|
+
react: "^19.2.5",
|
|
3520
|
+
"react-dom": "^19.2.5",
|
|
3521
|
+
typescript: "^6.0.3",
|
|
3522
|
+
vue: "^3.5.33"
|
|
3470
3523
|
},
|
|
3471
3524
|
volta: {
|
|
3472
3525
|
node: "24.14.0"
|
|
@@ -3502,7 +3555,7 @@ ${color("--timeout")} : change default timeout per test case
|
|
|
3502
3555
|
${color("--output")} : folder to distribute built qunitx html and js that a webservers can run[default: tmp]
|
|
3503
3556
|
${color("--failFast")} : run the target file or folders with immediate abort if a single test fails
|
|
3504
3557
|
${color("--port")} : HTTP server port (auto-selects a free port if the given port is taken)[default: 1234]
|
|
3505
|
-
${color("--extensions")} : comma-separated file extensions to track for discovery and watch-mode rebuilds[default: js,ts]
|
|
3558
|
+
${color("--extensions")} : comma-separated file extensions to track for discovery and watch-mode rebuilds[default: js,ts,jsx,tsx]
|
|
3506
3559
|
${color("--browser")} : browser engine to run tests in: chromium, firefox, webkit[default: chromium]
|
|
3507
3560
|
${color("--before")} : run a script before the tests(i.e start a new web server before tests)
|
|
3508
3561
|
${color("--after")} : run a script after the tests(i.e save test results to a file)
|
|
@@ -3561,17 +3614,8 @@ async function findProjectRoot() {
|
|
|
3561
3614
|
}
|
|
3562
3615
|
}
|
|
3563
3616
|
|
|
3564
|
-
// lib/setup/default-project-config-values.ts
|
|
3565
|
-
var defaultProjectConfigValues = {
|
|
3566
|
-
output: "tmp",
|
|
3567
|
-
timeout: 2e4,
|
|
3568
|
-
failFast: false,
|
|
3569
|
-
port: 1234,
|
|
3570
|
-
extensions: ["js", "ts"],
|
|
3571
|
-
browser: "chromium"
|
|
3572
|
-
};
|
|
3573
|
-
|
|
3574
3617
|
// lib/commands/init.ts
|
|
3618
|
+
init_default_project_config_values();
|
|
3575
3619
|
init_read_template();
|
|
3576
3620
|
async function initializeProject() {
|
|
3577
3621
|
const projectRoot = await findProjectRoot();
|
|
@@ -3658,13 +3702,17 @@ function pathToModuleName(filePath) {
|
|
|
3658
3702
|
}
|
|
3659
3703
|
|
|
3660
3704
|
// lib/setup/config.ts
|
|
3705
|
+
init_default_project_config_values();
|
|
3661
3706
|
import fs7 from "node:fs/promises";
|
|
3707
|
+
import { createRequire } from "node:module";
|
|
3708
|
+
import { pathToFileURL } from "node:url";
|
|
3662
3709
|
|
|
3663
3710
|
// lib/setup/fs-tree.ts
|
|
3711
|
+
init_default_project_config_values();
|
|
3664
3712
|
import fs6, { glob as fsGlob } from "node:fs/promises";
|
|
3665
3713
|
import path3 from "node:path";
|
|
3666
3714
|
async function buildFSTree(fileAbsolutePaths, config = {}) {
|
|
3667
|
-
const targetExtensions = config.extensions ||
|
|
3715
|
+
const targetExtensions = config.extensions || defaultProjectConfigValues.extensions;
|
|
3668
3716
|
const fsTree = {};
|
|
3669
3717
|
await Promise.all(
|
|
3670
3718
|
fileAbsolutePaths.map(async (fileAbsolutePath) => {
|
|
@@ -3849,11 +3897,13 @@ async function setupConfig() {
|
|
|
3849
3897
|
const projectRoot = await findProjectRoot();
|
|
3850
3898
|
const cliConfigFlags = parseCliFlags(projectRoot);
|
|
3851
3899
|
const projectPackageJSON = await readConfigFromPackageJSON(projectRoot);
|
|
3900
|
+
const { plugins: rawPlugins, ...userQunitx } = projectPackageJSON.qunitx ?? {};
|
|
3901
|
+
const pluginsPromise = resolvePlugins(rawPlugins, projectRoot);
|
|
3852
3902
|
const inputs2 = cliConfigFlags.inputs.concat(readInputsFromPackageJSON(projectPackageJSON));
|
|
3853
3903
|
const config = {
|
|
3854
3904
|
...defaultProjectConfigValues,
|
|
3855
3905
|
htmlPaths: [],
|
|
3856
|
-
...
|
|
3906
|
+
...userQunitx,
|
|
3857
3907
|
...cliConfigFlags,
|
|
3858
3908
|
projectRoot,
|
|
3859
3909
|
inputs: inputs2,
|
|
@@ -3874,7 +3924,10 @@ async function setupConfig() {
|
|
|
3874
3924
|
_onTestsJsServed: null
|
|
3875
3925
|
};
|
|
3876
3926
|
config.htmlPaths = normalizeHTMLPaths(config.projectRoot, config.htmlPaths);
|
|
3877
|
-
config.fsTree = await
|
|
3927
|
+
[config.fsTree, config.plugins] = await Promise.all([
|
|
3928
|
+
buildFSTree(config.testFileLookupPaths, config),
|
|
3929
|
+
pluginsPromise
|
|
3930
|
+
]);
|
|
3878
3931
|
return config;
|
|
3879
3932
|
}
|
|
3880
3933
|
async function readConfigFromPackageJSON(projectRoot) {
|
|
@@ -3888,6 +3941,22 @@ function readInputsFromPackageJSON(packageJSON) {
|
|
|
3888
3941
|
const qunitx = packageJSON.qunitx;
|
|
3889
3942
|
return qunitx && qunitx.inputs ? qunitx.inputs : [];
|
|
3890
3943
|
}
|
|
3944
|
+
function resolvePlugins(raw, projectRoot) {
|
|
3945
|
+
if (raw == null) return Promise.resolve([]);
|
|
3946
|
+
if (!Array.isArray(raw)) {
|
|
3947
|
+
console.error(`# qunitx: package.json#qunitx.plugins must be an array`);
|
|
3948
|
+
process.exit(1);
|
|
3949
|
+
}
|
|
3950
|
+
const projectRequire = createRequire(`${projectRoot}/package.json`);
|
|
3951
|
+
return Promise.all(
|
|
3952
|
+
raw.map(async (entry) => {
|
|
3953
|
+
const [spec, options] = Array.isArray(entry) ? entry : [entry];
|
|
3954
|
+
const mod = await import(pathToFileURL(projectRequire.resolve(spec)).href);
|
|
3955
|
+
const exported = mod.default ?? mod;
|
|
3956
|
+
return typeof exported === "function" ? exported(options) : exported;
|
|
3957
|
+
})
|
|
3958
|
+
);
|
|
3959
|
+
}
|
|
3891
3960
|
|
|
3892
3961
|
// cli.ts
|
|
3893
3962
|
process4.title = "qunitx";
|
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.22.1",
|
|
5
5
|
"description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
6
6
|
"author": "Izel Nakri",
|
|
7
7
|
"license": "MIT",
|
|
@@ -58,8 +58,11 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"js-yaml": "^4.1.1",
|
|
60
60
|
"prettier": "^3.8.3",
|
|
61
|
-
"qunitx": "^1.2.
|
|
62
|
-
"
|
|
61
|
+
"qunitx": "^1.2.9",
|
|
62
|
+
"react": "^19.2.5",
|
|
63
|
+
"react-dom": "^19.2.5",
|
|
64
|
+
"typescript": "^6.0.3",
|
|
65
|
+
"vue": "^3.5.33"
|
|
63
66
|
},
|
|
64
67
|
"volta": {
|
|
65
68
|
"node": "24.14.0"
|