qunitx-cli 0.22.2 → 0.23.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.
Files changed (3) hide show
  1. package/README.md +104 -31
  2. package/dist/cli.js +1777 -947
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -29,6 +29,7 @@ output to the terminal.
29
29
  - `--port` defaults to 1234 and auto-increments if taken; fails fast if an explicit port is unavailable
30
30
  - `--browser` flag to run tests in Chromium, Firefox, or WebKit
31
31
  - `--version` / `-v` prints the installed version
32
+ - Optional daemon mode (`qunitx daemon start`) keeps Chrome and the esbuild context warm across runs — roughly halves the wall-clock time of repeated invocations
32
33
  - Docker image for zero-install CI usage
33
34
 
34
35
  ## Installation
@@ -106,6 +107,56 @@ qunitx test/**/*.js --browser=webkit
106
107
  > npx playwright install webkit
107
108
  > ```
108
109
 
110
+ ## Daemon mode
111
+
112
+ **`qunitx daemon start` is optional.** Set `QUNITX_DAEMON=1` once (in your shell, `.env`, or a CI step) and plain `qunitx <file>` invocations auto-spawn the daemon on first use, then transparently route through it on every run after — no extra commands, no flags, nothing to remember between invocations. The explicit `daemon start` / `stop` subcommands exist only for when you want to control the lifecycle yourself.
113
+
114
+ What it does: cold-start cost dominates a single `qunitx` run — launching Chrome, loading playwright-core, and creating an esbuild incremental context together account for most of the wall-clock time on a small suite. The daemon keeps all three resources alive across runs so subsequent invocations skip them entirely — roughly **2-3× faster** on repeated runs of the same suite.
115
+
116
+ A single one-off run won't get faster from spinning the daemon up; it's an opt-in optimization aimed at two situations:
117
+
118
+ - **Local TDD loops.** Export `QUNITX_DAEMON=1` in your shell profile (or run `qunitx daemon start` once at the top of your session) and forget about it — subsequent runs reuse the daemon automatically until you `daemon stop` or 30 idle minutes pass.
119
+ - **Monorepo CI** where each package shells out to `qunitx` separately. Set `QUNITX_DAEMON=1` for the job and a single daemon is auto-spawned and reused across every package's invocation.
120
+
121
+ ```sh
122
+ # Start a background daemon for this project
123
+ qunitx daemon start
124
+
125
+ # Run tests as usual — the cli auto-detects the daemon and routes through it
126
+ qunitx test/**/*.ts
127
+
128
+ # Stop it when you're done
129
+ qunitx daemon stop
130
+ ```
131
+
132
+ `--watch` and `--open` manage their own browser lifecycle and bypass the daemon automatically. Single-invocation CI jobs (where `CI=1` is set) also bypass it by default — `QUNITX_DAEMON=1` overrides if you want it on anyway.
133
+
134
+ ### Daemon subcommands
135
+
136
+ ```sh
137
+ qunitx daemon start # Launch a detached daemon for this cwd (idempotent)
138
+ qunitx daemon stop # Ask the running daemon to exit and wait until it has
139
+ qunitx daemon status # Print the live daemon's pid, socket, and uptime
140
+ qunitx daemon restart # Stop + start in one step
141
+ ```
142
+
143
+ ### How it works
144
+
145
+ `qunitx daemon start` spawns a detached Node process listening on a per-project Unix socket (named pipe on Windows). Subsequent `qunitx` invocations detect the live socket and forward `argv` + `cwd` + `env` to the daemon; the daemon executes the run in-process and streams TAP back to your terminal. Ctrl+C is forwarded — the daemon abandons the in-flight run cleanly and stays up for the next one. A single daemon serves one run at a time; concurrent invocations queue in arrival order.
146
+
147
+ Running `qunitx daemon start` upfront is optional. With `QUNITX_DAEMON=1` set in your environment, a plain `qunitx <file>` invocation will spawn the daemon on its own when it doesn't find one already running — so the very first run pays the spawn cost and every run after that is warm. Without `QUNITX_DAEMON=1`, the cli skips auto-spawn and just runs locally; `qunitx daemon start` then becomes the explicit way to opt in.
148
+
149
+ ### Debugging the daemon
150
+
151
+ The daemon process is detached with `stdio: 'ignore'`, so its idle/startup/shutdown output never reaches a terminal. Set `QUNITX_DAEMON_LOG=<path>` before `daemon start` to redirect the daemon's stdout + stderr to a file:
152
+
153
+ ```sh
154
+ QUNITX_DAEMON_LOG=/tmp/qunitx-daemon.log qunitx daemon start
155
+ tail -f /tmp/qunitx-daemon.log
156
+ ```
157
+
158
+ The log captures startup banners, browser-crash recovery, idle-timeout shutdown, package.json-mutation restarts, and any unhandled rejection. During an active run the per-run interceptor still forwards stdout to the client; the log catches everything else.
159
+
109
160
  ## Writing Tests
110
161
 
111
162
  qunitx-cli runs [QUnitX](https://github.com/izelnakri/qunitx) tests — a superset of QUnit with async
@@ -177,17 +228,17 @@ All CLI flags can also be set in `package.json` under the `qunitx` key, so you d
177
228
  }
178
229
  ```
179
230
 
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). |
231
+ | Key | Default | Description |
232
+ | ------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
233
+ | `inputs` | `[]` | Glob patterns, file paths, or directories to use as test entry points. Merged with any paths given on the CLI. |
234
+ | `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. |
235
+ | `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. |
236
+ | `output` | `"tmp"` | Directory where compiled test bundles are written. |
237
+ | `timeout` | `20000` | Maximum milliseconds to wait for the full test suite before timing out. |
238
+ | `failFast` | `false` | Stop the run after the first failing test. |
239
+ | `port` | `1234` | Preferred HTTP server port. qunitx auto-selects a free port if this one is taken. |
240
+ | `browser` | `"chromium"` | Browser engine to use: `"chromium"`, `"firefox"`, or `"webkit"`. Overridden by `--browser` on the CLI. |
241
+ | `plugins` | `[]` | esbuild plugin specifiers loaded from your `node_modules` and applied to the test bundle. See [esbuild plugins](#esbuild-plugins). |
191
242
 
192
243
  CLI flags always override `package.json` values when both are present.
193
244
 
@@ -245,20 +296,20 @@ For file formats esbuild does not handle natively (e.g. `.vue` SFCs, `.svelte`),
245
296
 
246
297
  Each entry is one of:
247
298
 
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. |
299
+ | Form | Behavior |
300
+ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
301
+ | `"<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
302
  | `["<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). |
303
+ | `"./relative/plugin.js"` | Loads a plugin you wrote yourself. Resolved against the project root (where your `package.json` lives). |
253
304
 
254
305
  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
306
 
256
307
  ### Environment variables
257
308
 
258
- | Variable | Description |
259
- |------------------|---------------------------------------------------------------------------------------------------------------|
309
+ | Variable | Description |
310
+ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
260
311
  | `CHROME_BIN` | Path to the Chrome/Chromium executable. Required on systems where Chrome is not on `PATH` (e.g. many CI environments). Set automatically when using `browser-actions/setup-chrome` in GitHub Actions. |
261
- | `QUNITX_BROWSER` | Browser engine to use (`chromium`, `firefox`, `webkit`). Equivalent to `--browser` on the CLI. Useful in CI matrix jobs. |
312
+ | `QUNITX_BROWSER` | Browser engine to use (`chromium`, `firefox`, `webkit`). Equivalent to `--browser` on the CLI. Useful in CI matrix jobs. |
262
313
 
263
314
  If you do not provide any HTML template, qunitx falls back to its built-in `test/tests.html` boilerplate internally, so `qunitx init` is optional.
264
315
 
@@ -290,6 +341,12 @@ Options:
290
341
  --open=<binary> Open output in a specific browser binary (e.g. brave, google-chrome-lts)
291
342
  --port=<n> HTTP server port (auto-selects a free port if taken)
292
343
  --browser=<name> Browser engine: chromium (default), firefox, or webkit
344
+ --no-daemon Don't use the daemon for this run — skips a running daemon and prevents QUNITX_DAEMON auto-spawn
345
+
346
+ Subcommands:
347
+ qunitx daemon start | stop | status Manage the optional persistent daemon
348
+ qunitx init Bootstrap qunitx config + base HTML in this project
349
+ qunitx new <testFileName> Create a new qunitx test file
293
350
  ```
294
351
 
295
352
  ## Timezone
@@ -298,11 +355,11 @@ The browser inherits the **OS system timezone** automatically — no Playwright
298
355
 
299
356
  ### Setting a timezone for tests
300
357
 
301
- | Platform | How Chrome resolves the timezone | Override |
302
- |----------|----------------------------------|---------|
303
- | **Linux** | glibc reads `TZ` env var first, then `/etc/localtime` | `TZ=America/New_York npx qunitx …` works |
304
- | **macOS** | CoreFoundation reads the system timezone (ignores `TZ`) | Must set the system timezone: `sudo systemsetup -settimezone America/New_York` |
305
- | **Windows** | Reads the registry timezone (ignores `TZ`) | Must set the system timezone: `tzutil /s "Eastern Standard Time"` |
358
+ | Platform | How Chrome resolves the timezone | Override |
359
+ | ----------- | ------------------------------------------------------- | ------------------------------------------------------------------------------ |
360
+ | **Linux** | glibc reads `TZ` env var first, then `/etc/localtime` | `TZ=America/New_York npx qunitx …` works |
361
+ | **macOS** | CoreFoundation reads the system timezone (ignores `TZ`) | Must set the system timezone: `sudo systemsetup -settimezone America/New_York` |
362
+ | **Windows** | Reads the registry timezone (ignores `TZ`) | Must set the system timezone: `tzutil /s "Eastern Standard Time"` |
306
363
 
307
364
  On Linux, the `TZ` env var is the simplest way to run tests in a specific timezone:
308
365
 
@@ -356,12 +413,18 @@ module('Invoice formatting', (hooks) => {
356
413
  // Pin "now" to a fixed instant for the whole module
357
414
  const FIXED = new realDate('2024-06-01T12:00:00Z');
358
415
  globalThis.Date = class extends realDate {
359
- constructor(...args) { super(args.length ? args : [FIXED]); }
360
- static now() { return FIXED.getTime(); }
416
+ constructor(...args) {
417
+ super(args.length ? args : [FIXED]);
418
+ }
419
+ static now() {
420
+ return FIXED.getTime();
421
+ }
361
422
  };
362
423
  });
363
424
 
364
- hooks.after(() => { globalThis.Date = realDate; });
425
+ hooks.after(() => {
426
+ globalThis.Date = realDate;
427
+ });
365
428
 
366
429
  test('formats the current date correctly', (assert) => {
367
430
  assert.equal(new Date().toISOString().slice(0, 10), '2024-06-01');
@@ -377,8 +440,12 @@ import sinon from 'sinon';
377
440
  module('Debounce logic', (hooks) => {
378
441
  let clock;
379
442
 
380
- hooks.before(() => { clock = sinon.useFakeTimers({ now: new Date('2024-06-01T00:00:00Z') }); });
381
- hooks.after(() => { clock.restore(); });
443
+ hooks.before(() => {
444
+ clock = sinon.useFakeTimers({ now: new Date('2024-06-01T00:00:00Z') });
445
+ });
446
+ hooks.after(() => {
447
+ clock.restore();
448
+ });
382
449
 
383
450
  test('fires after 300 ms', (assert) => {
384
451
  // clock.tick(300) advances fake time without waiting in real time
@@ -393,11 +460,15 @@ If you need the mock active across the entire test run rather than inside a sing
393
460
  ```js
394
461
  // scripts/mock-date.js (passed as: qunitx … --before=scripts/mock-date.js)
395
462
  const realDate = globalThis.Date;
396
- const FIXED = new realDate('2024-06-01T12:00:00Z');
463
+ const FIXED = new realDate('2024-06-01T12:00:00Z');
397
464
 
398
465
  globalThis.Date = class extends realDate {
399
- constructor(...args) { super(args.length ? args : [FIXED]); }
400
- static now() { return FIXED.getTime(); }
466
+ constructor(...args) {
467
+ super(args.length ? args : [FIXED]);
468
+ }
469
+ static now() {
470
+ return FIXED.getTime();
471
+ }
401
472
  };
402
473
  ```
403
474
 
@@ -416,6 +487,8 @@ make demo # regenerate docs/demo.gif
416
487
  make release LEVEL=patch # bump version, update changelog, tag, push
417
488
  ```
418
489
 
490
+ For a tight TDD loop on this repo (or any consuming project), run `qunitx daemon start` once at the top of your session — every subsequent `qunitx` invocation reuses the warm Chrome and esbuild context, roughly halving the wait-per-iteration. AI/LLM coding agents benefit even more, since their inner loop is dozens of `qunitx <file>` invocations per feature. Caveat: agents running inside containers or CI-style environments (GitHub Actions Copilot, sandboxed coding agents) often have `CI=1` set, which bypasses the daemon by default — set `QUNITX_DAEMON=1` in those environments to opt back in.
491
+
419
492
  Use `--trace-perf` to print internal timing to stderr — useful when investigating startup or e2e regressions:
420
493
 
421
494
  ```sh