@c9up/helix 0.1.7 → 0.1.9

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 (124) hide show
  1. package/README.md +282 -11
  2. package/bin/helix.js +329 -13
  3. package/dist/cli/failed-cache.d.ts +27 -0
  4. package/dist/cli/failed-cache.d.ts.map +1 -0
  5. package/dist/cli/failed-cache.js +64 -0
  6. package/dist/cli/failed-cache.js.map +1 -0
  7. package/dist/cli/glob.d.ts +36 -0
  8. package/dist/cli/glob.d.ts.map +1 -0
  9. package/dist/cli/glob.js +185 -0
  10. package/dist/cli/glob.js.map +1 -0
  11. package/dist/cli/native.d.ts +4 -0
  12. package/dist/cli/native.d.ts.map +1 -1
  13. package/dist/cli/native.js.map +1 -1
  14. package/dist/cli/pool.d.ts +11 -0
  15. package/dist/cli/pool.d.ts.map +1 -1
  16. package/dist/cli/pool.js +12 -0
  17. package/dist/cli/pool.js.map +1 -1
  18. package/dist/cli/reporter.d.ts +11 -0
  19. package/dist/cli/reporter.d.ts.map +1 -1
  20. package/dist/cli/reporter.js +53 -0
  21. package/dist/cli/reporter.js.map +1 -1
  22. package/dist/cli/run.d.ts +41 -0
  23. package/dist/cli/run.d.ts.map +1 -1
  24. package/dist/cli/run.js +181 -21
  25. package/dist/cli/run.js.map +1 -1
  26. package/dist/cli/runner.d.ts +19 -0
  27. package/dist/cli/runner.d.ts.map +1 -0
  28. package/dist/cli/runner.js +15 -0
  29. package/dist/cli/runner.js.map +1 -0
  30. package/dist/cli/suites.d.ts +102 -0
  31. package/dist/cli/suites.d.ts.map +1 -0
  32. package/dist/cli/suites.js +226 -0
  33. package/dist/cli/suites.js.map +1 -0
  34. package/dist/index.d.ts +10 -9
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +9 -8
  37. package/dist/index.js.map +1 -1
  38. package/dist/japa/core.d.ts +106 -0
  39. package/dist/japa/core.d.ts.map +1 -0
  40. package/dist/japa/core.js +128 -0
  41. package/dist/japa/core.js.map +1 -0
  42. package/dist/runtime/assert.d.ts +174 -0
  43. package/dist/runtime/assert.d.ts.map +1 -0
  44. package/dist/runtime/assert.js +591 -0
  45. package/dist/runtime/assert.js.map +1 -0
  46. package/dist/runtime/asymmetric.d.ts +34 -0
  47. package/dist/runtime/asymmetric.d.ts.map +1 -0
  48. package/dist/runtime/asymmetric.js +78 -0
  49. package/dist/runtime/asymmetric.js.map +1 -0
  50. package/dist/runtime/bootstrap.d.ts +47 -0
  51. package/dist/runtime/bootstrap.d.ts.map +1 -0
  52. package/dist/runtime/bootstrap.js +166 -0
  53. package/dist/runtime/bootstrap.js.map +1 -0
  54. package/dist/runtime/cli-args.d.ts +77 -0
  55. package/dist/runtime/cli-args.d.ts.map +1 -0
  56. package/dist/runtime/cli-args.js +85 -0
  57. package/dist/runtime/cli-args.js.map +1 -0
  58. package/dist/runtime/configure.d.ts +214 -0
  59. package/dist/runtime/configure.d.ts.map +1 -0
  60. package/dist/runtime/configure.js +164 -0
  61. package/dist/runtime/configure.js.map +1 -0
  62. package/dist/runtime/context.d.ts +84 -0
  63. package/dist/runtime/context.d.ts.map +1 -0
  64. package/dist/runtime/context.js +146 -0
  65. package/dist/runtime/context.js.map +1 -0
  66. package/dist/runtime/emitter.d.ts +141 -0
  67. package/dist/runtime/emitter.d.ts.map +1 -0
  68. package/dist/runtime/emitter.js +79 -0
  69. package/dist/runtime/emitter.js.map +1 -0
  70. package/dist/runtime/equals.d.ts.map +1 -1
  71. package/dist/runtime/equals.js +21 -0
  72. package/dist/runtime/equals.js.map +1 -1
  73. package/dist/runtime/expect.d.ts +22 -1
  74. package/dist/runtime/expect.d.ts.map +1 -1
  75. package/dist/runtime/expect.js +15 -1
  76. package/dist/runtime/expect.js.map +1 -1
  77. package/dist/runtime/global-hooks.d.ts +31 -0
  78. package/dist/runtime/global-hooks.d.ts.map +1 -0
  79. package/dist/runtime/global-hooks.js +126 -0
  80. package/dist/runtime/global-hooks.js.map +1 -0
  81. package/dist/runtime/index.d.ts +16 -3
  82. package/dist/runtime/index.d.ts.map +1 -1
  83. package/dist/runtime/index.js +7 -1
  84. package/dist/runtime/index.js.map +1 -1
  85. package/dist/runtime/matchers.d.ts +5 -0
  86. package/dist/runtime/matchers.d.ts.map +1 -1
  87. package/dist/runtime/matchers.js +117 -0
  88. package/dist/runtime/matchers.js.map +1 -1
  89. package/dist/runtime/run.d.ts +63 -1
  90. package/dist/runtime/run.d.ts.map +1 -1
  91. package/dist/runtime/run.js +696 -79
  92. package/dist/runtime/run.js.map +1 -1
  93. package/dist/runtime/runner.d.ts +97 -0
  94. package/dist/runtime/runner.d.ts.map +1 -0
  95. package/dist/runtime/runner.js +160 -0
  96. package/dist/runtime/runner.js.map +1 -0
  97. package/dist/runtime/suite-config.d.ts +23 -0
  98. package/dist/runtime/suite-config.d.ts.map +1 -0
  99. package/dist/runtime/suite-config.js +52 -0
  100. package/dist/runtime/suite-config.js.map +1 -0
  101. package/dist/runtime/suite-taps.d.ts +122 -0
  102. package/dist/runtime/suite-taps.d.ts.map +1 -0
  103. package/dist/runtime/suite-taps.js +218 -0
  104. package/dist/runtime/suite-taps.js.map +1 -0
  105. package/dist/runtime/suite.d.ts +316 -10
  106. package/dist/runtime/suite.d.ts.map +1 -1
  107. package/dist/runtime/suite.js +307 -8
  108. package/dist/runtime/suite.js.map +1 -1
  109. package/dist/runtime/test-context.d.ts +88 -1
  110. package/dist/runtime/test-context.d.ts.map +1 -1
  111. package/dist/runtime/test-context.js +170 -2
  112. package/dist/runtime/test-context.js.map +1 -1
  113. package/dist/runtime/vi/fake-timers.d.ts.map +1 -1
  114. package/dist/runtime/vi/fake-timers.js +3 -4
  115. package/dist/runtime/vi/fake-timers.js.map +1 -1
  116. package/dist/runtime/worker.d.ts.map +1 -1
  117. package/dist/runtime/worker.js +87 -5
  118. package/dist/runtime/worker.js.map +1 -1
  119. package/index.darwin-arm64.node +0 -0
  120. package/index.darwin-x64.node +0 -0
  121. package/index.linux-arm64-gnu.node +0 -0
  122. package/index.linux-x64-gnu.node +0 -0
  123. package/index.win32-x64-msvc.node +0 -0
  124. package/package.json +10 -1
package/README.md CHANGED
@@ -1,25 +1,257 @@
1
1
  # `@c9up/helix`
2
2
 
3
- Unified testing toolkit for the Ream ecosystem — bus, HTTP, and DB
4
- fakes, fluent assertions, container overrides, time-travel, plus a
5
- Vitest-compatible test runner CLI.
3
+ Framework-agnostic testing toolkit for the Ream ecosystem — a
4
+ Vitest-compatible test runner CLI with fluent assertions, container
5
+ overrides, and time-travel. Host-specific fakes (bus / HTTP / DB) live in
6
+ their own integration packages, not here — helix stays dependency-light.
6
7
 
7
8
  ## Sub-barrels
8
9
 
9
- - `@c9up/helix/bus` — Event bus assertions (emitted events, ack chains)
10
- - `@c9up/helix/http` — fluent HTTP TestClient with assertions + auth
11
- - `@c9up/helix/db` — factory, useTransaction, truncateAll, in-memory SQLite
10
+ - `@c9up/helix` — assertions + `expect`, the test entry point
12
11
  - `@c9up/helix/runtime` — Vitest-compatible DSL (`test`, `describe`, `expect`, `vi`, lifecycle hooks)
12
+ - `@c9up/helix/runtime/worker` — worker entry for the parallel runner
13
+ - `@c9up/helix/container` — `useContainer` / `override` / `overrideOn` / `spy`
14
+ - `@c9up/helix/time` — time-travel helpers
13
15
 
14
16
  ## CLI
15
17
 
16
18
  ```sh
17
- helix test [paths...] # run a one-shot suite (e.g. `helix test app`)
19
+ helix test [paths...|suites...] # run paths, or suites named in helix.config
18
20
  helix test --watch # re-run on file changes
19
21
  helix test --coverage # V8 coverage + LCOV + thresholds
20
22
  helix test --diff-cov # diff coverage vs main branch
23
+ helix test --bail # stop at the first failure
24
+ helix test --failed # re-run what failed last time
25
+ helix test --list-pinned # print the .pin()ed tests, run nothing
21
26
  ```
22
27
 
28
+ ## Suites
29
+
30
+ Declare named suites and `helix test unit` runs one, the AdonisJS way.
31
+ With no positional, every suite runs, in order:
32
+
33
+ ```ts
34
+ // helix.config.ts
35
+ export default {
36
+ timeout: 2_000, // AdonisJS `tests.timeout`
37
+ forceExit: false, // AdonisJS `tests.forceExit`
38
+ suites: [
39
+ { name: "unit", files: ["tests/unit/**/*.spec.(js|ts)"] },
40
+ { name: "functional", files: ["tests/functional/**/*.spec.ts"], timeout: 30_000 },
41
+ ],
42
+ }
43
+ ```
44
+
45
+ A suite's name reaches the tests as `ctx.test.options.meta.suite.name`
46
+ and rides on the `suite:*` events — `meta.suite` is the suite object, as
47
+ in Japa, not the bare string. Without a config file — or when a
48
+ positional is not a suite name — positionals stay paths, exactly as
49
+ before.
50
+
51
+ A suite may carry `configure(suite)` — Japa's `TestSuite.configure` — which
52
+ gets the same handle as the bootstrap's `configureSuite` and runs after it.
53
+ It costs an import of the config module in EVERY worker, because a function
54
+ cannot cross the process boundary: `configureSuite` in `tests/bootstrap.ts`
55
+ does the same job for free, and is what AdonisJS itself uses. Nothing is
56
+ imported when no suite declares one.
57
+
58
+ Suites run one after another, and the sequence is what the run-wide
59
+ flags act on: `--watch` re-runs every selected suite on each change (one
60
+ watcher for the whole sequence), `--bail` stops at the suite that failed,
61
+ and the `--failed` cache holds every suite's failures.
62
+
63
+ `files` takes Japa's three forms — one pattern, an array of them, or a
64
+ callback returning the URLs (`files: () => [pathToFileURL(…)]`), which
65
+ runs in the CLI process and can therefore reach files no suffix-based
66
+ discovery would find.
67
+
68
+ Pattern entries are plain paths (a directory is walked with helix's
69
+ suffix discovery) or globs — `*`, `**`, `?`, `{a,b}`, `(a|b)`, `[abc]`
70
+ and the extglob quantifiers `@(a|b)`, `?(a|b)`, `*(a|b)`, `+(a|b)`,
71
+ which covers AdonisJS's own defaults verbatim
72
+ (`tests/unit/**/*.spec.(js|ts)`). An entry starting with `!` subtracts
73
+ from what the others selected (`"!tests/unit/slow/**"`).
74
+
75
+ One form is refused rather than approximated: the negated extglob GROUP
76
+ `!(a|b)`. Its semantics have no faithful regex rendering, and a
77
+ near-miss would silently select the wrong files — the one thing a file
78
+ selector must not do. Write a `!pattern` entry instead.
79
+
80
+ Filters follow Japa: `--tests` and `--groups` take exact titles,
81
+ `--files` matches path segments (`--files=user`, `--files=unit/*`),
82
+ `--tags` matches ANY of the given tags (`--match-all`, spelled
83
+ `--matchAll` too, requires every one), and a `~@tag` / `!@tag` entry
84
+ excludes. `--suite=<name>` names the suite the files belong to
85
+ (`"default"` otherwise, like Japa's implicit suite). `--grep` is a helix
86
+ extra: a regex or substring over the full test name.
87
+
88
+ `--bail` stops at the first failure; `--bail-layer=group|suite|runner`
89
+ says how far that reaches. Within a file the remaining tests are
90
+ reported as SKIPPED, like Japa. Files not yet started are dropped
91
+ rather than skipped — a named deviation that follows from per-file
92
+ process isolation: reporting them as skipped would mean spawning every
93
+ remaining file just to collect names.
94
+
95
+ `--list-pinned` collects the files, prints the tests marked `.pin()` and
96
+ runs nothing — the global `setup` hooks are skipped too, as in Japa,
97
+ since nothing they would open gets used. It stays on the TypeScript
98
+ pool: it prints a list rather than executing, so the native engine's
99
+ fast path buys nothing.
100
+
101
+ `--failed` re-runs only what failed last time, from the cache each run
102
+ writes to `node_modules/.cache/helix/summary.json` (same `{ tests }`
103
+ shape as Japa). `--reporters=spec,json` activates several reporters at
104
+ once. `--force-exit` (or `forceExit` in the config) calls
105
+ `process.exit()` as soon as the run ends; without it the process exits
106
+ on its own once the event loop drains, as in Japa — so a resource a test
107
+ left open surfaces as a diagnosable hang instead of being swallowed.
108
+
109
+ All of these work on BOTH orchestrators: the native (Rust) engine
110
+ serializes the full per-test summary and implements bail and the
111
+ reporter chain itself. Only coverage, diff coverage and a pluggable
112
+ reporter instance keep a run on the TypeScript pool.
113
+
114
+ ## Bootstrap
115
+
116
+ `tests/bootstrap.ts` is the AdonisJS bootstrap module, with the same three
117
+ exports — an Adonis one ports over unchanged:
118
+
119
+ ```ts
120
+ // tests/bootstrap.ts
121
+ export const plugins = [apiClient({ baseUrl })]
122
+ export const runnerHooks = {
123
+ setup: [(runner) => migrate()], // may resolve to its own undo
124
+ teardown: [() => closePool()],
125
+ }
126
+ export const configureSuite = (suite) => {
127
+ if (["functional", "e2e"].includes(suite.name)) {
128
+ return suite.setup(() => httpServer.start())
129
+ }
130
+ }
131
+ ```
132
+
133
+ It is picked up automatically (`helix.config`'s `bootstrap` overrides the
134
+ path) and imported by each worker before its test file, so a plugin's
135
+ context macros exist by the time the first test declares itself.
136
+
137
+ A `setup` hook receives the `runner` and may RESOLVE TO ITS OWN UNDO —
138
+ the AdonisJS idiom (`setup: [() => testUtils.db().migrate()]`, where
139
+ `migrate()` resolves to the rollback). Returned undos unwind first, then
140
+ the declared teardowns, both in reverse order.
141
+
142
+ `configureSuite` receives Japa's `Suite` surface: `name`, `setup`,
143
+ `teardown`, `bail`, and the `onTest` / `onGroup` taps — each mapped onto
144
+ the node the runtime actually reads, so `suite.onTest(t => t.timeout(30_000))`
145
+ really does change the timeout. What a callback cannot get is what only
146
+ the owner of execution has (`add`, `stack`, `exec`, `failed`): helix
147
+ builds the tree from the file's own `describe`/`test` and runs it itself.
148
+
149
+ Two more Japa `Config` fields live here too, since helix has no
150
+ `bin/test.ts` to put them in: `filters` (`{ tests, groups, tags, matchAll }`
151
+ — the CLI flags still win over them) and `importer`, the hook that replaces
152
+ the plain dynamic import of a test file. Japa's `filters.files` /
153
+ `filters.suites` stay CLI-side (`--files`, a suite positional): helix
154
+ settles the file list before any worker — and so any bootstrap — exists,
155
+ and filtering there still avoids the spawn.
156
+
157
+ `runnerHooks` run ONCE for the whole run, in the process that spawns the
158
+ workers — Japa's semantics. A migration in `setup` migrates once, not once
159
+ per test file. Everything else in the bootstrap is per worker because it has
160
+ to be: a context macro, a filter, an importer only mean anything in the
161
+ process that loads the test file, which is why `plugins` are where an
162
+ in-memory resource belongs.
163
+
164
+ ## Official Japa plugins
165
+
166
+ `japaPlugins: true` in `helix.config` points `@japa/runner/core` at a helix
167
+ shim in every worker, so a plugin written for Japa instruments helix:
168
+
169
+ ```ts
170
+ // helix.config.ts
171
+ export default { japaPlugins: true }
172
+
173
+ // tests/bootstrap.ts
174
+ import { assert } from "@japa/assert"
175
+ export const plugins = [assert()]
176
+ ```
177
+
178
+ This is what "not drop-in" meant for the whole of this package's life, and
179
+ it was never an API-shape problem: a plugin does not talk to the runner
180
+ through an interface, it imports `Test` / `TestContext` and mutates them.
181
+ Nothing helix does at runtime can change what that import already resolved
182
+ to — module resolution can.
183
+
184
+ Off by default: redirecting a package specifier is not something to do
185
+ behind a user's back, and a project with no Japa plugin gains nothing. The
186
+ shim exports every name the real module does — a missing one is an
187
+ ImportError before any test runs, not a degraded experience. `Emitter` is
188
+ helix's own and `Refiner` collects for real; `BaseReporter`, `Group`,
189
+ `Suite` and `Runner` exist so imports resolve and `instanceof` answers
190
+ `false` (a helix group is not a Japa `Group`), and throw on construction
191
+ with the reason. `Test.isHelixShim` answers "which module did this import
192
+ resolve to?", a question that otherwise costs an afternoon.
193
+
194
+ The alias goes into the PARENT too, not just the workers: the parent
195
+ imports the bootstrap for `runnerHooks`, and two processes resolving that
196
+ specifier differently is how a top-level registration lands on a class
197
+ nothing reads. It is switched off again when the run ends —
198
+ `node:module.register()` has no counterpart, so a host running twice in one
199
+ process would otherwise keep resolving to the shim after asking for the
200
+ real module. ESM only — under a CJS build the import is a `require`, which an ESM
201
+ resolve hook never sees.
202
+
203
+ A `Test.executed` hook is a VERDICT: what it throws fails the test, which is
204
+ how `@japa/assert` enforces `assert.plan(n)`. And a plugin may replace
205
+ `assert` — helix ships one, but a project installing `@japa/assert` is
206
+ asking for that one. `cleanup` and `test` stay helix's: the runtime hands
207
+ them to the body, and a test with someone else's `cleanup` is not a test.
208
+
209
+ ## Plugins
210
+
211
+ A plugin is a function run once at `configure()` time, handed the same
212
+ object Japa hands its plugins — plus two helix extras:
213
+
214
+ ```ts
215
+ await configure({
216
+ plugins: [
217
+ ({ config, cliArgs, runner, emitter, context, cleanup }) => {
218
+ context.macro("greeting", "hello") // extend the test context
219
+ emitter.on("test:end", (t) => { … }) // observe the run
220
+ cleanup(async () => server.close()) // close resources afterwards
221
+ },
222
+ ],
223
+ })
224
+ ```
225
+
226
+ - `config` — Japa's `BaseConfig`, filled in with what this run actually is:
227
+ `cwd`, `timeout`, `retries`, `filters`, `configureSuite`, `reporters`,
228
+ `plugins`, `importer`, `refiner`, `forceExit`, `setup`, `teardown`. What a
229
+ plugin can STEER is marked as such below; `filters.files` / `filters.suites`
230
+ and `reporters.activated` report what the CLI decided, because the file list
231
+ and the reporter chain are settled before a worker exists
232
+ - `cliArgs` — every flag the CLI forwarded to this worker (Japa's set:
233
+ `tags`, `tests`, `groups`, `files`, `matchAll`, `timeout`, `retries`,
234
+ `reporters`, `bail`, `bailLayer`, `failed`, `forceExit`, `suite`)
235
+ - `runner` — `getSummary()`, `failed`, `bail()`, `onSuite()`, `suites`,
236
+ and `registerReporter()`, which hands a Japa reporter this worker's
237
+ runner and emitter. It observes THIS FILE; run-wide output is the CLI's
238
+ (`--reporters`, `run({ reporterInstance })`). `add` / `start` / `exec` /
239
+ `end` throw a `RunnerNotDrivableError` explaining that the CLI owns
240
+ discovery and execution — a sentence rather than the missing-property
241
+ crash they would otherwise be
242
+ - `emitter` — `runner:start` / `suite:*` / `group:*` / `test:*`, with
243
+ `errors[].error` the thrown `Error` itself
244
+ - `context` — `macro` / `getter` (also on the `TestContext` class, as
245
+ in Japa)
246
+ - `cleanup` — a teardown run once the file's tests finish
247
+
248
+ `config` and `cliArgs` are handed over MUTABLE and read back once every
249
+ plugin has run, so a plugin can raise `config.timeout`, push a `setup`
250
+ hook, narrow `cliArgs.tags`, call `config.refiner.add("tags", […])` or
251
+ replace `config.configureSuite` and have the run follow — Japa's contract.
252
+ Plugins therefore run BEFORE both the run's `setup` hooks and
253
+ `configureSuite`, as in Japa.
254
+
23
255
  In `package.json`, call the `helix` bin directly — in npm scripts it resolves to
24
256
  `node_modules/.bin/helix` and bootstraps the TS loader itself, so the verbose
25
257
  `node --import tsx node_modules/@c9up/helix/bin/helix.js …` form is unnecessary:
@@ -48,7 +280,47 @@ Both commands run independently in CI. Stage 2b will retire vitest
48
280
  once the helix self-test corpus reaches parity coverage with the
49
281
  vitest suite.
50
282
 
51
- ### Parity proofs
283
+ ### Japa parity proofs (golden tests)
284
+
285
+ `tests/golden/` runs helix against the **real `@japa/runner`**. Every
286
+ spec under `specs/helix/` has a byte-identical twin under
287
+ `specs/japa/` — only the runner import differs. Each pair is executed
288
+ by its own runner; both harnesses write the same event
289
+ journal (`runner:start`, `group:start`, `test:start`, `test:end`, …)
290
+ and the journals must match event for event:
291
+
292
+ | Spec | What it pins down |
293
+ | --- | --- |
294
+ | `lifecycle` | group `setup`/`teardown`/`each.*` order |
295
+ | `outcomes` | pass / fail / `.skip()` / todo / tags, as reported |
296
+ | `dataset` | `.with()` expansion and `{prop}` / `{$i}` titles |
297
+ | `retries` | one start/end pair per test, 1-based `retryAttempt` |
298
+ | `macros` | `test.macro(callback)` + `t.cleanup` |
299
+ | `group_identity` | `test.group()` returns the instance its hooks get |
300
+ | `filters` | `--tags` (OR), `--match-all`, `~@tag`, `--tests`, `--groups` |
301
+
302
+ Each journal entry also carries the payload's RAW key set, so the
303
+ comparison is not "the two runners agree on the fields we chose to look
304
+ at" but "they hand a reporter the same object". That is what pinned
305
+ `isTodo`/`retries` to always-present, `isSkipped`/`isFailing`/
306
+ `skipReason` to only-when-set, and the bail skip reason to Japa's own
307
+ wording.
308
+
309
+ The filter matrix runs the same flags through both runners, including
310
+ the rules that a group — or a whole suite — with no runnable test
311
+ announces nothing.
312
+
313
+ `tests/golden/assert-surface.test.ts` does the same for assertions: it
314
+ asserts helix exposes every public assertion of the installed
315
+ `@japa/assert`, then runs a battery of inputs through BOTH
316
+ implementations and requires the same verdict (this is what pinned
317
+ `sameMembers` to strict equality and `sameDeepMembers` to structural).
318
+
319
+ Since helix runs one process per FILE, `suite:*` fires once per file
320
+ rather than once for a multi-file suite. That is the only deviation the
321
+ golden journals still carry.
322
+
323
+ ### Vitest parity proofs
52
324
 
53
325
  A handful of identical test bodies live in BOTH directories
54
326
  (`tests/selftest/parity-*.test.ts` and
@@ -62,10 +334,9 @@ currently cover:
62
334
  - assertion failure shapes (`AssertionError` thrown, message contains
63
335
  both received and expected values)
64
336
 
65
- What the parity proofs do **NOT** cover today:
337
+ What the VITEST parity proofs do **NOT** cover today (the Japa golden
338
+ tests above cover the runner semantics):
66
339
 
67
- - Lifecycle hook semantics across runners (`beforeEach`/`afterEach`
68
- behaviour is exercised in `tests/selftest/lifecycle.test.ts` only).
69
340
  - Spy / fake-timer parity (`vi.fn`, `vi.spyOn`, `vi.useFakeTimers`).
70
341
  - Failure-pipeline parity (i.e. that both runners REPORT a failed
71
342
  test the same way, at the runner level). The current parity-fail