@systemfsoftware/vitest 0.0.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @systemfsoftware/vitest
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Tests take `expect` from the test callback and yield each check: `it(name, function* ({ expect }) { yield* expect(actual).toEqual(expected) })`. The package stops exporting `expect`, `assert` and the `utils` helpers. With its `guard` entry listed in `setupFiles`, an `expect` imported from Vitest, or a test registered through Vitest's own `it`, is refused. Also refused, each with its rewrite as the message: a second check on one observed state, an unyielded check, a body with no check, a non-generator body, `it.effect`/`it.scoped`/`it.scopedLive`, `beforeEach`/`afterEach`, and weak matchers such as `toHaveLength`, `toBeDefined`, snapshots, `expect.anything`, `expect.soft`/`expect.poll` and argument-less `toThrow`. Libraries that judge inside a test import `step`, `captureRunBinding` and the `Check`/`Expect`/`Asserted` types from the `integration` entry.
8
+
9
+ - `@systemfsoftware/differential-spec` depends on and imports `@systemfsoftware/vitest` by its own name instead of through an `@effect/vitest` alias. `@systemfsoftware/oxlint-config-recommended` enables the test-discipline rule under its new id `vitest-from-systemfsoftware-vitest`. `@systemfsoftware/vitest` documentation examples import from `@systemfsoftware/vitest`.
10
+
11
+ The lawful runner is published under its own name, `@systemfsoftware/vitest`, starting at `0.1.0`, so peers declared as `^0.1.0` accept its patch releases.
12
+
13
+ - A failing test now reports one failure record instead of a raw cause: the error with its message, the first source line outside the spec libraries, the steps that ran and the cell decisions each caused, and a rerun command for that one scenario. Every cause in the chain keeps all lines of its message. A replay value is printed only for a run a seed or a property chose. Each failure prints once; other errors in the same cause are logged. A failed `expect` points at the line that called it and a property failure at the line that declared it. A new `@systemfsoftware/vitest/failure` entry exports the renderer, the recorder, the cause summary (`summaryOf`) and the author call-site capture (`callSite`, `callFrameOutside`, `withRaisingFrame`) for libraries that raise their own failures.
14
+
15
+ - Kernel runs are serialized. A run started while another is live now waits behind it, in arrival order, instead of failing: two runs never interleave, so a scenario never sees another run's decisions, and a suite whose cases drive the kernel can run its tests concurrently.
16
+
17
+ `@systemfsoftware/vitest` adds `captureRunBinding`, the running test's run binding. `bind(effect)` re-provides it to an effect a library runs on a runtime of its own — its own scheduler, a worker, a simulation kernel — so the checks inside it count as that test's assertions, report softly, and see the same `owned` regions.
18
+
19
+ `@systemfsoftware/effect-spec-runtime` runs a case that declares no live reason through that runner, and a case's annotations now reach the test's report.
20
+
21
+ ## 0.1.0
22
+
23
+ ### Minor Changes
24
+
25
+ - Tests take `expect` from the test callback and yield each check: `it(name, function* ({ expect }) { yield* expect(actual).toEqual(expected) })`. The package stops exporting `expect`, `assert` and the `utils` helpers. With its `guard` entry listed in `setupFiles`, an `expect` imported from Vitest, or a test registered through Vitest's own `it`, is refused. Also refused, each with its rewrite as the message: a second check on one observed state, an unyielded check, a body with no check, a non-generator body, `it.effect`/`it.scoped`/`it.scopedLive`, `beforeEach`/`afterEach`, and weak matchers such as `toHaveLength`, `toBeDefined`, snapshots, `expect.anything`, `expect.soft`/`expect.poll` and argument-less `toThrow`. Libraries that judge inside a test import `step`, `captureRunBinding` and the `Check`/`Expect`/`Asserted` types from the `integration` entry.
26
+
3
27
  ## 0.0.0
4
28
 
5
29
  ### Minor Changes
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @systemfsoftware/vitest
2
2
 
3
- A fork of [`@effect/vitest`](https://github.com/Effect-TS/effect/tree/main/packages/vitest) whose defaults make the lazy test a good test. Write the obvious thing and you get a fresh build of your services, a second run that catches leaked state, virtual time, checks that stop the test at its next step, and properties refuted against a constant impostor. Write the slop form and the refusal names the rewrite.
3
+ A fork of [`@effect/vitest`](https://github.com/Effect-TS/effect/tree/main/packages/vitest) whose defaults make the lazy test a good test. `expect` is the parameter of the test's own body, every check is yielded, and one observed state gets one check. Write the obvious thing and you also get a fresh build of your services, a second run that catches leaked state, virtual time, and properties refuted against a constant impostor. Write the slop form and the refusal names the rewrite.
4
4
 
5
- Everything upstream exports is still exported: `it`, `test`, `it.effect`, `it.live`, `it.scoped`, `it.each`, `it.layer`, `layer`, `describe`, `expect`, `it.prop`, `it.effect.prop`, `flakyTest`, `addEqualityTesters`, `makeMethods`, `describeWrapped`, and `export * from "vitest"`. Two things do differ from upstream: `describe` is the fork's lawful collector, and four accepted-input types are narrower — both are listed under [Compatibility](#compatibility-with-effectvitest). On top of that surface the fork adds six things: `owned`, `recordAssertion`, `captureRunBinding`, `layer(L, { shared: true })`, a lawful `it.prop`, and `VitestTestContext` — the running test's context, published so a library can read it. `layer`, `it.layer`, `flakyTest`, `it.prop`, `it.effect`, `it.live`, `describeWrapped` and `it` itself also take a data-last form (`it.effect(body, timeout?)(name)`, `layer(options)(L)`, `flakyTest(timeout?)(effect)`), so they pipe.
5
+ Everything upstream exports is still exported except what the callback parameter replaces. `it`, `test`, `it.live`, `it.each`, `it.layer`, `layer`, `describe`, `it.prop`, `it.effect.prop`, `flakyTest`, `addEqualityTesters`, `makeMethods` and `describeWrapped` are here; `expect` and `assert` are not; `it.effect`, `it.scoped` and `it.scopedLive` are refusals carrying the generator rewrite; and the Vitest values the fork keeps are re-exported by name — the list is under [Compatibility](#compatibility-with-effectvitest). Two things differ from upstream: `describe` is the fork's lawful collector, and four accepted-input types are narrower. On top of that surface the fork adds `@systemfsoftware/vitest/integration` (`step`, `captureRunBinding`), `layer(L, { shared: true })`, a lawful `it.prop`, and `VitestTestContext` — the running test's context, published so a library can read it. `it`/`test`/`it.live`/`describeWrapped`/`layer`/`flakyTest` also take a data-last form (`it(body, timeout?)(name)`), so they pipe.
6
6
 
7
7
  The defaults are forced, not opted into. Every package in this workspace imports `@systemfsoftware/vitest` by its own name, and published packages peer on it directly — nothing resolves through an alias to the upstream specifier.
8
8
 
@@ -25,7 +25,7 @@ npm install -D @systemfsoftware/vitest
25
25
  ## Quick start
26
26
 
27
27
  ```ts
28
- import { expect, it, layer } from '@systemfsoftware/vitest'
28
+ import { it, layer } from '@systemfsoftware/vitest'
29
29
  import { Context, Effect, Layer, Ref } from 'effect'
30
30
 
31
31
  class Store extends Context.Service<Store, Ref.Ref<ReadonlyArray<string>>>()('Store') {
@@ -33,16 +33,55 @@ class Store extends Context.Service<Store, Ref.Ref<ReadonlyArray<string>>>()('St
33
33
  }
34
34
 
35
35
  layer(Store.layer)('store', (it) => {
36
- it.effect('starts from an empty store', () =>
37
- Effect.gen(function*() {
38
- const store = yield* Store
39
- const items = yield* Ref.get(store)
40
- expect(items).toEqual([])
41
- }))
36
+ it('starts from an empty store', function*({ expect }) {
37
+ const store = yield* Store
38
+ const items = yield* Ref.get(store)
39
+ yield* expect(items).toEqual([])
40
+ })
41
+ })
42
+ ```
43
+
44
+ A test body is a generator, and the runner drives it: `yield*` a service, an Effect or a check. The layer is built fresh for the test, and the test runs twice on two different builds — a test that only passes the first time is a test that leaked state.
45
+
46
+ ## Checks
47
+
48
+ `expect` is not importable. The only one in reach is the `{ expect }` the body receives, so a check is bound to its test by construction. Every matcher returns a **check**: an `Effect` that needs the runner's `Asserted` service, which is why it has to be yielded.
49
+
50
+ ```ts
51
+ it('ships a pending order', function*({ expect }) {
52
+ const order = yield* shop.place(items)
53
+ yield* expect(order).toMatchObject({ id: 1, status: 'Pending' })
42
54
  })
43
55
  ```
44
56
 
45
- The layer is built fresh for the test, and the test runs twice on two different builds. A test that only passes the first time is a test that leaked state.
57
+ - **One check per observed state.** A second check before the body's next non-check step — and a check inside a loop — is refused with `assert the state once` and the rewrite. A `yield*` of anything that is not a check opens a new observed state, so `yield* TestClock.adjust('3 seconds')` between two checks is a second state.
58
+ - **A check that fails stops the test there.** The generator is returned, so `finally` blocks and scope finalizers still run, and the report carries the diff.
59
+ - **A body that yields no check is refused** at the test name (compile time) and at run time; a check that is written but never yielded is refused as `written but never yielded`.
60
+ - **`toEqual` compares with Effect `Equal`**, so two structurally equal values of different references are equal.
61
+ - **The vocabulary is curated.** `toEqual`, `toStrictEqual`, `toBe`, `toContain`, `toMatch`, `toMatchObject`, `toThrow(X)`, `toSatisfy(fn, why)`, the call-ordering matchers, the ordered comparisons, and `objectContaining`/`arrayContaining`/`stringMatching`/`closeTo`/`any`/`schemaMatching` are kept. Everything weak is refused by name (see [Refusals](#refusals)), and a `boolean` actual is a type error — a boolean can only report `expected false to be true`.
62
+
63
+ ### The guard
64
+
65
+ A check is the only way to assert, and the guard is what makes that true rather than advisory. `@systemfsoftware/vitest/guard` installs a wrapper over chai's assertion prototype and marks every task the fork registers. The shared Vitest config loads it into every test project, inline projects included, so no import is needed. A project stays out only when vitest-config's exemption table names it together with the foreign runner that registers its tests (oxlint's `RuleTester`, Storybook's plugin). A package outside that table that cannot resolve `@systemfsoftware/vitest/guard` fails at config load rather than running unguarded. Under it:
66
+
67
+ - **a raw `vitest` `expect` (or `assert`) is refused**, even beside a real check: "✗ an expect imported from vitest ran; take it from the test callback: it(name, function\* ({ expect }) { ... })";
68
+ - **a test registered with `vitest`'s own `it`/`test` is refused**: "✗ this test was registered with vitest's it; import it from @systemfsoftware/vitest".
69
+
70
+ ## Lanes
71
+
72
+ ```ts
73
+ import { describe, it, layer } from '@systemfsoftware/vitest'
74
+
75
+ it(name, function*({ expect }) { /* virtual time */ })
76
+ it.live(name, function*({ expect }) { /* the real clock */ })
77
+ it.each(rows)(name, function*(row, { expect }) { /* Vitest's it.for order */ })
78
+ layer(Service.layer)((it) => { it(name, function*({ expect }) { ... }) })
79
+ describe('a block', (it) => { ... })
80
+ ```
81
+
82
+ `it`, `test`, `it.live`, `it.each`, `it.only`, `it.skip`, `it.skipIf`, `it.runIf`, `it.fails`, `it.layer`, `layer` and `describeWrapped` all take a generator body. `it`/`test`/`it.live`/`describeWrapped`/`layer`/`flakyTest` also take a data-last form (`it(body, timeout?)(name)`), so they pipe. `it.prop`, `it.effect.prop`, `it.law` and `flakyTest` are unchanged from #512.
83
+
84
+ The habit lanes are refusals, not aliases: `it.effect`, `it.scoped` and `it.scopedLive` are compile-time errors and run-time throws carrying the generator rewrite. `it.effect.prop` still runs properties. `beforeEach` and `afterEach` are refused too — build what a test needs inside it; services come fresh per test from `layer`.
46
85
 
47
86
  ## Layers
48
87
 
@@ -60,60 +99,62 @@ That is how a module-level counter, a shared cache, or a process-global histogra
60
99
 
61
100
  ## Concurrency and shuffle
62
101
 
63
- Tests inside a block run concurrently and in shuffled order, and each concurrent test keeps its own failure list and assertion counts. Concurrency is what makes a cross-test leak reproducible: two tests that interleave are two tests whose state must not be shared. Nothing else pins their order.
102
+ Tests inside a block run concurrently and in shuffled order, and each concurrent test keeps its own failure list and check counts. Concurrency is what makes a cross-test leak reproducible: two tests that interleave are two tests whose state must not be shared. Nothing else pins their order.
64
103
 
65
104
  A failure in a shuffled block names the seed the run used. Pass that seed back with `--sequence.seed=<seed>` and the same order replays, so an order-dependent failure stops being intermittent.
66
105
 
67
- ## Checks
68
-
69
- `expect` records softly within one Effect step. Every failed check in the step is reported, and before any of the test's fibers takes its next step after a failed check, that fiber is interrupted — the step after a failed check never runs its side effects, which would only act on a state already known wrong. A child fiber is interrupted too. Anything thrown after a failed check is reported as `AfterFailedExpect`, most likely caused by the failure above it.
70
-
71
- `toEqual` compares with Effect `Equal`, so two values that are structurally equal but different references are equal. A test that asserts nothing fails: the code under test returned something, so assert on it. A testing library whose checks are Effect-native can count them through `recordAssertion()`.
72
-
73
106
  ## Virtual time
74
107
 
75
- Effect bodies run on virtual time that advances only when the test's fibers are idle. `Effect.sleep("3 seconds")` returns without waiting; on a deadline tie the background sleepers wake before the test fiber; fractional-millisecond schedules work.
108
+ Test bodies run on virtual time that advances only when the test's fibers are idle. `Effect.sleep("3 seconds")` returns without waiting; on a deadline tie the background sleepers wake before the test fiber; fractional-millisecond schedules work.
76
109
 
77
110
  ```ts
78
- import { expect, it } from '@systemfsoftware/vitest'
111
+ import { it } from '@systemfsoftware/vitest'
79
112
  import { Clock, Effect } from 'effect'
80
113
 
81
- it.effect('three seconds pass at once', () =>
82
- Effect.gen(function*() {
83
- const before = yield* Clock.currentTimeMillis
84
- yield* Effect.sleep('3 seconds')
85
- const after = yield* Clock.currentTimeMillis
86
- expect(after - before).toEqual(3000)
87
- }))
114
+ it('three seconds pass at once', function*({ expect }) {
115
+ const before = yield* Clock.currentTimeMillis
116
+ yield* Effect.sleep('3 seconds')
117
+ const after = yield* Clock.currentTimeMillis
118
+ yield* expect(after - before).toEqual(3000)
119
+ })
88
120
  ```
89
121
 
90
122
  `TestClock.adjust` still moves the clock — on virtual time, adjusting is letting that much time pass, which is the one clock move a test can ask for. Effect v3 code that imports `TestClock` from `effect/TestClock` resolves to this fork's compat entry, `@systemfsoftware/vitest/TestClock`, through the shared Vitest config's `effect/TestClock` alias.
91
123
 
92
124
  ## Refusals
93
125
 
94
- Each refusal states its rewrite, in the same words at compile time and at run time:
95
-
96
- | Refused | The rewrite in the message |
97
- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
98
- | `expect(x).toBeDefined()`, `.toBeTruthy()`, `.toBeFalsy()`, and the negated `.not.toBeNull()`, `.not.toBeUndefined()` | Assert the value: `toEqual(expected)`; for a key that must exist: `toHaveProperty(key)` |
99
- | `beforeEach` / `afterEach` | Build what a test needs inside it; services come fresh per test from `layer(Service.layer)((it) => { ... })` |
100
- | an `async` test body | Return the Effect instead: `it.effect(name, () => Effect.gen(function* () { ... }))` |
101
- | an Effect body that needs a service nothing provides | Pipe the body through `Effect.provide(Service.layer)`, or write the tests inside `layer(Service.layer)((it) => { ... })` |
102
- | the positional `it.prop(name, [arbitraries], predicate)` | Name the function under test and pass a budget: `it.prop(name, { of, subject, runs }, holds)` |
103
- | a test with no assertion (at run time only) | Assert on what the code returned: `expect(actual).toEqual(expected)` |
104
-
105
- A presence refusal reads exactly like this:
106
-
107
- > ✗ toBeDefined passes for almost any value the code returns. Assert the value: toEqual(expected); for a key that must exist: toHaveProperty(key).
108
-
109
- `expect(<boolean>)` is a type error — a boolean can only report `expected false to be true`. Pass the two values instead: `expect(a).toEqual(b)`, which uses Effect `Equal`, or a predicate: `expect(value).toSatisfy(predicate)`. A boolean the code under test returned stays legal. `toBe` on primitives, `toBeTypeOf` and `toBeInstanceOf` are not refused.
126
+ Each refusal states its rewrite, in the same words at compile time (a `this` type, an argument union, or the test name) and at run time:
127
+
128
+ | Refused | The rewrite in the message |
129
+ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
130
+ | `toBeDefined`, `toBeTruthy`, `toBeFalsy`, `not.toBeNull`, `not.toBeUndefined` | Assert the value: `yield* expect(actual).toEqual(expected)` |
131
+ | `toHaveLength` | Assert the contents: `toEqual([...])`; only some of them: `toEqual(expect.arrayContaining([...]))` |
132
+ | `toHaveProperty` | Assert the shape once: `toMatchObject({ key: value, ... })` |
133
+ | `toBeInstanceOf`, `toBeTypeOf` | Assert the value: `toEqual(expected)`; only its shape: `toEqual(expect.schemaMatching(Schema))` |
134
+ | `toHaveBeenCalled`, `toHaveBeenCalledTimes` | `toHaveBeenCalledExactlyOnceWith(...args)`, or `expect(spy.mock.calls).toEqual([[...], [...]])` |
135
+ | every snapshot matcher | State the expected value: `toEqual(expected)`; for a throw: `toThrow(MyError)` |
136
+ | `expect.anything`, `.resolves`, `.rejects`, `expect.poll`, `expect.soft` | Name what the value must be, yield the Effect, or let virtual time pass |
137
+ | `toThrow()`, `toThrowError()`, `toSatisfy(fn)`, `toMatchObject({})` | Name the error, the reason, or the fields: `toThrow(MyError)`, `toSatisfy(predicate, "why")`, `toMatchObject({ status: "Pending" })` |
138
+ | a `boolean` actual | Pass the two values instead: `expect(a).toEqual(b)`; a predicate: `toSatisfy(predicate, "what must hold")` |
139
+ | a second check on one state | Assert the state once: `toMatchObject({...})`, `expect({ a, b }).toEqual({...})`, or `it.each` for several inputs |
140
+ | a check written but never yielded | Yield it: `yield* expect(actual).toEqual(expected)` |
141
+ | a body that yields no check | Yield one from the test's own `expect` |
142
+ | a sync body, an `async` body, a body returning an Effect | Pass the generator itself: `it(name, function* ({ expect }) { ... })` |
143
+ | `it.effect` / `it.scoped` / `it.scopedLive` | `it(name, function* ({ expect }) { const x = yield* program; yield* expect(x).toEqual(expected) })` |
144
+ | `beforeEach` / `afterEach` | Build what a test needs inside it; services come fresh per test from `layer(Service.layer)((it) => { ... })` |
145
+ | a body that needs a service nothing provides | Write the test inside `layer(Service.layer)((it) => { ... })`; every test gets its own fresh build |
146
+ | the positional `it.prop(name, [arbitraries], predicate)` | Name the function under test and pass a budget: `it.prop(name, { of, subject, runs }, holds)` |
147
+
148
+ A named refusal reads exactly like this:
149
+
150
+ > ✗ toHaveLength checks how many, not which. Assert the contents: toEqual([...]); only some of them: toEqual(expect.arrayContaining([...])).
110
151
 
111
152
  ## Properties
112
153
 
113
154
  A property names the function under test. It runs `runs` times; omit `runs` and the run's configured default applies:
114
155
 
115
156
  ```ts
116
- import { expect, it } from '@systemfsoftware/vitest'
157
+ import { it } from '@systemfsoftware/vitest'
117
158
  import { Schema as S } from 'effect'
118
159
 
119
160
  const sort = (xs: ReadonlyArray<number>): ReadonlyArray<number> => [...xs].sort((a, b) => a - b)
@@ -171,31 +212,38 @@ A class fails only when a sequential statistical test is confident its share is
171
212
 
172
213
  ## For testing libraries
173
214
 
174
- A library that registers tests for its users wires in with two calls.
175
-
176
- `owned(effect)` marks a region where `expect` throws instead of recording softly, so the enclosing Effect sees a failed check as a failure in its cause:
215
+ A library takes the test's `expect` by parameter and ends in a check:
177
216
 
178
217
  ```ts
179
- import { owned } from '@systemfsoftware/vitest'
218
+ import type { Check, Expect } from '@systemfsoftware/vitest'
180
219
  import { Effect } from 'effect'
181
220
 
182
- // A library wraps the user's step; a failed check inside fails the step.
183
- export const runStep = <A, E, R>(step: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> => owned(step)
221
+ export const runContract = (expect: Expect) =>
222
+ Effect.gen(function*() {
223
+ const report = yield* reportOf()
224
+ yield* expect(report).toMatchObject({ holds: true })
225
+ })
184
226
  ```
185
227
 
186
- `recordAssertion()` counts an Effect-native check as an assertion, so a test whose only judgement is the library's own passes the no-assertion gate:
228
+ A flow that asserts at several points marks each point with `step` from `@systemfsoftware/vitest/integration`, so every phase opens its own observed state and each gets one check:
187
229
 
188
230
  ```ts
189
- import { recordAssertion } from '@systemfsoftware/vitest'
231
+ import { step } from '@systemfsoftware/vitest/integration'
190
232
  import { Effect } from 'effect'
191
233
 
192
- export const recordVerdict = (): Effect.Effect<void> => Effect.sync(recordAssertion)
234
+ export const runPipeline = (expect: Expect) =>
235
+ Effect.gen(function*() {
236
+ yield* step(yield* phaseOne())
237
+ yield* expect(yield* phaseOne()).toMatchObject({ ok: true })
238
+ yield* step(yield* phaseTwo())
239
+ yield* expect(yield* phaseTwo()).toMatchObject({ ok: true })
240
+ })
193
241
  ```
194
242
 
195
- `captureRunBinding` is the test's run binding. `bind` re-provides it to an effect a library runs on a runtime of its own — its own scheduler, a worker, a simulation kernel — so the checks inside it count as that test's assertions, report softly, and see the same `owned` regions as the test. Capture it from the test's own fiber and bind the effect before handing it to the other runtime:
243
+ `captureRunBinding` — from `@systemfsoftware/vitest/integration` — is the test's run binding. Capture it inside the test, then `bind` the effect before handing it to a runtime of your own — its own scheduler, a worker, a simulation kernel — so its checks count as that test's assertions. `bind` provides what a check reads off the test's own fiber (the ledger, and the running task context) and the bound effect no longer requires `Asserted`, which is what lets it cross into a runtime that cannot know the ledger. Every run of a bound effect opens its own observed state, so a runtime that re-runs it — a kernel's baseline and its seeded replays — checks each run once; two checks inside one run are still refused:
196
244
 
197
245
  ```ts
198
- import { captureRunBinding } from '@systemfsoftware/vitest'
246
+ import { captureRunBinding } from '@systemfsoftware/vitest/integration'
199
247
  import { Effect } from 'effect'
200
248
 
201
249
  export const runOnOwnScheduler = <A, E>(
@@ -207,7 +255,7 @@ export const runOnOwnScheduler = <A, E>(
207
255
  })
208
256
  ```
209
257
 
210
- `VitestTestContext` is the running Vitest `TestContext` under the key `vitestTestContextKey`, provided on every test the fork runs. A library that carries its own view of the task context builds it on that key, so a case lane and a property lane read the same context:
258
+ `VitestTestContext` is the running Vitest `TestContext` under the key `vitestTestContextKey`, provided on every test the fork runs — every generator lane, both property lanes, and each `it.each` row. A library that carries its own view of the task context builds it on that key, so a case lane and a property lane read the same context:
211
259
 
212
260
  ```ts
213
261
  import { vitestTestContextKey } from '@systemfsoftware/vitest'
@@ -218,16 +266,20 @@ export const TaskRef = Context.Reference<{ annotate?: (message: string) => void
218
266
  })
219
267
  ```
220
268
 
269
+ There is no `owned`, no `recordAssertion` and no `@systemfsoftware/vitest/utils`: a library reports through the caller's check, and the one-check rule is what keeps a verdict a verdict.
270
+
221
271
  ## Compatibility with @effect/vitest
222
272
 
223
- Everything upstream exports is still exported, and the fork adds behaviour rather than removing bindings: example tests, `it.each`, `toStrictEqual` and the other Vitest bindings stay. It is not a drop-in, though, in one behaviour and four accepted-input types:
273
+ This is a fork of the surface, not a superset of it. What changed:
224
274
 
225
- - `describe` is the fork's lawful collector (R4), not upstream's: it forces the fork's concurrency and shuffle defaults and hands its body the fork's methods. Vitest's own collector — one test at a time, in declaration order — stays reachable as `import { describe } from 'vitest'`.
226
- - `deepStrictEqual`, `notDeepStrictEqual`, `strictEqual` and `assertEquals` take no message argument. A data-last dual has to repeat its data-first twin parameter for parameter, so a trailing string cannot be told from an expected value; the message is refused rather than silently dropped.
227
- - `skipIf` and `runIf` take a `boolean` condition where upstream took `unknown`, and `each` cases must be objects (`T extends object`). An `unknown` or `any` condition is refused by this repo's lint.
228
- - `assertTrue` takes a `boolean` where upstream took `unknown`, for the same reason.
275
+ - **No `expect` and no `assert`**, from this package or from its `vitest` re-export; the callback parameter replaces both (and the guard refuses a raw one).
276
+ - **No `it.effect`, `it.scoped`, `it.scopedLive`, `owned`, `recordAssertion`, `@systemfsoftware/vitest/utils`, or `@systemfsoftware/vitest/refusals`.** `it.effect.prop` remains. `it.live` is a generator body on the real clock.
277
+ - **`export * from "vitest"` is gone.** The Vitest values the fork keeps — `vi`, `expectTypeOf`, `assertType`, `beforeAll`, `afterAll`, `onTestFailed`, `onTestFinished`, `inject`, `recordArtifact`, `vitest` — are re-exported explicitly, so a refused value cannot arrive through a star export.
278
+ - **A test body is a generator**, so a sync, `async` or Effect-returning body is refused with the rewrite.
279
+ - **`describe` is the fork's lawful collector** (forced concurrency and shuffle, the fork's methods), not upstream's. Vitest's own collector stays reachable as `import { describe } from 'vitest'`.
280
+ - **`skipIf` and `runIf` take a `boolean`**, where upstream took `unknown`; an `unknown` or `any` condition is refused by this repo's lint.
229
281
 
230
- What else differs is what runs by default, what `expect` refuses, and what `toEqual` means — that is why the fork is imported under its own name rather than aliasing upstream.
282
+ What else differs is what runs by default, what a check refuses, and what `toEqual` means — that is why the fork is imported under its own name rather than aliasing upstream.
231
283
 
232
284
  This package is part of [systemfsoftware](https://github.com/systemfsoftware/systemfsoftware).
233
285
 
@@ -1,6 +1,6 @@
1
1
 
2
- import * as Effect from "effect/Effect";
3
2
  import * as Duration from "effect/Duration";
3
+ import * as Effect from "effect/Effect";
4
4
  //#region src/TestClock.d.ts
5
5
  export declare const adjust: (duration: Duration.Input) => Effect.Effect<void>;
6
6
  //#endregion