sxy-test-runner 2.3.19 → 2.3.22
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/AGENTS.md +147 -6
- package/LICENSE +21 -0
- package/dist/cli/help.js +3 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/init.js +6 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/lib/agentReport.d.ts +16 -0
- package/dist/cli/lib/agentReport.js +117 -0
- package/dist/cli/lib/agentReport.js.map +1 -0
- package/dist/cli/lib/defaultConfig.js +3 -1
- package/dist/cli/lib/defaultConfig.js.map +1 -1
- package/dist/cli/lib/doUserSetup.js +4 -0
- package/dist/cli/lib/doUserSetup.js.map +1 -1
- package/dist/cli/lib/failureEntries.d.ts +9 -0
- package/dist/cli/lib/failureEntries.js +42 -0
- package/dist/cli/lib/failureEntries.js.map +1 -0
- package/dist/cli/lib/failureLines.d.ts +3 -0
- package/dist/cli/lib/failureLines.js +33 -0
- package/dist/cli/lib/failureLines.js.map +1 -0
- package/dist/cli/lib/listenToUser.d.ts +2 -1
- package/dist/cli/lib/listenToUser.js +6 -3
- package/dist/cli/lib/listenToUser.js.map +1 -1
- package/dist/cli/lib/reRunManager.d.ts +2 -1
- package/dist/cli/lib/reRunManager.js +9 -2
- package/dist/cli/lib/reRunManager.js.map +1 -1
- package/dist/cli/lib/runTests.js +65 -14
- package/dist/cli/lib/runTests.js.map +1 -1
- package/dist/cli/lib/statusFile.d.ts +12 -0
- package/dist/cli/lib/statusFile.js +60 -0
- package/dist/cli/lib/statusFile.js.map +1 -0
- package/dist/cli/lib/validateConfig.js +7 -0
- package/dist/cli/lib/validateConfig.js.map +1 -1
- package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.js +24 -23
- package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.js.map +1 -1
- package/dist/cli/once.js +3 -0
- package/dist/cli/once.js.map +1 -1
- package/dist/cli/watch.js +48 -13
- package/dist/cli/watch.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +47 -1
package/AGENTS.md
CHANGED
|
@@ -85,6 +85,12 @@ npx sxy-test-runner once --filter-describe "users"
|
|
|
85
85
|
# Run tests whose names contain "creates a user"
|
|
86
86
|
npx sxy-test-runner once --filter-it "creates a user"
|
|
87
87
|
|
|
88
|
+
# Watch mode, reporting each run as single agent readable lines
|
|
89
|
+
npx sxy-test-runner watch --agent
|
|
90
|
+
|
|
91
|
+
# Watch mode, recording each run's result where another process can read it
|
|
92
|
+
npx sxy-test-runner watch --status-file .sxyt-status.txt
|
|
93
|
+
|
|
88
94
|
# Generate an initial config
|
|
89
95
|
npx sxy-test-runner init
|
|
90
96
|
```
|
|
@@ -95,7 +101,13 @@ configured pattern. Both options are resolved relative to `testsBase` and can be
|
|
|
95
101
|
|
|
96
102
|
`--filter-describe` and `--filter-it` apply case-insensitive substring filters to describe
|
|
97
103
|
and test names. They work in both watch and `once` modes and can be combined. If the
|
|
98
|
-
filters select no runnable tests, `once` exits with code `4
|
|
104
|
+
filters select no runnable tests, `once` exits with code `5` - `4` means no test file
|
|
105
|
+
matched the `tests` pattern in the first place.
|
|
106
|
+
|
|
107
|
+
`--agent` and `--status-file` are watch-only, and are the two ways to follow a watch session
|
|
108
|
+
from outside it: `--agent` streams each run to stdout as single lines, `--status-file` writes
|
|
109
|
+
the latest run's result to a file. Both are covered under "Watch mode" below. `once` ignores
|
|
110
|
+
them - it already ends with an exit code.
|
|
99
111
|
|
|
100
112
|
Describe and test filters run after the selected test files have loaded. The runner gives
|
|
101
113
|
each test file a fresh ESM instance and propagates it through the file's static dependency
|
|
@@ -233,9 +245,14 @@ on parallel `it` execution.
|
|
|
233
245
|
|
|
234
246
|
Watch mode builds a static dependency tree for every test. When a file changes, it reruns
|
|
235
247
|
the changed test itself and tests whose static import graph includes that file. If
|
|
236
|
-
`watch.reRunFailingTests` is true,
|
|
237
|
-
|
|
238
|
-
|
|
248
|
+
`watch.reRunFailingTests` is true (the default), test files that failed earlier in the
|
|
249
|
+
session are included in every run until they pass, so a failure stays visible instead of
|
|
250
|
+
disappearing the moment an unrelated file is edited. Keyboard commands normally require
|
|
251
|
+
Enter; set `watch.ttyActionsOnKeypress` to `true` to trigger `r`, `l`, `a`, and `f`
|
|
252
|
+
immediately on keypress in a TTY session.
|
|
253
|
+
|
|
254
|
+
Changes that arrive while a run is in flight are folded into the next run rather than
|
|
255
|
+
queueing a run each, so a burst of edits produces one rerun covering all of them.
|
|
239
256
|
|
|
240
257
|
Important limitations:
|
|
241
258
|
|
|
@@ -247,7 +264,129 @@ Important limitations:
|
|
|
247
264
|
process. Keep mutable access sequential or provide application-level synchronization,
|
|
248
265
|
and arrange explicit cleanup for resources such as servers and database connections.
|
|
249
266
|
- Use `once` instead of watch mode for autonomous agent verification so the command
|
|
250
|
-
terminates and returns a meaningful status.
|
|
267
|
+
terminates and returns a meaningful status. To follow someone else's long running watch
|
|
268
|
+
session instead, see "Following a watch session as an agent" below.
|
|
269
|
+
|
|
270
|
+
### Watching someone else's watch session
|
|
271
|
+
|
|
272
|
+
`--status-file <file>` (`-s`, or `watch.statusFile` in the config) makes watch mode record
|
|
273
|
+
each run's result where another process can read it, rather than in the terminal only. The
|
|
274
|
+
first line is `<code> - <description>`, the second is when the run finished, then the summary,
|
|
275
|
+
then any failures, with the run's duration last:
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
1 - 2 tests failed
|
|
279
|
+
2026-08-03 20:14:52 +01:00
|
|
280
|
+
Tests: 8/10
|
|
281
|
+
Items: 4/5
|
|
282
|
+
Test Files: 1/2
|
|
283
|
+
Failures:
|
|
284
|
+
f: tests/users.test.js
|
|
285
|
+
d: creating a user
|
|
286
|
+
t: rejects a duplicate email
|
|
287
|
+
e: AssertionError: expected 409 to equal 200
|
|
288
|
+
t: sends a welcome email
|
|
289
|
+
e: Error: connect ECONNREFUSED 127.0.0.1:2525
|
|
290
|
+
1,204ms
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Failures nest as `f:` file, `d:` describe, `t:` test, `e:` error - one line each. A file that
|
|
294
|
+
would not load gives `e: did not load: <error>` under its `f:` line, and a describe that
|
|
295
|
+
threw gives `e: threw: <error>` under its `d:` line. Diffs, logs and stack traces are only in
|
|
296
|
+
the terminal output.
|
|
297
|
+
|
|
298
|
+
| code | meaning |
|
|
299
|
+
| --- | --- |
|
|
300
|
+
| -1 | watch mode has started, but no run has finished |
|
|
301
|
+
| 0 | everything passed |
|
|
302
|
+
| 1 | tests ran and failed |
|
|
303
|
+
| 2 | the run died partway - a test file would not load, or a describe threw |
|
|
304
|
+
| 3 | nothing ran - no test files were found, or the filters matched nothing |
|
|
305
|
+
|
|
306
|
+
Code `2` is worth handling separately from `1`: it usually means the suite broke rather
|
|
307
|
+
than the code under test. Watch mode writes `-1` on startup, so a status from a previous
|
|
308
|
+
session is never read as current, and each write is renamed into place, so a poller only
|
|
309
|
+
ever sees a complete status.
|
|
310
|
+
|
|
311
|
+
### Following a watch session as an agent
|
|
312
|
+
|
|
313
|
+
`--agent` (or `watch.agent` in the config) replaces watch mode's usual output with one line
|
|
314
|
+
per event, for an agent reading the process's stdout rather than a terminal. It applies to
|
|
315
|
+
watch mode only - `once` already ends with an exit code, so a config setting it is ignored
|
|
316
|
+
there.
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
[sxyt] init | pid=16744
|
|
320
|
+
[sxyt] found | files=2
|
|
321
|
+
[sxyt] run | n=1 | files=2 | why=startup
|
|
322
|
+
[sxyt] fail | n=1 | file=tests/users.test.js | describe=creating a user | test=rejects a duplicate email | error=AssertionError: expected 409 to equal 200
|
|
323
|
+
[sxyt] done | n=1 | code=1 | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=150
|
|
324
|
+
[sxyt] watching | files=2
|
|
325
|
+
[sxyt] change | file=src/orders.js | event=update
|
|
326
|
+
[sxyt] run | n=2 | files=2 | why=auto
|
|
327
|
+
[sxyt] fail | n=2 | file=tests/users.test.js | describe=creating a user | test=rejects a duplicate email | error=AssertionError: expected 409 to equal 200
|
|
328
|
+
[sxyt] done | n=2 | code=1 | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=169
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The grammar is `[sxyt] <event>` followed by ` | <key>=<value>` pairs. Values are never
|
|
332
|
+
quoted: newlines in free text are collapsed to spaces and any `|` becomes `/`, so one event
|
|
333
|
+
is always exactly one line and a field boundary is always a real one.
|
|
334
|
+
|
|
335
|
+
| event | meaning |
|
|
336
|
+
| --- | --- |
|
|
337
|
+
| `init` | the process is up, before the config is read - proof it started at all |
|
|
338
|
+
| `found` | test discovery finished |
|
|
339
|
+
| `run` | a run has started. `why` is `startup`, `auto` (a file changed), or `all`/`last`/`failed` from a keyboard command |
|
|
340
|
+
| `fail` | one failure. `describe` is absent when a file would not load, `test` when the describe itself threw |
|
|
341
|
+
| `done` | the run finished |
|
|
342
|
+
| `change` | a watched file changed |
|
|
343
|
+
| `watching` | startup is complete and the watcher is live |
|
|
344
|
+
|
|
345
|
+
`n` ties a run's lines together. Failures are emitted before their `done`, so `done` is a
|
|
346
|
+
complete end-of-run marker: wait for it and everything about that run has already arrived.
|
|
347
|
+
|
|
348
|
+
`code` on `done` is the same status code the status file uses, so `0` is a pass and `2`
|
|
349
|
+
still means the run died partway rather than merely failing.
|
|
350
|
+
|
|
351
|
+
`covers` on the `run` line says what the run was built from, and so how far its result
|
|
352
|
+
generalises:
|
|
353
|
+
|
|
354
|
+
| covers | the run contained |
|
|
355
|
+
| --- | --- |
|
|
356
|
+
| `all` | every discovered test file - the startup run, or `a` from the keyboard |
|
|
357
|
+
| `changed+failing` | the changed files and every file still failing (`watch.reRunFailingTests` on, the default) |
|
|
358
|
+
| `changed` | only the changed files (`watch.reRunFailingTests` off) |
|
|
359
|
+
| `last` | the previous run's file set |
|
|
360
|
+
| `failing` | only the files still failing |
|
|
361
|
+
|
|
362
|
+
**No incremental run can tell you the suite is green.** `code` and the tallies describe
|
|
363
|
+
only the files that run contained. `failing` is broader - it counts the test files still
|
|
364
|
+
known to be failing across the session - but it is still only knowledge from files that
|
|
365
|
+
have actually been run, and a run happens only when the watcher both notices a change and
|
|
366
|
+
maps it to a test. Either half can miss:
|
|
367
|
+
|
|
368
|
+
- Nothing is watched but `watch.watchFiles` under `watch.watchFilesBase` - `**/*.js` from
|
|
369
|
+
the project root by default. Editing `.env`, a `.json` fixture, a template, a database
|
|
370
|
+
seed, or anything outside that base changes what the tests do and produces **no run at
|
|
371
|
+
all**. The stream simply stays quiet, and the previous `done` remains the last word while
|
|
372
|
+
silently going stale.
|
|
373
|
+
- Even for a watched file, the changed-to-test mapping is the static import graph, so it
|
|
374
|
+
misses dynamic imports and anything reached through a `._cached_.*` boundary. A test file
|
|
375
|
+
that was neither changed nor already failing is not re-run, so a break in it never reaches
|
|
376
|
+
`failing`.
|
|
377
|
+
|
|
378
|
+
So `failing=0` means "nothing is known to be broken", not "everything passes", and silence
|
|
379
|
+
means "no watched file changed", not "still green". To actually verify the suite, use a run
|
|
380
|
+
with `covers=all` - the startup run, `a` at the keyboard, or a separate `once` - and read
|
|
381
|
+
`code` and `failing` from that. For the ordinary edit-test loop in between, `failing` is the
|
|
382
|
+
right signal: with the default `watch.reRunFailingTests`, a failure keeps being re-run and
|
|
383
|
+
re-reported until it passes.
|
|
384
|
+
|
|
385
|
+
Widening `watch.watchFiles` (for example to `**/*.{js,json,env}`) closes the first gap where
|
|
386
|
+
it matters, at the cost of more reruns.
|
|
387
|
+
|
|
388
|
+
Diffs, logs and stack traces are not in the agent stream; errors are reduced to one line.
|
|
389
|
+
Use `once` when a finite run and an exit code are all that is needed.
|
|
251
390
|
|
|
252
391
|
## Configuration reference
|
|
253
392
|
|
|
@@ -284,7 +423,9 @@ export default {
|
|
|
284
423
|
watchFilesIgnore: '**/node_modules/**',
|
|
285
424
|
runAllOnStartup: true,
|
|
286
425
|
reRunFailingTests: true,
|
|
287
|
-
ttyActionsOnKeypress: false
|
|
426
|
+
ttyActionsOnKeypress: false,
|
|
427
|
+
statusFile: false,
|
|
428
|
+
agent: false
|
|
288
429
|
}
|
|
289
430
|
}
|
|
290
431
|
```
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robert Sandiford
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/cli/help.js
CHANGED
|
@@ -29,6 +29,8 @@ export function help() {
|
|
|
29
29
|
' -f, --filter <glob> Further constrain the configured test pattern',
|
|
30
30
|
' -d, --filter-describe <substring> Run matching describe blocks',
|
|
31
31
|
' -i, --filter-it <substring> Run matching tests',
|
|
32
|
+
' -s, --status-file <file> Write the latest watch run status to a file',
|
|
33
|
+
' --agent Report a watch session as single agent readable lines',
|
|
32
34
|
'',
|
|
33
35
|
'Watch controls:',
|
|
34
36
|
' r/l + Enter Re-run the last set of tests',
|
|
@@ -44,7 +46,7 @@ export function help() {
|
|
|
44
46
|
` ${cliName} once --config config/sxyt.config.js`,
|
|
45
47
|
'',
|
|
46
48
|
`Configuration: ${configNames} in ${configDirectories}.`,
|
|
47
|
-
`Executable aliases:
|
|
49
|
+
`Executable aliases: ${cliName}, sxy-test, ${packageName}.`
|
|
48
50
|
].join('\n'));
|
|
49
51
|
}
|
|
50
52
|
//# sourceMappingURL=help.js.map
|
package/dist/cli/help.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAElC,MAAM,UAAU,IAAI;IAChB,MAAM,WAAW,GAAG,eAAe;SAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,eAAe,CAAC;SACnC,IAAI,CAAC,MAAM,CAAC,CAAA;IACjB,MAAM,iBAAiB,GAAG,eAAe;SACpC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;SACtE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEjB,GAAG,CAAC;QACA,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;QAC7B,EAAE;QACF,QAAQ;QACR,KAAK,OAAO,oBAAoB;QAChC,KAAK,OAAO,iBAAiB;QAC7B,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,OAAO;QACnB,EAAE;QACF,WAAW;QACX,iEAAiE;QACjE,2CAA2C;QAC3C,+CAA+C;QAC/C,yBAAyB;QACzB,EAAE;QACF,UAAU;QACV,8DAA8D;QAC9D,iEAAiE;QACjE,0EAA0E;QAC1E,mEAAmE;QACnE,yDAAyD;QACzD,EAAE;QACF,iBAAiB;QACjB,sDAAsD;QACtD,0CAA0C;QAC1C,8CAA8C;QAC9C,qEAAqE;QACrE,EAAE;QACF,WAAW;QACX,KAAK,OAAO,EAAE;QACd,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,4CAA4C;QACxD,KAAK,OAAO,8BAA8B;QAC1C,KAAK,OAAO,sCAAsC;QAClD,EAAE;QACF,kBAAkB,WAAW,OAAO,iBAAiB,GAAG;QACxD,
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAElC,MAAM,UAAU,IAAI;IAChB,MAAM,WAAW,GAAG,eAAe;SAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,eAAe,CAAC;SACnC,IAAI,CAAC,MAAM,CAAC,CAAA;IACjB,MAAM,iBAAiB,GAAG,eAAe;SACpC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;SACtE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEjB,GAAG,CAAC;QACA,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;QAC7B,EAAE;QACF,QAAQ;QACR,KAAK,OAAO,oBAAoB;QAChC,KAAK,OAAO,iBAAiB;QAC7B,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,OAAO;QACnB,EAAE;QACF,WAAW;QACX,iEAAiE;QACjE,2CAA2C;QAC3C,+CAA+C;QAC/C,yBAAyB;QACzB,EAAE;QACF,UAAU;QACV,8DAA8D;QAC9D,iEAAiE;QACjE,0EAA0E;QAC1E,mEAAmE;QACnE,yDAAyD;QACzD,kFAAkF;QAClF,4FAA4F;QAC5F,EAAE;QACF,iBAAiB;QACjB,sDAAsD;QACtD,0CAA0C;QAC1C,8CAA8C;QAC9C,qEAAqE;QACrE,EAAE;QACF,WAAW;QACX,KAAK,OAAO,EAAE;QACd,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,4CAA4C;QACxD,KAAK,OAAO,8BAA8B;QAC1C,KAAK,OAAO,sCAAsC;QAClD,EAAE;QACF,kBAAkB,WAAW,OAAO,iBAAiB,GAAG;QACxD,uBAAuB,OAAO,eAAe,WAAW,GAAG;KAC9D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AACjB,CAAC","sourcesContent":["import chalk from '../packages/chalk.js'\nimport { packageName, cliName, configFileNames, configLocations } from '../config.js'\nimport { out } from '../output.js'\n\nexport function help(): void {\n const configNames = configFileNames\n .map(name => `${name}.{js,cjs,mjs}`)\n .join(' or ')\n const configDirectories = configLocations\n .map(location => location === '' ? 'the project root' : `${location}/`)\n .join(' or ')\n\n out([\n chalk.brightblue(packageName),\n '',\n 'Usage:',\n ` ${cliName} [watch] [options]`,\n ` ${cliName} once [options]`,\n ` ${cliName} init`,\n ` ${cliName} help`,\n '',\n 'Commands:',\n ' watch Run tests and watch for related file changes (default)',\n ' once Run the test suite once and exit',\n ' init Create an initial configuration file',\n ' help Show this help',\n '',\n 'Options:',\n ' -c, --config <file> Use a specific configuration file',\n ' -t, --test-files <glob> Override the configured test pattern',\n ' -f, --filter <glob> Further constrain the configured test pattern',\n ' -d, --filter-describe <substring> Run matching describe blocks',\n ' -i, --filter-it <substring> Run matching tests',\n ' -s, --status-file <file> Write the latest watch run status to a file',\n ' --agent Report a watch session as single agent readable lines',\n '',\n 'Watch controls:',\n ' r/l + Enter Re-run the last set of tests',\n ' a + Enter Re-run all tests',\n ' f + Enter Re-run failing tests',\n ' Set watch.ttyActionsOnKeypress to true to run these without Enter',\n '',\n 'Examples:',\n ` ${cliName}`,\n ` ${cliName} once`,\n ` ${cliName} watch --test-files \"**/*.focused.test.js\"`,\n ` ${cliName} once --filter \"**/users/**\"`,\n ` ${cliName} once --config config/sxyt.config.js`,\n '',\n `Configuration: ${configNames} in ${configDirectories}.`,\n `Executable aliases: ${cliName}, sxy-test, ${packageName}.`\n ].join('\\n'))\n}\n"]}
|
package/dist/cli/init.js
CHANGED
|
@@ -126,6 +126,12 @@ export default {
|
|
|
126
126
|
// run watch mode keyboard commands immediately, without waiting for Enter
|
|
127
127
|
ttyActionsOnKeypress: false,
|
|
128
128
|
|
|
129
|
+
// file to write the latest run status to, or false for none
|
|
130
|
+
statusFile: false,
|
|
131
|
+
|
|
132
|
+
// report the session as single agent readable lines instead of the usual output
|
|
133
|
+
agent: false,
|
|
134
|
+
|
|
129
135
|
},
|
|
130
136
|
|
|
131
137
|
}`;
|
package/dist/cli/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,MAAM,UAAU,IAAI;IAChB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEhC,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;QAC3C,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;YAC3C,IAAI,GAAG,CAAA;YACP,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,KAAK,CAAC,CAAA;YAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IAED,SAAS,mBAAmB,CAAC,GAAW;QACpC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,yCAAyC,GAAG,EAAE;cACvE,kEAAkE,OAAO,SAAS,CAAC,CAAA;IAC7F,CAAC;IAED,MAAM,0BAA0B,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IACnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,2CAA2C;cACpE,gEAAgE;cAChE,iFAAiF,CAAC,CAAA;IAC5F,CAAC;IAED,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAA;IAC1E,mEAAmE;IACnE,MAAM,WAAW,GAAsB,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;IAEjE,MAAM,gBAAgB,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,CAAC;QACpD,aAAa;QACb,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QACxE,SAAS;QACT,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;IAE7E,MAAM,aAAa,GACvB
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,MAAM,UAAU,IAAI;IAChB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEhC,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;QAC3C,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;YAC3C,IAAI,GAAG,CAAA;YACP,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,KAAK,CAAC,CAAA;YAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IAED,SAAS,mBAAmB,CAAC,GAAW;QACpC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,yCAAyC,GAAG,EAAE;cACvE,kEAAkE,OAAO,SAAS,CAAC,CAAA;IAC7F,CAAC;IAED,MAAM,0BAA0B,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IACnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,2CAA2C;cACpE,gEAAgE;cAChE,iFAAiF,CAAC,CAAA;IAC5F,CAAC;IAED,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAA;IAC1E,mEAAmE;IACnE,MAAM,WAAW,GAAsB,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;IAEjE,MAAM,gBAAgB,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,CAAC;QACpD,aAAa;QACb,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QACxE,SAAS;QACT,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;IAE7E,MAAM,aAAa,GACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8FE,CAAA;IAEE,EAAE,CAAC,aAAa,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAA;IAEjD,GAAG,CAAC,KAAK,CAAC,WAAW,CAAA,IAAI,WAAW,sBAAsB,GAAG,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAA;AAC9F,CAAC","sourcesContent":["import { join } from 'path'\nimport chalk from '../packages/chalk.js'\nimport { packageName, cliName, configFileNames, configLocations } from '../config.js'\nimport { out } from '../output.js'\nimport fs from 'fs'\n\nexport function init(): void {\n const projectDir = process.cwd()\n\n for (const configLocation of configLocations) {\n for (const configFileName of configFileNames) {\n let loc\n loc = join(projectDir, configLocation, configFileName + '.js')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n loc = join(projectDir, configLocation, configFileName + '.cjs')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n loc = join(projectDir, configLocation, configFileName + '.mjs')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n }\n }\n\n function configAlreadyExists(loc: string): void {\n out(chalk.orange`[${packageName}] A matching config already exists at ${loc}`\n + `\\nPlease remove this config before generating a new one with \\`${cliName} init\\``)\n }\n\n const projectPackageJsonLocation = join(projectDir, 'package.json')\n if (!fs.existsSync(projectPackageJsonLocation)) {\n out(chalk.orange`[${packageName}] No package.json exists in this location`\n + '\\nPlease initialise the package with `yarn init` on `npm init`'\n + ' before running this command, and run this command from the project root folder')\n }\n\n const packageJsonRaw = fs.readFileSync(projectPackageJsonLocation, 'utf8')\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const packageJson: { type?: string } = JSON.parse(packageJsonRaw)\n\n const targetConfigName = (packageJson.type === 'module')\n // ES6 module\n ? join(projectDir, configLocations[0] ?? '', configFileNames[0] + '.js')\n // common\n : join(projectDir, configLocations[0] ?? '', configFileNames[0] + '.mjs')\n\n const defaultConfig =\n`\n// @ts-check\n\n/** @satisfies {import('sxy-test-runner').Config} */\nexport default {\n\n // base folder for tests match and ignore patterns\n testsBase: '.',\n\n // glob pattern or array of glob patterns of test files\n tests: [\n '**/*.sxyt.{js,mjs,jsx}',\n '**/*.test.{js,mjs,jsx}',\n '**/*.spec.{js,mjs,jsx}',\n '**/tests?/*.{js,mjs,jsx}',\n '**/__tests?__/*.{js,mjs,jsx}'\n ],\n\n // glob pattern or array of patterns of test files to ignore\n testsIgnore: ['**/node_modules/**/*'],\n\n // whether to show the full error trace when an error occurs in a test file\n showErrorTrace: false,\n\n // tasks to run on startup\n // accepts a function, file location (relative to project base), or array of functions and or files\n setup: undefined,\n\n // tasks to run before each test file is run\n beforeEachFile: undefined,\n\n // tasks to run after each test file is run\n afterEachFile: undefined,\n\n // tasks to run before each describe block is processed\n // if the function returns an object with named properties, or the file exports named exports,\n // these will be passed to the callback function in argument 0, and can be access by destructruing e.g.\n // describe('thing', ({exportA, exportB}) => { it('should', ...) })\n beforeEachDescribe: undefined,\n\n // tasks to run after each describe block\n afterEachDescribe: undefined,\n\n // tasks to run before each it/test block\n // function returns and file exports will be passed to the it/test block as with describe above, e.g.\n // it('should', ({exportA, exportB}) => { assert('something') })\n beforeEachTest: undefined,\n\n // tasks to run after each it/test block\n afterEachTest: undefined,\n\n // how to execute the different parts of test files\n execution: {\n\n // run test files in 'parallel' or 'sequential'ly\n files: 'sequential',\n\n // run describe blocks within a test file in 'parallel' or 'sequential'ly\n describes: 'sequential',\n\n // run it/test blocks within a describe blocks in 'parallel' or 'sequential'ly\n tests: 'sequential'\n\n },\n\n // watch mode configurations\n watch: {\n\n //// the base directory to watch files in\n watchFilesBase: '.',\n\n //// glob filter or array of glob filters of files to watch\n watchFiles: '**/*.js',\n\n //// glob filter or array of globl filters of files to ignore\n watchFilesIgnore: '**/node_modules/**/*',\n\n // run all tests when starting the watcher\n runAllOnStartup: true,\n\n // re run all previously failed tests on each test run, until they pass\n reRunFailingTests: true,\n\n // run watch mode keyboard commands immediately, without waiting for Enter\n ttyActionsOnKeypress: false,\n\n // file to write the latest run status to, or false for none\n statusFile: false,\n\n // report the session as single agent readable lines instead of the usual output\n agent: false,\n\n },\n\n}`\n\n fs.writeFileSync(targetConfigName, defaultConfig)\n\n out(chalk.mediumgreen`[${packageName}] Created config at ` + chalk.blue(targetConfigName))\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FinalConfig, TestsResultsSummary } from '../../types.js';
|
|
2
|
+
import type { FailureEntry } from './failureEntries.js';
|
|
3
|
+
import type { RunType } from './reRunManager.js';
|
|
4
|
+
import type { StatusCode } from './statusFile.js';
|
|
5
|
+
export type RunReason = RunType | 'startup';
|
|
6
|
+
export declare function agentEnabled(config: FinalConfig): boolean;
|
|
7
|
+
export declare function agentInit(enabled: boolean): void;
|
|
8
|
+
export declare function agentFound(config: FinalConfig, files: number): void;
|
|
9
|
+
export declare function agentSetRunReason(reason: RunReason): void;
|
|
10
|
+
export declare function agentRunStart(config: FinalConfig, files: number): number;
|
|
11
|
+
export declare function agentFailure(config: FinalConfig, run: number, entry: FailureEntry): void;
|
|
12
|
+
export declare function agentDone(config: FinalConfig, run: number, code: StatusCode, description: string, summary: TestsResultsSummary | null, durationMs: number, failingFiles: number): void;
|
|
13
|
+
export declare function agentWatching(config: FinalConfig, files: number): void;
|
|
14
|
+
export declare function agentChange(config: FinalConfig, file: string, event: string): void;
|
|
15
|
+
export declare function agentSilentOut(): void;
|
|
16
|
+
//# sourceMappingURL=agentReport.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { out as mainOut } from '../../output.js';
|
|
2
|
+
// Agent mode reports a watch session as a stream of single lines, for a reader tailing the
|
|
3
|
+
// process rather than watching the terminal. Every line is one complete event:
|
|
4
|
+
//
|
|
5
|
+
// [sxyt] <event> | <key>=<value> | <key>=<value>
|
|
6
|
+
//
|
|
7
|
+
// Values are never quoted, so free text is flattened to keep the grammar intact - one event
|
|
8
|
+
// can never span two lines, and a stray `|` can never look like a field boundary.
|
|
9
|
+
const prefix = '[sxyt]';
|
|
10
|
+
let runCounter = 0;
|
|
11
|
+
// requestRun sets this before handing a run to runTests; the initial run never goes through
|
|
12
|
+
// the queue, so 'startup' is the right default
|
|
13
|
+
let nextRunReason = 'startup';
|
|
14
|
+
function emit(event, fields = []) {
|
|
15
|
+
mainOut(prefix + ` ${event}` + fields.map(([key, value]) => ` | ${key}=${value}`).join(''));
|
|
16
|
+
}
|
|
17
|
+
// newlines would split one event across two lines, and a `|` would read as a field boundary
|
|
18
|
+
function flatten(text) {
|
|
19
|
+
return text.replace(/\s*[\r\n]+\s*/g, ' ').replace(/\|/g, '/').trim();
|
|
20
|
+
}
|
|
21
|
+
export function agentEnabled(config) {
|
|
22
|
+
return config.watch.agent;
|
|
23
|
+
}
|
|
24
|
+
// the process is up, before the config is even loaded - so a slow startup is
|
|
25
|
+
// distinguishable from one that never began. Takes the flag rather than the config for that
|
|
26
|
+
// reason, and resets the counters so repeated in process sessions start from 1.
|
|
27
|
+
export function agentInit(enabled) {
|
|
28
|
+
runCounter = 0;
|
|
29
|
+
nextRunReason = 'startup';
|
|
30
|
+
if (!enabled)
|
|
31
|
+
return;
|
|
32
|
+
emit('init', [['pid', process.pid]]);
|
|
33
|
+
}
|
|
34
|
+
export function agentFound(config, files) {
|
|
35
|
+
if (!agentEnabled(config))
|
|
36
|
+
return;
|
|
37
|
+
emit('found', [['files', files]]);
|
|
38
|
+
}
|
|
39
|
+
export function agentSetRunReason(reason) {
|
|
40
|
+
nextRunReason = reason;
|
|
41
|
+
}
|
|
42
|
+
// claims the next run number and announces the run, so a reader knows work is in flight
|
|
43
|
+
// before any result arrives
|
|
44
|
+
export function agentRunStart(config, files) {
|
|
45
|
+
if (!agentEnabled(config))
|
|
46
|
+
return 0;
|
|
47
|
+
runCounter++;
|
|
48
|
+
emit('run', [
|
|
49
|
+
['n', runCounter],
|
|
50
|
+
['files', files],
|
|
51
|
+
['why', nextRunReason],
|
|
52
|
+
['covers', runScope(config)]
|
|
53
|
+
]);
|
|
54
|
+
return runCounter;
|
|
55
|
+
}
|
|
56
|
+
// what the run was built from. A reader needs this to know how far the run's own `code`
|
|
57
|
+
// generalises - only a run covering everything can speak for the whole suite.
|
|
58
|
+
function runScope(config) {
|
|
59
|
+
switch (nextRunReason) {
|
|
60
|
+
case 'startup':
|
|
61
|
+
case 'all': return 'all';
|
|
62
|
+
case 'last': return 'last';
|
|
63
|
+
case 'failed': return 'failing';
|
|
64
|
+
// the reRunFailingTests case is the one worth spelling out: without it a run says
|
|
65
|
+
// nothing about files that were already failing
|
|
66
|
+
case 'auto': return (config.watch.reRunFailingTests) ? 'changed+failing' : 'changed';
|
|
67
|
+
default: return 'changed';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// failures are emitted before the run's done line, so done is a complete end of run marker
|
|
71
|
+
export function agentFailure(config, run, entry) {
|
|
72
|
+
if (!agentEnabled(config))
|
|
73
|
+
return;
|
|
74
|
+
emit('fail', [
|
|
75
|
+
['n', run],
|
|
76
|
+
['file', flatten(entry.file)],
|
|
77
|
+
...(entry.describe === undefined)
|
|
78
|
+
? []
|
|
79
|
+
: [['describe', flatten(entry.describe)]],
|
|
80
|
+
...(entry.test === undefined) ? [] : [['test', flatten(entry.test)]],
|
|
81
|
+
['error', flatten(entry.error)]
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
// `failing` counts the test files still failing across the whole session, not just this
|
|
85
|
+
// run. A watch re-run usually covers only the changed files, so its own tallies can read as
|
|
86
|
+
// a clean pass while the suite is still broken elsewhere.
|
|
87
|
+
export function agentDone(config, run, code, description, summary, durationMs, failingFiles) {
|
|
88
|
+
if (!agentEnabled(config))
|
|
89
|
+
return;
|
|
90
|
+
emit('done', [
|
|
91
|
+
['n', run],
|
|
92
|
+
['code', code],
|
|
93
|
+
['desc', flatten(description)],
|
|
94
|
+
...(summary === null) ? [] : [
|
|
95
|
+
['tests', `${summary.itsPasses}/${summary.itsTotal}`],
|
|
96
|
+
['items', `${summary.describesPasses}/${summary.describesTotal}`],
|
|
97
|
+
['files', `${summary.passes}/${summary.total}`]
|
|
98
|
+
],
|
|
99
|
+
['failing', failingFiles],
|
|
100
|
+
['ms', durationMs]
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
export function agentWatching(config, files) {
|
|
104
|
+
if (!agentEnabled(config))
|
|
105
|
+
return;
|
|
106
|
+
emit('watching', [['files', files]]);
|
|
107
|
+
}
|
|
108
|
+
export function agentChange(config, file, event) {
|
|
109
|
+
if (!agentEnabled(config))
|
|
110
|
+
return;
|
|
111
|
+
emit('change', [['file', flatten(file)], ['event', flatten(event)]]);
|
|
112
|
+
}
|
|
113
|
+
// agent mode owns stdout, so the ordinary coloured output is routed into a sink
|
|
114
|
+
export function agentSilentOut() {
|
|
115
|
+
// deliberately nothing - the agent lines are the whole stream
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=agentReport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentReport.js","sourceRoot":"","sources":["../../../src/cli/lib/agentReport.ts"],"names":[],"mappings":"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","sourcesContent":["import { out as mainOut } from '../../output.js'\n\nimport type { FinalConfig, TestsResultsSummary } from '../../types.js'\nimport type { FailureEntry } from './failureEntries.js'\nimport type { RunType } from './reRunManager.js'\nimport type { StatusCode } from './statusFile.js'\n\n// Agent mode reports a watch session as a stream of single lines, for a reader tailing the\n// process rather than watching the terminal. Every line is one complete event:\n//\n// [sxyt] <event> | <key>=<value> | <key>=<value>\n//\n// Values are never quoted, so free text is flattened to keep the grammar intact - one event\n// can never span two lines, and a stray `|` can never look like a field boundary.\n\nconst prefix = '[sxyt]'\n\nexport type RunReason = RunType | 'startup'\n\nlet runCounter = 0\n// requestRun sets this before handing a run to runTests; the initial run never goes through\n// the queue, so 'startup' is the right default\nlet nextRunReason: RunReason = 'startup'\n\ntype Field = [string, string | number]\n\nfunction emit(event: string, fields: Field[] = []): void {\n mainOut(prefix + ` ${event}` + fields.map(([key, value]) => ` | ${key}=${value}`).join(''))\n}\n\n// newlines would split one event across two lines, and a `|` would read as a field boundary\nfunction flatten(text: string): string {\n return text.replace(/\\s*[\\r\\n]+\\s*/g, ' ').replace(/\\|/g, '/').trim()\n}\n\nexport function agentEnabled(config: FinalConfig): boolean {\n return config.watch.agent\n}\n\n// the process is up, before the config is even loaded - so a slow startup is\n// distinguishable from one that never began. Takes the flag rather than the config for that\n// reason, and resets the counters so repeated in process sessions start from 1.\nexport function agentInit(enabled: boolean): void {\n runCounter = 0\n nextRunReason = 'startup'\n if (!enabled) return\n emit('init', [['pid', process.pid]])\n}\n\nexport function agentFound(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('found', [['files', files]])\n}\n\nexport function agentSetRunReason(reason: RunReason): void {\n nextRunReason = reason\n}\n\n// claims the next run number and announces the run, so a reader knows work is in flight\n// before any result arrives\nexport function agentRunStart(config: FinalConfig, files: number): number {\n if (!agentEnabled(config)) return 0\n runCounter++\n emit('run', [\n ['n', runCounter],\n ['files', files],\n ['why', nextRunReason],\n ['covers', runScope(config)]\n ])\n return runCounter\n}\n\n// what the run was built from. A reader needs this to know how far the run's own `code`\n// generalises - only a run covering everything can speak for the whole suite.\nfunction runScope(config: FinalConfig): string {\n switch (nextRunReason) {\n case 'startup':\n case 'all': return 'all'\n case 'last': return 'last'\n case 'failed': return 'failing'\n // the reRunFailingTests case is the one worth spelling out: without it a run says\n // nothing about files that were already failing\n case 'auto': return (config.watch.reRunFailingTests) ? 'changed+failing' : 'changed'\n default: return 'changed'\n }\n}\n\n// failures are emitted before the run's done line, so done is a complete end of run marker\nexport function agentFailure(config: FinalConfig, run: number, entry: FailureEntry): void {\n if (!agentEnabled(config)) return\n emit('fail', [\n ['n', run],\n ['file', flatten(entry.file)],\n ...(entry.describe === undefined)\n ? []\n : [['describe', flatten(entry.describe)] as Field],\n ...(entry.test === undefined) ? [] : [['test', flatten(entry.test)] as Field],\n ['error', flatten(entry.error)]\n ])\n}\n\n// `failing` counts the test files still failing across the whole session, not just this\n// run. A watch re-run usually covers only the changed files, so its own tallies can read as\n// a clean pass while the suite is still broken elsewhere.\nexport function agentDone(\n config: FinalConfig,\n run: number,\n code: StatusCode,\n description: string,\n summary: TestsResultsSummary | null,\n durationMs: number,\n failingFiles: number\n): void {\n if (!agentEnabled(config)) return\n emit('done', [\n ['n', run],\n ['code', code],\n ['desc', flatten(description)],\n ...(summary === null) ? [] : [\n ['tests', `${summary.itsPasses}/${summary.itsTotal}`] as Field,\n ['items', `${summary.describesPasses}/${summary.describesTotal}`] as Field,\n ['files', `${summary.passes}/${summary.total}`] as Field\n ],\n ['failing', failingFiles],\n ['ms', durationMs]\n ])\n}\n\nexport function agentWatching(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('watching', [['files', files]])\n}\n\nexport function agentChange(config: FinalConfig, file: string, event: string): void {\n if (!agentEnabled(config)) return\n emit('change', [['file', flatten(file)], ['event', flatten(event)]])\n}\n\n// agent mode owns stdout, so the ordinary coloured output is routed into a sink\nexport function agentSilentOut(): void {\n // deliberately nothing - the agent lines are the whole stream\n}\n"]}
|
|
@@ -21,7 +21,9 @@ export const defaultConfig = {
|
|
|
21
21
|
watchFilesIgnore: '**/node_modules/**/*',
|
|
22
22
|
runAllOnStartup: true,
|
|
23
23
|
reRunFailingTests: true,
|
|
24
|
-
ttyActionsOnKeypress: false
|
|
24
|
+
ttyActionsOnKeypress: false,
|
|
25
|
+
statusFile: false,
|
|
26
|
+
agent: false
|
|
25
27
|
}
|
|
26
28
|
};
|
|
27
29
|
//# sourceMappingURL=defaultConfig.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultConfig.js","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAgB;IACtC,SAAS,EAAE,GAAG;IACd,KAAK,EAAE;QACH,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,0BAA0B;QAC1B,8BAA8B;KACjC;IACD,WAAW,EAAE,CAAC,sBAAsB,CAAC;IACrC,cAAc,EAAE,KAAK;IACrB,OAAO,EAAE,KAAK;IACd,SAAS,EAAE;QACP,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,YAAY;KACtB;IACD,KAAK,EAAE;QACH,cAAc,EAAE,GAAG;QACnB,UAAU,EAAE,SAAS;QACrB,gBAAgB,EAAE,sBAAsB;QACxC,eAAe,EAAE,IAAI;QACrB,iBAAiB,EAAE,IAAI;QACvB,oBAAoB,EAAE,KAAK;
|
|
1
|
+
{"version":3,"file":"defaultConfig.js","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAgB;IACtC,SAAS,EAAE,GAAG;IACd,KAAK,EAAE;QACH,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,0BAA0B;QAC1B,8BAA8B;KACjC;IACD,WAAW,EAAE,CAAC,sBAAsB,CAAC;IACrC,cAAc,EAAE,KAAK;IACrB,OAAO,EAAE,KAAK;IACd,SAAS,EAAE;QACP,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,YAAY;KACtB;IACD,KAAK,EAAE;QACH,cAAc,EAAE,GAAG;QACnB,UAAU,EAAE,SAAS;QACrB,gBAAgB,EAAE,sBAAsB;QACxC,eAAe,EAAE,IAAI;QACrB,iBAAiB,EAAE,IAAI;QACvB,oBAAoB,EAAE,KAAK;QAC3B,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,KAAK;KACf;CACJ,CAAA","sourcesContent":["import type { FinalConfig } from '../../types.js'\n\nexport const defaultConfig: FinalConfig = {\n testsBase: '.',\n tests: [\n '**/*.sxyt.{js,mjs,jsx}',\n '**/*.test.{js,mjs,jsx}',\n '**/*.spec.{js,mjs,jsx}',\n '**/tests?/*.{js,mjs,jsx}',\n '**/__tests?__/*.{js,mjs,jsx}'\n ],\n testsIgnore: ['**/node_modules/**/*'],\n showErrorTrace: false,\n compact: false,\n execution: {\n files: 'sequential',\n describes: 'sequential',\n tests: 'sequential'\n },\n watch: {\n watchFilesBase: '.',\n watchFiles: '**/*.js',\n watchFilesIgnore: '**/node_modules/**/*',\n runAllOnStartup: true,\n reRunFailingTests: true,\n ttyActionsOnKeypress: false,\n statusFile: false,\n agent: false\n }\n}\n"]}
|
|
@@ -4,13 +4,17 @@ export async function doUserSetup(config) {
|
|
|
4
4
|
if (config.setup === undefined)
|
|
5
5
|
return;
|
|
6
6
|
const setupTasks = Array.isArray(config.setup) ? config.setup : [config.setup];
|
|
7
|
+
let setupFunctions = 0;
|
|
8
|
+
let setupFiles = 0;
|
|
7
9
|
for (const setupTask of setupTasks) {
|
|
8
10
|
switch (typeof setupTask) {
|
|
9
11
|
case 'function': {
|
|
12
|
+
out('Running setup function ' + String(++setupFunctions));
|
|
10
13
|
await setupTask();
|
|
11
14
|
break;
|
|
12
15
|
}
|
|
13
16
|
case 'string': {
|
|
17
|
+
out('Running setup file ' + String(++setupFiles) + ': ' + setupTask);
|
|
14
18
|
await import(`file://${join(process.cwd(), setupTask)}`);
|
|
15
19
|
break;
|
|
16
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doUserSetup.js","sourceRoot":"","sources":["../../../src/cli/lib/doUserSetup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAGrC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAmB;IACjD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,OAAM;IAEtC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACjC,QAAQ,OAAO,SAAS,EAAE,CAAC;YACvB,KAAK,UAAU,CAAC,CAAC,CAAC;gBACd,MAAM,SAAS,EAAE,CAAA;gBACjB,MAAK;YACT,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACZ,MAAM,MAAM,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE,CAAC,CAAA;gBACxD,MAAK;YACT,CAAC;YACD,OAAO,CAAC,CAAC,CAAC;gBACN,GAAG,CAAC,mGAAmG,CAAC,CAAA;gBACxG,GAAG,CAAC,YAAY,OAAO,SAAS,GAAG,CAAC,CAAA;YACxC,CAAC;QACL,CAAC;IACL,CAAC;AACL,CAAC","sourcesContent":["import { join } from 'path'\nimport { out } from '../../output.js'\nimport type { FinalConfig } from '../../types.js'\n\nexport async function doUserSetup(config: FinalConfig): Promise<void> {\n if (config.setup === undefined) return\n\n const setupTasks = Array.isArray(config.setup) ? config.setup : [config.setup]\n for (const setupTask of setupTasks) {\n switch (typeof setupTask) {\n case 'function': {\n await setupTask()\n break\n }\n case 'string': {\n await import(`file://${join(process.cwd(), setupTask)}`)\n break\n }\n default: {\n out('Error: setup entry has an invalid type, expected \\'function\\' or \\'string\\' of an file to import.')\n out(`Received ${typeof setupTask}.`)\n }\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"doUserSetup.js","sourceRoot":"","sources":["../../../src/cli/lib/doUserSetup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAGrC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAmB;IACjD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,OAAM;IAEtC,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC9E,IAAI,cAAc,GAAG,CAAC,CAAA;IACtB,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACjC,QAAQ,OAAO,SAAS,EAAE,CAAC;YACvB,KAAK,UAAU,CAAC,CAAC,CAAC;gBACd,GAAG,CAAC,yBAAyB,GAAG,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,CAAA;gBACzD,MAAM,SAAS,EAAE,CAAA;gBACjB,MAAK;YACT,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACZ,GAAG,CAAC,qBAAqB,GAAG,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;gBACpE,MAAM,MAAM,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE,CAAC,CAAA;gBACxD,MAAK;YACT,CAAC;YACD,OAAO,CAAC,CAAC,CAAC;gBACN,GAAG,CAAC,mGAAmG,CAAC,CAAA;gBACxG,GAAG,CAAC,YAAY,OAAO,SAAS,GAAG,CAAC,CAAA;YACxC,CAAC;QACL,CAAC;IACL,CAAC;AACL,CAAC","sourcesContent":["import { join } from 'path'\nimport { out } from '../../output.js'\nimport type { FinalConfig } from '../../types.js'\n\nexport async function doUserSetup(config: FinalConfig): Promise<void> {\n if (config.setup === undefined) return\n\n const setupTasks = Array.isArray(config.setup) ? config.setup : [config.setup]\n let setupFunctions = 0\n let setupFiles = 0\n for (const setupTask of setupTasks) {\n switch (typeof setupTask) {\n case 'function': {\n out('Running setup function ' + String(++setupFunctions))\n await setupTask()\n break\n }\n case 'string': {\n out('Running setup file ' + String(++setupFiles) + ': ' + setupTask)\n await import(`file://${join(process.cwd(), setupTask)}`)\n break\n }\n default: {\n out('Error: setup entry has an invalid type, expected \\'function\\' or \\'string\\' of an file to import.')\n out(`Received ${typeof setupTask}.`)\n }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TestFile, TestResult } from '../../types.js';
|
|
2
|
+
export interface FailureEntry {
|
|
3
|
+
file: string;
|
|
4
|
+
describe?: string;
|
|
5
|
+
test?: string;
|
|
6
|
+
error: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function failureEntries(testResults: TestResult[], erroringTests?: Record<string, TestFile>): FailureEntry[];
|
|
9
|
+
//# sourceMappingURL=failureEntries.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function failureEntries(testResults, erroringTests = {}) {
|
|
2
|
+
const entries = [];
|
|
3
|
+
for (const [file, test] of Object.entries(erroringTests)) {
|
|
4
|
+
entries.push({ file, error: `did not load: ${simplifyError(test.error)}` });
|
|
5
|
+
}
|
|
6
|
+
for (const testResult of testResults) {
|
|
7
|
+
for (const describeResult of testResult.describeResults) {
|
|
8
|
+
if (describeResult.error !== undefined) {
|
|
9
|
+
entries.push({
|
|
10
|
+
file: testResult.file,
|
|
11
|
+
describe: describeResult.thing,
|
|
12
|
+
error: `threw: ${simplifyError(describeResult.error)}`
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
for (const itResult of describeResult.itResults) {
|
|
16
|
+
if (itResult.success !== false)
|
|
17
|
+
continue;
|
|
18
|
+
entries.push({
|
|
19
|
+
file: testResult.file,
|
|
20
|
+
describe: describeResult.thing,
|
|
21
|
+
test: itResult.should,
|
|
22
|
+
error: simplifyError(itResult.error)
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return entries;
|
|
28
|
+
}
|
|
29
|
+
function simplifyError(error) {
|
|
30
|
+
if (error === undefined || error === null || error === false)
|
|
31
|
+
return 'unknown error';
|
|
32
|
+
if (typeof error === 'object' && 'message' in error && typeof error.message === 'string') {
|
|
33
|
+
const name = ('name' in error && typeof error.name === 'string') ? error.name : 'Error';
|
|
34
|
+
return `${name}: ${firstLine(error.message)}`;
|
|
35
|
+
}
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- only a fallback
|
|
37
|
+
return firstLine(String(error));
|
|
38
|
+
}
|
|
39
|
+
function firstLine(text) {
|
|
40
|
+
return text.split('\n')[0]?.trim() ?? '';
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=failureEntries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failureEntries.js","sourceRoot":"","sources":["../../../src/cli/lib/failureEntries.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,cAAc,CAC1B,WAAyB,EACzB,gBAA0C,EAAE;IAE5C,MAAM,OAAO,GAAmB,EAAE,CAAA;IAElC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;IAC/E,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,KAAK,MAAM,cAAc,IAAI,UAAU,CAAC,eAAe,EAAE,CAAC;YACtD,IAAI,cAAc,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACrC,OAAO,CAAC,IAAI,CAAC;oBACT,IAAI,EAAE,UAAU,CAAC,IAAI;oBACrB,QAAQ,EAAE,cAAc,CAAC,KAAK;oBAC9B,KAAK,EAAE,UAAU,aAAa,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE;iBACzD,CAAC,CAAA;YACN,CAAC;YAED,KAAK,MAAM,QAAQ,IAAI,cAAc,CAAC,SAAS,EAAE,CAAC;gBAC9C,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK;oBAAE,SAAQ;gBACxC,OAAO,CAAC,IAAI,CAAC;oBACT,IAAI,EAAE,UAAU,CAAC,IAAI;oBACrB,QAAQ,EAAE,cAAc,CAAC,KAAK;oBAC9B,IAAI,EAAE,QAAQ,CAAC,MAAM;oBACrB,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;iBACvC,CAAC,CAAA;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAA;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACjC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,eAAe,CAAA;IAEpF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,SAAS,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvF,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAA;QACvF,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAA;IACjD,CAAC;IAED,mFAAmF;IACnF,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AAC5C,CAAC","sourcesContent":["import type { TestFile, TestResult } from '../../types.js'\n\n// One failure, flattened. `describe` is absent when a file would not load, and `test` is\n// absent when the describe itself threw. Shared by the status file's nested tree and the\n// agent reporter's flat lines, so both describe a failure the same way.\nexport interface FailureEntry {\n file: string\n describe?: string\n test?: string\n error: string\n}\n\nexport function failureEntries(\n testResults: TestResult[],\n erroringTests: Record<string, TestFile> = {}\n): FailureEntry[] {\n const entries: FailureEntry[] = []\n\n for (const [file, test] of Object.entries(erroringTests)) {\n entries.push({ file, error: `did not load: ${simplifyError(test.error)}` })\n }\n\n for (const testResult of testResults) {\n for (const describeResult of testResult.describeResults) {\n if (describeResult.error !== undefined) {\n entries.push({\n file: testResult.file,\n describe: describeResult.thing,\n error: `threw: ${simplifyError(describeResult.error)}`\n })\n }\n\n for (const itResult of describeResult.itResults) {\n if (itResult.success !== false) continue\n entries.push({\n file: testResult.file,\n describe: describeResult.thing,\n test: itResult.should,\n error: simplifyError(itResult.error)\n })\n }\n }\n }\n\n return entries\n}\n\nfunction simplifyError(error: unknown): string {\n if (error === undefined || error === null || error === false) return 'unknown error'\n\n if (typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n const name = ('name' in error && typeof error.name === 'string') ? error.name : 'Error'\n return `${name}: ${firstLine(error.message)}`\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- only a fallback\n return firstLine(String(error))\n}\n\nfunction firstLine(text: string): string {\n return text.split('\\n')[0]?.trim() ?? ''\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { failureEntries } from './failureEntries.js';
|
|
2
|
+
// The failures for the status file, as f: file > d: describe > t: test > error. Errors are
|
|
3
|
+
// reduced to a single line - anything wanting the diff, the logs or the trace reads the
|
|
4
|
+
// terminal.
|
|
5
|
+
export function failureLines(testResults, erroringTests = {}) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
let currentFile;
|
|
8
|
+
let currentDescribe;
|
|
9
|
+
// entries arrive in tree order, so a heading is needed only when its value changes
|
|
10
|
+
for (const entry of failureEntries(testResults, erroringTests)) {
|
|
11
|
+
if (entry.file !== currentFile) {
|
|
12
|
+
lines.push(` f: ${entry.file}`);
|
|
13
|
+
currentFile = entry.file;
|
|
14
|
+
currentDescribe = undefined;
|
|
15
|
+
}
|
|
16
|
+
if (entry.describe === undefined) {
|
|
17
|
+
lines.push(` e: ${entry.error}`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (entry.describe !== currentDescribe) {
|
|
21
|
+
lines.push(` d: ${entry.describe}`);
|
|
22
|
+
currentDescribe = entry.describe;
|
|
23
|
+
}
|
|
24
|
+
if (entry.test === undefined) {
|
|
25
|
+
lines.push(` e: ${entry.error}`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
lines.push(` t: ${entry.test}`, ` e: ${entry.error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return (lines.length > 0) ? ['Failures:', ...lines] : [];
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=failureLines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failureLines.js","sourceRoot":"","sources":["../../../src/cli/lib/failureLines.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD,2FAA2F;AAC3F,wFAAwF;AACxF,YAAY;AACZ,MAAM,UAAU,YAAY,CACxB,WAAyB,EACzB,gBAA0C,EAAE;IAE5C,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,WAA+B,CAAA;IACnC,IAAI,eAAmC,CAAA;IAEvC,mFAAmF;IACnF,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,CAAC;QAC7D,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;YAChC,WAAW,GAAG,KAAK,CAAC,IAAI,CAAA;YACxB,eAAe,GAAG,SAAS,CAAA;QAC/B,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;YACnC,SAAQ;QACZ,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;YACtC,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAA;QACpC,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QACzC,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,IAAI,EAAE,EAAE,cAAc,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QACrE,CAAC;IACL,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC5D,CAAC","sourcesContent":["import type { TestFile, TestResult } from '../../types.js'\n\nimport { failureEntries } from './failureEntries.js'\n\n// The failures for the status file, as f: file > d: describe > t: test > error. Errors are\n// reduced to a single line - anything wanting the diff, the logs or the trace reads the\n// terminal.\nexport function failureLines(\n testResults: TestResult[],\n erroringTests: Record<string, TestFile> = {}\n): string[] {\n const lines: string[] = []\n let currentFile: string | undefined\n let currentDescribe: string | undefined\n\n // entries arrive in tree order, so a heading is needed only when its value changes\n for (const entry of failureEntries(testResults, erroringTests)) {\n if (entry.file !== currentFile) {\n lines.push(` f: ${entry.file}`)\n currentFile = entry.file\n currentDescribe = undefined\n }\n\n if (entry.describe === undefined) {\n lines.push(` e: ${entry.error}`)\n continue\n }\n\n if (entry.describe !== currentDescribe) {\n lines.push(` d: ${entry.describe}`)\n currentDescribe = entry.describe\n }\n\n if (entry.test === undefined) {\n lines.push(` e: ${entry.error}`)\n } else {\n lines.push(` t: ${entry.test}`, ` e: ${entry.error}`)\n }\n }\n\n return (lines.length > 0) ? ['Failures:', ...lines] : []\n}\n"]}
|