@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/dist/index.mjs CHANGED
@@ -1,59 +1,88 @@
1
1
 
2
- import { a as refuseBareEffect, c as refuseNoAssertion, i as refuseAsync, l as refusePositionalProp, n as narrowVacuous, r as presenceMessage, s as refuseHook, t as narrowNegated, u as refuseUnprovided } from "./refusals-BQtDszyU.mjs";
2
+ import { a as replayOfFailure, c as isFailureRecordError, f as InvalidBudget, h as Slop, i as makeProperty, l as throwFailureRecord, m as NonBooleanVerdict, o as testIdentityOf, p as LeakedState, r as createSpanRecorder, s as providedRoot } from "./failure-BquJJPFZ.mjs";
3
+ import { a as isCheck, i as checksFor, n as vitestTestContextKey$1, o as makeLedger, r as Asserted, t as VitestTestContext$1 } from "./test-context-ChvjVyCo.mjs";
4
+ import { S as unprovidedText, _ as refuseScopedLiveLane, d as refuseAsyncBody, f as refuseEffectLane, g as refuseScopedLane, i as markTask, l as noCheckRefusal, o as isRefusal$1, p as refuseHook, u as refusalOf$1, v as refuseSyncBody, y as refuseUnyielded } from "./guard-p_8ARgPm.mjs";
3
5
  import * as V from "vitest";
4
- import { inject } from "vitest";
5
- import * as Function from "effect/Function";
6
- import * as Context from "effect/Context";
7
- import * as Fiber from "effect/Fiber";
8
- import * as Equal from "effect/Equal";
9
- import { Schema } from "effect";
10
- import * as Effect from "effect/Effect";
6
+ import { afterAll, assertType, beforeAll, expectTypeOf, inject, onTestFailed, onTestFinished, recordArtifact, vi, vitest } from "vitest";
11
7
  import * as Cause from "effect/Cause";
12
8
  import * as Duration from "effect/Duration";
9
+ import * as Effect from "effect/Effect";
13
10
  import * as Exit from "effect/Exit";
11
+ import * as Function from "effect/Function";
14
12
  import * as Layer from "effect/Layer";
15
13
  import * as Schedule from "effect/Schedule";
16
- import * as Scheduler from "effect/Scheduler";
17
- import * as Schema$1 from "effect/Schema";
14
+ import * as Schema from "effect/Schema";
18
15
  import * as Scope from "effect/Scope";
19
16
  import * as TestClock from "effect/testing/TestClock";
20
17
  import * as TestConsole from "effect/testing/TestConsole";
21
- import * as Arbitrary from "effect/unstable/arbitrary/Arbitrary";
22
- import * as Data from "effect/Data";
18
+ import * as Equal from "effect/Equal";
23
19
  import * as Clock from "effect/Clock";
24
- export * from "vitest";
25
- //#region src/internal/binding.ts
20
+ import * as Scheduler from "effect/Scheduler";
21
+ //#region src/internal/driver.ts
26
22
  /**
27
- * The run a test's fibers execute in. Effect code reads it through
28
- * `Fiber.getCurrent`, so no node-only store is needed (KTD4).
23
+ * The driver: one Effect that steps the body's generator in the fiber the test runs in.
29
24
  *
30
- * @internal
25
+ * A `yield*` is a step, so the runner sees every one of them (KTD1):
26
+ * - a branded check is judged against this test's ledger, so it counts, and a failed judgement fails the check
27
+ * itself and stops the test right there (R8);
28
+ * - anything else opens a new observed state and then runs, which is what makes a second check on one state a
29
+ * second check in one window (R5), and a check inside a loop the same window as its siblings.
30
+ *
31
+ * The steps run in the same fiber as the test, so layers, virtual time, concurrency, the second run and scope
32
+ * finalizers keep working through the runner unchanged. A step that fails, and the test being interrupted, both
33
+ * return the generator: its `finally` blocks run and the steps after it never start (R8). When the body returns,
34
+ * the gate refuses what the body wrote and never judged, then a body that judged nothing at all (R4).
35
+ */
36
+ /**
37
+ * A value the body yielded, as the driver runs it. What a body needs of its environment belongs to the layer the
38
+ * runner provides around this whole Effect, so the driver erases the channels here, exactly as the runner erases
39
+ * a body's own (`outputIsEffect` in `runner.ts`): the layer block is where a requirement is checked, and a body
40
+ * that needs a service nothing provides is refused by the runner at run time (R9).
41
+ */
42
+ const isRunnable = (value) => Effect.isEffect(value);
43
+ /** A body that yielded something the driver cannot run ran nothing, so nothing in the test could fail. */
44
+ const refuseUnsteppable = "✗ the body yielded a value that is not an Effect. Yield the Effect itself, or a check: yield* expect(actual).toEqual(expected).";
45
+ /**
46
+ * A yielded Effect: a branded check judges itself, and anything else opens a new observed state before it runs,
47
+ * which is what resets the ledger's one-check window (R5).
48
+ */
49
+ const runEffect = (value, ledger) => isCheck(value) ? value : Effect.andThen(Effect.sync(() => ledger.asserted.step()), value);
50
+ /** One step: anything the driver cannot run is refused, because then no step of the test ran at all. */
51
+ const runStep = (value, ledger) => isRunnable(value) ? runEffect(value, ledger) : Effect.die(refusalOf$1(refuseUnsteppable));
52
+ /** Steps the iterator to its end, feeding each yielded Effect's result back into the body. */
53
+ const stepThrough = (steps, ledger, input) => Effect.suspend(() => {
54
+ const state = steps.next(input);
55
+ if (state.done === true) return Effect.void;
56
+ return Effect.flatMap(runStep(state.value, ledger), (value) => stepThrough(steps, ledger, value));
57
+ });
58
+ /**
59
+ * After the body returns: a check written and never yielded is refused first, then a body that judged none.
60
+ * Both carry the same text the refusal carries on its compile channel, branded by `refusalOf` so the runner's
61
+ * second run re-throws them as the refusals they are.
31
62
  */
32
- const Running = Context.Reference("effect-vitest/binding", { defaultValue: () => void 0 });
33
- /** @internal */
34
- const currentRun = () => Fiber.getCurrent()?.getRef(Running);
35
- let syncRun = void 0;
36
- /** @internal */
37
- const withSyncRun = Function.dual(2, (run, body) => {
38
- const previous = syncRun;
39
- syncRun = run;
40
- try {
41
- return body();
42
- } finally {
43
- syncRun = previous;
44
- }
63
+ const gate = (ledger) => Effect.suspend(() => ledger.unyielded() > 0 ? refuse(refuseUnyielded) : judgedNone(ledger));
64
+ const judgedNone = (ledger) => ledger.judged() === 0 ? refuse(noCheckRefusal) : Effect.void;
65
+ const refuse = (text) => Effect.die(refusalOf$1(text));
66
+ /**
67
+ * A failure or an interruption returns the generator, so its `finally` blocks run and the step after the failed
68
+ * check never does (R8).
69
+ */
70
+ const unwind = (steps) => Effect.sync(() => {
71
+ steps.return?.(void 0);
45
72
  });
73
+ const driveBody = (body, args, ledger) => Effect.suspend(() => {
74
+ const steps = body(...args);
75
+ return Effect.onExit(Effect.flatMap(stepThrough(steps, ledger, void 0), () => gate(ledger)), (exit) => Exit.isSuccess(exit) ? Effect.void : unwind(steps));
76
+ });
77
+ /** The body's steps, with this test's ledger provided as the `Asserted` every check in it reaches for. */
78
+ const driven = (body, args, ledger) => Effect.provideService(driveBody(body, args, ledger), Asserted, ledger.asserted);
46
79
  /**
47
- * The run the current execution belongs to: the fiber's binding first, then the
48
- * synchronous slot for code that runs outside a fiber.
80
+ * Runs one test's body: `body` called with `args`, stepped against `ledger`, with the ledger's `asserted`
81
+ * provided as `Asserted` so every check in the body — including one yielded inside a forked fiber — reaches it.
49
82
  *
50
83
  * @internal
51
84
  */
52
- const lookupRun = () => currentRun() ?? syncRun;
53
- const NO_ERRORS = [];
54
- /** @internal */
55
- const errorCount = (ctx) => presentErrors(ctx.task.result?.errors).length;
56
- const presentErrors = (errors) => errors === void 0 ? NO_ERRORS : errors;
85
+ const drive = Function.dual(3, driven);
57
86
  //#endregion
58
87
  //#region src/internal/equal.ts
59
88
  let registered = false;
@@ -67,653 +96,6 @@ const equalTester = (first, second) => equalTogether(first, second) ? true : voi
67
96
  const equalTogether = (first, second) => bothEqualable(first, second) && Equal.equals(first, second);
68
97
  const bothEqualable = (first, second) => Equal.isEqual(first) && Equal.isEqual(second);
69
98
  //#endregion
70
- //#region src/internal/errors.schema.ts
71
- /** @internal */
72
- var NonBooleanVerdict = class extends Schema.TaggedError()("NonBooleanVerdict", { detail: Schema.String }) {
73
- get message() {
74
- return this.detail;
75
- }
76
- };
77
- /** @internal */
78
- var LeakedState = class extends Schema.TaggedError()("LeakedState", { detail: Schema.String }) {
79
- get message() {
80
- return this.detail;
81
- }
82
- };
83
- /** @internal */
84
- var AfterFailedExpect = class extends Schema.TaggedError()("AfterFailedExpect", { detail: Schema.String }) {
85
- get message() {
86
- return this.detail;
87
- }
88
- };
89
- /** @internal */
90
- var Slop = class extends Schema.TaggedError()("Slop", { detail: Schema.String }) {
91
- get message() {
92
- return this.detail;
93
- }
94
- };
95
- /** @internal */
96
- var InvalidBudget = class extends Schema.TaggedError()("InvalidBudget", { detail: Schema.String }) {
97
- get message() {
98
- return this.detail;
99
- }
100
- };
101
- //#endregion
102
- //#region src/internal/owned.ts
103
- /** @internal */
104
- const OwnedAssertions = Context.Reference("effect-vitest/owned", { defaultValue: () => false });
105
- /** @internal */
106
- const isOwned = () => Fiber.getCurrent()?.getRef(OwnedAssertions) === true;
107
- /** @internal */
108
- const owned$1 = (self) => Effect.provideService(self, OwnedAssertions, true);
109
- /**
110
- * Counts an Effect-native check as an assertion of the run it happens in, so the run's own gate sees it.
111
- *
112
- * @internal
113
- */
114
- const recordAssertion$1 = () => {
115
- const run = lookupRun();
116
- if (run !== void 0) bump(run);
117
- };
118
- const bump = (run) => {
119
- const { expect } = run.ctx;
120
- expect.setState({ assertionCalls: expect.getState().assertionCalls + 1 });
121
- };
122
- const bindingOf = (running, owned) => ({ bind: (self) => self.pipe(Effect.provideService(Running, running), Effect.provideService(OwnedAssertions, owned)) });
123
- /**
124
- * The run binding of the test the caller is executing in.
125
- *
126
- * @internal
127
- */
128
- const captureRunBinding$1 = Effect.withFiber((fiber) => Effect.succeed(bindingOf(fiber.getRef(Running), fiber.getRef(OwnedAssertions))));
129
- //#endregion
130
- //#region src/internal/expect.ts
131
- registerEqualTester();
132
- const refuse = (detail) => {
133
- throw new Slop({ detail });
134
- };
135
- const narrowedNames = (negated) => negated ? narrowNegated : narrowVacuous;
136
- const narrowedName = (property, negated) => namedMatch(property, narrowedNames(negated));
137
- const namedMatch = (property, names) => typeof property === "string" ? foundMatch(property, names) : void 0;
138
- const foundMatch = (property, names) => names.find((name) => name === property);
139
- const refuseNarrowed = (name) => () => refuse(presenceMessage(name));
140
- const refusedMember = (property, negated) => {
141
- const name = narrowedName(property, negated);
142
- return name === void 0 ? void 0 : refuseNarrowed(name);
143
- };
144
- const memberAt = (target, property) => Reflect.get(target, property, target);
145
- /**
146
- * The narrowed names answer with the refusal, `not` re-enters the guard with the negation flipped, and every
147
- * other member is Vitest's own, handed over unbound so Chai's property accessors keep working.
148
- */
149
- const guardOf = (assertion, negated) => new Proxy(assertion, guardHandler(negated));
150
- const guardHandler = (negated) => ({ get: (target, property) => guardedMember(target, negated, property) });
151
- const guardedMember = (target, negated, property) => property === "not" ? guardOf(target.not, !negated) : forwardedMember(target, negated, property);
152
- const forwardedMember = (target, negated, property) => refusedMember(property, negated) ?? memberAt(target, property);
153
- const plainAssertion = (actual, message) => counted(guardOf(V.expect(actual, message), false));
154
- const countsInThisRun = () => lookupRun()?.shadow !== true;
155
- /**
156
- * A check outside a soft run counts on the run it happens in: an `owned` region's checks throw, and a
157
- * check outside every run has no gate to satisfy.
158
- */
159
- const counted = (assertion) => {
160
- if (countsInThisRun()) recordAssertion$1();
161
- return assertion;
162
- };
163
- const softAssertion = (running, actual, message) => guardOf(running.ctx.expect.soft(actual, message), false);
164
- const isSoftRun = (running) => running.shadow === false && isOwned() === false;
165
- const liveAssertion = (running, actual, message) => isSoftRun(running) ? softAssertion(running, actual, message) : plainAssertion(actual, message);
166
- /**
167
- * A check inside a run is soft: it records the failure and the test stops at the next step. The second run
168
- * and an `owned` region throw, so their own handler sees the failure.
169
- */
170
- const assertionFor = (actual, message) => {
171
- const running = lookupRun();
172
- return running === void 0 ? plainAssertion(actual, message) : liveAssertion(running, actual, message);
173
- };
174
- Function.dual(2, assertionFor);
175
- const messageOf = (value) => typeof value === "string" ? value : void 0;
176
- const applyCheck = (args) => assertionFor(args[0], messageOf(args[1]));
177
- /** The statics are Vitest's; only `soft` is the fork's, and soft is what a check already is inside a run. */
178
- const makeExpect = () => new Proxy(V.expect, {
179
- get: (target, property) => property === "soft" ? assertionFor : memberAt(target, property),
180
- apply: (_target, _thisArg, args) => applyCheck(args)
181
- });
182
- /**
183
- * The fork's `expect`: soft within one Effect step, refusing the slop forms.
184
- *
185
- * @internal
186
- */
187
- const expect$1 = makeExpect();
188
- const refuseHookNow = () => refuse(refuseHook);
189
- const takesHook = (args) => typeof args[0] === "function";
190
- const refuseHookPair = (fn, timeout) => {
191
- return refuseHookNow();
192
- };
193
- /**
194
- * @internal
195
- */
196
- const beforeEach$1 = Function.dual(takesHook, refuseHookPair);
197
- /**
198
- * @internal
199
- */
200
- const afterEach$1 = Function.dual(takesHook, refuseHookPair);
201
- /**
202
- * @internal
203
- */
204
- const beforeAll$1 = Function.dual(takesHook, V.beforeAll);
205
- /**
206
- * @internal
207
- */
208
- const afterAll$1 = Function.dual(takesHook, V.afterAll);
209
- //#endregion
210
- //#region src/internal/property/defaults.ts
211
- /** @internal The `test.provide` key the shared config fills with this run's tier's check options. */
212
- const checkDefaultsKey = "@systemfsoftware/vitest:property-check";
213
- /** @internal The configured property-check defaults, or `undefined` when the run provided none. */
214
- const providedCheckDefaults = () => inject(checkDefaultsKey);
215
- //#endregion
216
- //#region src/internal/property/coverage.ts
217
- /**
218
- * @internal The R14 coverage classes: labelled input predicates, each with a minimum share of runs.
219
- *
220
- * A class fails only when QuickCheck's sequential test (`stdConfidence`) is confident its share is below
221
- * the minimum, and is satisfied once the test is confident the share is at least 0.9 of the minimum
222
- * (`TOLERANCE` at a certainty of 10^9). The tolerance is what makes the test terminate: a class sitting
223
- * exactly at its minimum would otherwise draw forever.
224
- */
225
- const CERTAINTY = 10 ** 9;
226
- const TOLERANCE = .9;
227
- const FIRST_BATCH = 64;
228
- const BATCH_CAP = 4096;
229
- const DRAW_CAP = 1e5;
230
- const FAILURE_HEAD = "coverage";
231
- /** The certainty's two-sided tail probability: `alpha / 2` for `alpha = 1 / CERTAINTY`. */
232
- const TAIL_PROBABILITY = 1 / (2 * CERTAINTY);
233
- const C1 = -.007784894002430293;
234
- const C2 = -.3223964580411365;
235
- const C3 = -2.400758277161838;
236
- const C4 = -2.549732539343734;
237
- const C5 = 4.374664141464968;
238
- const C6 = 2.938163982698783;
239
- const D1 = .007784695709041462;
240
- const D2 = .3224671290700398;
241
- const D3 = 2.445134137142996;
242
- const D4 = 3.754408661907416;
243
- const TAIL_NUMERATOR = [
244
- C1,
245
- C2,
246
- C3,
247
- C4,
248
- C5,
249
- C6
250
- ];
251
- const TAIL_DENOMINATOR = [
252
- D1,
253
- D2,
254
- D3,
255
- D4
256
- ];
257
- /** Horner's rule over a coefficient list, highest power first. */
258
- const poly = (coefficients, x) => coefficients.reduceRight((total, coefficient) => total * x + coefficient, 0);
259
- const tailSpread = (p) => Math.sqrt(-2 * Math.log(p));
260
- const tailInverse = (spread) => poly(TAIL_NUMERATOR, spread) / poly(TAIL_DENOMINATOR, spread);
261
- /** The inverse standard-normal CDF at the tail probability the certainty asks for. */
262
- const TAIL_Z = tailInverse(tailSpread(TAIL_PROBABILITY));
263
- const wilson = (hits, runs, z) => {
264
- const share = hits / runs;
265
- const zz = z * z;
266
- return (share + zz / (2 * runs) + z * Math.sqrt(share * (1 - share) / runs + zz / (4 * runs * runs))) / (1 + zz / runs);
267
- };
268
- /**
269
- * Thrown when a coverage class is confidently below its minimum share of runs.
270
- *
271
- * @internal
272
- */
273
- var CoverageBelowMinimum = class extends Data.Error {};
274
- const wilsonLower = (entry, runs) => wilson(entry.hits, runs, -TAIL_Z);
275
- const wilsonUpper = (entry, runs) => wilson(entry.hits, runs, TAIL_Z);
276
- const covered = (entry, runs) => wilsonLower(entry, runs) >= TOLERANCE * entry.minimum;
277
- const uncovered = (entry, runs) => wilsonUpper(entry, runs) < entry.minimum;
278
- const decided = (entry, runs) => covered(entry, runs) || uncovered(entry, runs);
279
- const settled = (entry, runs) => runs === 0 ? false : decided(entry, runs);
280
- const entriesOf = (classes) => Object.keys(classes).map((label) => entryAt(classes, label));
281
- const entryAt = (classes, label) => [label, classOf(classes, label)];
282
- const classOf = (classes, label) => classes[label] ?? {
283
- hits: 0,
284
- minimum: 0
285
- };
286
- const judgeOf = (classes, runs) => ({
287
- classes: new Map(entriesOf(classes)),
288
- runs
289
- });
290
- const openLabels = (judge) => [...judge.classes].filter(([, entry]) => !settled(entry, judge.runs)).map(([label]) => label);
291
- const failedEntries = (judge) => [...judge.classes].filter(([, entry]) => uncovered(entry, judge.runs));
292
- const describeShare = (hits, runs) => `${String(hits)}/${String(runs)}`;
293
- const describeFailure = (label, entry, runs) => `${FAILURE_HEAD} ${label}: ${describeShare(entry.hits, runs)} of runs, below the required ${String(entry.minimum)}`;
294
- const reportFailures = (judge) => failedEntries(judge).map(([label, entry]) => describeFailure(label, entry, judge.runs)).join("\n");
295
- /** @internal */
296
- const countHit = Function.dual(2, (classes, label) => {
297
- const prior = classes.get(label);
298
- if (prior !== void 0) classes.set(label, {
299
- hits: prior.hits + 1,
300
- minimum: prior.minimum
301
- });
302
- });
303
- const countLabels = (classes, labels) => {
304
- for (const label of labels) countHit(classes, label);
305
- return classes;
306
- };
307
- const countDraws = (classes, drawn) => drawn.reduce(countLabels, new Map(classes));
308
- const withDraws = (judge, drawn) => ({
309
- classes: countDraws(judge.classes, drawn),
310
- runs: judge.runs + drawn.length
311
- });
312
- const drawBatch = (judge, draw, batch) => withDraws(judge, draw.more(sizeOf(judge, batch)));
313
- const sizeOf = (judge, batch) => Math.min(batch, DRAW_CAP - judge.runs);
314
- const grown = (batch) => Math.min(batch * 2, BATCH_CAP);
315
- const keepDrawing = (judge) => openLabels(judge).length > 0 && judge.runs < DRAW_CAP;
316
- const toppedUp = (judge, draw, batch) => keepDrawing(judge) ? toppedUp(drawBatch(judge, draw, batch), draw, grown(batch)) : judge;
317
- const reportOf$1 = (judge) => failedEntries(judge).length === 0 ? void 0 : reportFailures(judge);
318
- /**
319
- * Runs R14's sequential draw: tops up the undecided classes until every class is decided or the draw cap
320
- * is reached, then reports the confidently under-covered classes, naming each label and its observed share.
321
- *
322
- * @internal
323
- */
324
- const judgeCoverage = (options) => reportOf$1(toppedUp(judgeOf(options.classes, options.runs), options.draw, FIRST_BATCH));
325
- //#endregion
326
- //#region src/internal/property/error.schema.ts
327
- /**
328
- * Thrown at file end when no property in the file refuted a subject's constant impostor (R12).
329
- *
330
- * @internal
331
- */
332
- var VacuousProperty = class extends Schema$1.TaggedError()("VacuousProperty", { detail: Schema$1.String }) {
333
- get message() {
334
- return this.detail;
335
- }
336
- };
337
- /**
338
- * Thrown when a law kind is given a record `of`, which cannot be spread into its subject.
339
- *
340
- * @internal
341
- */
342
- var NonTupleOf = class extends Schema$1.TaggedError()("NonTupleOf", { detail: Schema$1.String }) {
343
- get message() {
344
- return this.detail;
345
- }
346
- };
347
- //#endregion
348
- //#region src/internal/property/impostor.ts
349
- const SUBJECT_KEY = "the subject";
350
- const NO_VALUE = "";
351
- const isCallable = (value) => typeof value === "function";
352
- const isCallableSubject = (target) => typeof target === "function";
353
- const describeValue = (value) => isCallable(value) ? "a function" : String(value);
354
- const describeEntry = (key, value) => `${key} always returned ${describeValue(value)}`;
355
- const emptyState = () => ({ outputs: /* @__PURE__ */ new Map() });
356
- const frozenAt = (state, key, compute) => state.outputs.has(key) ? state.outputs.get(key) : remember(state, key, compute());
357
- const remember = (state, key, value) => {
358
- state.outputs.set(key, value);
359
- return value;
360
- };
361
- const callOutput = (state, fn, thisArg, args) => frozenAt(state, SUBJECT_KEY, () => Reflect.apply(fn, thisArg, args));
362
- const memberWrapper = (state, key, fn) => (...values) => frozenAt(state, key, () => Reflect.apply(fn, void 0, values));
363
- const memberWrapperOf = (state, fn, key) => fn === void 0 ? void 0 : memberWrapper(state, key, fn);
364
- const appliedImpostor = (state, target, thisArg, args) => isCallableSubject(target) ? callOutput(state, target, thisArg, args) : void 0;
365
- const memberImpostor = (state, target, key) => memberWrapperOf(state, target[key], key);
366
- const readImpostor = (state, target, property) => isCallableSubject(target) ? Reflect.get(target, property, target) : memberRead(state, target, property);
367
- const memberRead = (state, target, property) => typeof property === "string" ? memberImpostor(state, target, property) : void 0;
368
- const impostorHandler = (state) => ({
369
- apply: (target, thisArg, args) => appliedImpostor(state, target, thisArg, args),
370
- get: (target, property) => readImpostor(state, target, property)
371
- });
372
- const describeFrozen = (state) => [...state.outputs].map(([key, value]) => describeEntry(key, value)).join("; ");
373
- /**
374
- * A constant impostor of `subject`: a function returns its first output forever, and so does each member
375
- * of a record.
376
- *
377
- * @internal
378
- */
379
- const impostorOf = (subject) => {
380
- const state = emptyState();
381
- return {
382
- impostor: new Proxy(subject, impostorHandler(state)),
383
- frozen: () => describeFrozen(state)
384
- };
385
- };
386
- /** @internal */
387
- const subjectLabel = (subject) => isCallableSubject(subject) ? `a function of ${String(subject.length)} argument(s)` : `a record of ${Object.keys(subject).sort().join(", ")}`;
388
- const judgeFor = (subject) => ({
389
- label: subjectLabel(subject),
390
- properties: [],
391
- refuters: [],
392
- frozen: NO_VALUE,
393
- runs: 0
394
- });
395
- const trackProperty = (judge, property) => judge.properties.includes(property) ? judge : {
396
- ...judge,
397
- properties: [...judge.properties, property]
398
- };
399
- const trackRefuter = (judge, property) => judge.refuters.includes(property) ? judge : {
400
- ...judge,
401
- refuters: [...judge.refuters, property]
402
- };
403
- const trackVerdict = (judge, property, verdict) => {
404
- const named = trackProperty(judge, property);
405
- return verdict.refuted ? trackRefuter(named, property) : {
406
- ...named,
407
- frozen: verdict.frozen,
408
- runs: verdict.runs
409
- };
410
- };
411
- const recordJudge = (state, subject, property, verdict) => {
412
- const prior = state.judges.get(subject) ?? judgeFor(subject);
413
- state.judges.set(subject, trackVerdict(prior, property, verdict));
414
- };
415
- const isVacuous = (judge) => judge.refuters.length === 0;
416
- const describeVacuous = (judge) => `${judge.properties.join(", ")}: no property in this file refuted the constant impostor of this subject (${judge.label}), so nothing here pins it down. It held for ${judge.runs} run(s) against a fake that ${judge.frozen === NO_VALUE ? "was never called" : judge.frozen}, whatever the input. Laws that only relate outputs to each other (additivity, idempotence, commutativity, round trips through the subject) hold for such constants. Pin the output to the input: compare against an independent model (\`subject(x)\` equals a straightforward reimplementation), or conjoin a base case (\`subject([one])\` equals its known value). Also check that the body calls the \`subject\` it was given, not the imported implementation.`;
417
- const finaliseJudges = (state, onProblems) => {
418
- const vacuous = [...state.judges.values()].filter(isVacuous).map(describeVacuous);
419
- if (vacuous.length === 0) return;
420
- throw onProblems(vacuous.join("\n\n"));
421
- };
422
- const ledgerOf = (state, onProblems) => ({
423
- record: (subject, property, verdict) => recordJudge(state, subject, property, verdict),
424
- finalise: () => finaliseJudges(state, onProblems)
425
- });
426
- /**
427
- * Collects the impostor verdicts of every property in the file and decides, at file end, which subjects
428
- * nothing refuted.
429
- *
430
- * @internal
431
- */
432
- const makeFileLedger = (onProblems) => ledgerOf({ judges: /* @__PURE__ */ new Map() }, onProblems);
433
- //#endregion
434
- //#region src/internal/property/kinds.ts
435
- /**
436
- * @internal The R13 law kinds: `model`, `metamorphic`, `roundTrip` and `invariant` are sugar over the
437
- * gated property (R11/R12); `idempotent` and `deterministic` are declared exempt — they skip the impostor
438
- * gate, are reported as exempt, and are the only exemptions.
439
- *
440
- * Each kind owns the comparison, so there is nowhere to write `return true`: the author hands over
441
- * functions, and the kind relates their outputs with Effect's structural `Equal`.
442
- */
443
- /**
444
- * The generated values as the argument list a law subject is called with.
445
- *
446
- * @internal
447
- */
448
- const spreadValues = (values) => Array.isArray(values) ? values : refuseRecordOf();
449
- const refuseRecordOf = () => {
450
- throw new NonTupleOf({ detail: "a law kind spreads the generated values into its subject, so `of` must be a tuple; a record `of` holds functions of the record, which only it.prop reaches" });
451
- };
452
- const call = (subject, values) => subject(...spreadValues(values));
453
- const equalOutputs = (original, transformed) => Equal.equals(original, transformed);
454
- /** @internal */
455
- const modelHolds = (oracle) => (subject, values) => Equal.equals(call(subject, values), call(oracle, values));
456
- /** @internal */
457
- const metamorphicHolds = (relation) => (subject, values) => (relation.relate ?? equalOutputs)(call(subject, values), call(subject, relation.transform(...spreadValues(values))));
458
- /** @internal */
459
- const roundTripHolds = (decode) => (subject, values) => Equal.equals(decode(call(subject, values)), values);
460
- /** @internal */
461
- const invariantHolds = (holds) => (subject, values) => holds(call(subject, values), ...spreadValues(values));
462
- const firstOf = (values) => values[0];
463
- const sameTwice = (subject, value) => value === void 0 ? false : Equal.equals(subject(subject(value)), subject(value));
464
- /** @internal */
465
- const idempotentHolds = () => (subject, values) => sameTwice(subject, firstOf(spreadValues(values)));
466
- /** @internal */
467
- const deterministicHolds = () => (subject, values) => Equal.equals(call(subject, values), call(subject, values));
468
- //#endregion
469
- //#region src/internal/property/engine.ts
470
- /**
471
- * @internal The lawful property engine (R11-R15): a property names the function under test, takes a budget
472
- * that merges its own fields over the configured default over `runs: 100`, accepts only a literal boolean
473
- * verdict (or an `Effect` of one), judges coverage classes with QuickCheck's sequential test, and defers the
474
- * constant-impostor verdict to the end of the file.
475
- *
476
- * `of` accepts what upstream accepts — a tuple or record of Schemas or Arbitraries (KTD10) — and `holds`
477
- * receives the generated values exactly as drawn, typed by the gens that produced them.
478
- */
479
- const REWRITE = "it.prop(name, { of, subject, runs }, holds)";
480
- const DEFAULT_RUNS = 100;
481
- const isPositiveInteger = (runs) => Number.isInteger(runs) && runs > 0;
482
- const describeRuns = (runs) => Number.isInteger(runs) ? `${String(runs)}` : "a non-integer";
483
- const invalidPropertyBudget = (name, runs) => `${name}: pass a positive integer \`runs\`; received ${describeRuns(runs)}. Write ${REWRITE}.`;
484
- const invalidProvidedBudget = (runs) => `the configured property budget (${checkDefaultsKey}) supplies \`runs\`: ${describeRuns(runs)}; it must be a positive integer.`;
485
- const requirePositiveRuns = (message, runs) => {
486
- if (isInvalidRuns(runs)) throw new InvalidBudget({ detail: message(runs) });
487
- };
488
- const isInvalidRuns = (runs) => runs !== void 0 && isPositiveInteger(runs) === false;
489
- /** The property's own explicit fields, a property-level `runs` outranking the same field in `arbitrary`. */
490
- const explicitOptions = (spec) => spec.runs === void 0 ? { ...spec.arbitrary } : {
491
- ...spec.arbitrary,
492
- runs: spec.runs
493
- };
494
- const mergedOptions = (provided, explicit) => ({
495
- runs: DEFAULT_RUNS,
496
- ...provided,
497
- ...explicit
498
- });
499
- const resolvedRuns = (options) => options.runs ?? DEFAULT_RUNS;
500
- /**
501
- * The effective check options: the property's own fields over the configured default over `runs: 100`,
502
- * merged field by field so a property that sets only `runs` still inherits the configured size and caps.
503
- */
504
- const resolveBudget = (name, spec, provided) => {
505
- const explicit = explicitOptions(spec);
506
- requirePositiveRuns((runs) => invalidPropertyBudget(name, runs), explicit.runs);
507
- requirePositiveRuns(invalidProvidedBudget, provided?.runs);
508
- const options = mergedOptions(provided, explicit);
509
- return {
510
- runs: resolvedRuns(options),
511
- options
512
- };
513
- };
514
- const toArbitrary = (input) => Schema$1.isSchema(input) ? Arbitrary.schema(input) : input;
515
- const assignArbitrary = (built, of, key) => {
516
- const input = of[key];
517
- if (input !== void 0) built[key] = toArbitrary(input);
518
- };
519
- const recordArbitraries = (of) => {
520
- const built = {};
521
- for (const key of Object.keys(of)) assignArbitrary(built, of, key);
522
- return built;
523
- };
524
- const isTupleOf = (of) => Array.isArray(of);
525
- function arbitraryOf(of) {
526
- return isTupleOf(of) ? Arbitrary.all(of.map(toArbitrary)) : Arbitrary.all(recordArbitraries(of));
527
- }
528
- const minimumOf = (entry) => entry[1];
529
- const seedClasses = (entries) => {
530
- const classes = /* @__PURE__ */ new Map();
531
- for (const [label, entry] of entries) classes.set(label, {
532
- hits: 0,
533
- minimum: minimumOf(entry)
534
- });
535
- return classes;
536
- };
537
- const satisfiedLabels = (entries, values) => entries.filter(([, entry]) => entry[0](...spreadValues(values))).map(([label]) => label);
538
- const observeRun = (classes, counter, entries, values) => {
539
- counter.runs = counter.runs + 1;
540
- for (const label of satisfiedLabels(entries, values)) countHit(classes, label);
541
- };
542
- const drawFor = (entries, arbitrary) => ({ more: (count) => Effect.runSync(Arbitrary.sampleEffect(arbitrary, { count }).pipe(Effect.orDie)).map((values) => satisfiedLabels(entries, values)) });
543
- const noCoverage = () => ({
544
- observe: () => void 0,
545
- judge: () => void 0
546
- });
547
- const liveCoverage = (cover, arbitrary) => {
548
- const entries = Object.entries(cover);
549
- const classes = seedClasses(entries);
550
- const counter = { runs: 0 };
551
- return {
552
- observe: (values) => observeRun(classes, counter, entries, values),
553
- judge: () => judgeCoverage({
554
- classes: Object.fromEntries(classes),
555
- runs: counter.runs,
556
- draw: drawFor(entries, arbitrary)
557
- })
558
- };
559
- };
560
- const makeCoverage = (cover, arbitrary) => cover === void 0 ? noCoverage() : liveCoverage(cover, arbitrary);
561
- const describeVerdict = (verdict) => Effect.isEffect(verdict) ? "an Effect, which this lane never runs" : `a ${typeof verdict}`;
562
- const nonBoolean = (verdict, violations) => {
563
- violations.add(describeVerdict(verdict));
564
- return true;
565
- };
566
- const literalVerdict = (verdict, violations) => typeof verdict === "boolean" ? verdict : nonBoolean(verdict, violations);
567
- const effectVerdict = (lane, verdict, violations) => lane === "sync" ? Effect.succeed(nonBoolean(verdict, violations)) : Effect.map(verdict, (value) => literalVerdict(value, violations));
568
- const verdictFor = (run, values, violations) => {
569
- run.observe(values);
570
- const verdict = run.holds(run.subject, values);
571
- return Effect.isEffect(verdict) ? effectVerdict(run.lane, verdict, violations) : Effect.succeed(literalVerdict(verdict, violations));
572
- };
573
- const tolerateInterruption = (cause) => Cause.hasInterrupts(cause) ? Effect.interrupt : Effect.fail(cause);
574
- const guardedVerdict = (run, violations) => (values) => Effect.catchCause(Effect.suspend(() => verdictFor(run, values, violations)), tolerateInterruption);
575
- const violationMessage = (name, violations) => `${name}: the property returned no boolean; it returned ${violations.join(", ")}. A property must return a literal boolean verdict — asserting with expect(), or returning an Option, Result or object, is not a verdict; write ${REWRITE}.`;
576
- const runCheck = (run) => {
577
- const violations = /* @__PURE__ */ new Set();
578
- return Arbitrary.checkEffect(run.arbitrary, guardedVerdict(run, violations), run.options).pipe(Effect.map((result) => ({
579
- violations,
580
- result
581
- })));
582
- };
583
- const reportOf = (checked) => Arbitrary.formatCheckFailure(checked.result);
584
- const violationOf = (name, checked) => checked.violations.size === 0 ? void 0 : violationMessage(name, [...checked.violations]);
585
- const isRefuted = (checked) => checked.violations.size > 0 || reportOf(checked) !== void 0;
586
- const dieViolation = (detail) => Effect.die(new NonBooleanVerdict({ detail }));
587
- const dieReported = (name, report) => Effect.die(/* @__PURE__ */ new Error(`${name}: the property was falsified. ${report}`));
588
- const dieUncovered = (failure) => Effect.die(new CoverageBelowMinimum({ message: failure }));
589
- const impostorHolds = (holds, impostor) => (_subject, values) => holds(impostor.impostor, values);
590
- const recordImpostor = (run, budget, impostor, checked) => {
591
- const verdict = {
592
- refuted: isRefuted(checked),
593
- frozen: impostor.frozen(),
594
- runs: budget.runs
595
- };
596
- gatedLedger.record(run.subject, run.name, verdict);
597
- };
598
- const impostorRun = (run, budget) => {
599
- const impostor = impostorOf(run.subject);
600
- const retry = {
601
- ...run,
602
- holds: impostorHolds(run.holds, impostor),
603
- observe: noObserve
604
- };
605
- return runCheck(retry).pipe(Effect.map((checked) => recordImpostor(retry, budget, impostor, checked)));
606
- };
607
- const noObserve = () => void 0;
608
- const gateRun = (run, budget, gate) => gate ? impostorRun(run, budget) : Effect.void;
609
- const finishPassed = (run, budget, coverage, gate) => {
610
- const failure = coverage.judge();
611
- return failure === void 0 ? gateRun(run, budget, gate) : dieUncovered(`${run.name}: ${failure}`);
612
- };
613
- const settleReport = (run, budget, coverage, gate, report) => report === void 0 ? finishPassed(run, budget, coverage, gate) : dieReported(run.name, report);
614
- const settle = (registration, run, budget, coverage, checked) => {
615
- const violation = violationOf(registration.name, checked);
616
- return violation === void 0 ? settleReport(run, budget, coverage, registration.gate, reportOf(checked)) : dieViolation(violation);
617
- };
618
- const checkOf = (registration, arbitrary, budget, observe) => ({
619
- name: registration.name,
620
- lane: registration.lane,
621
- subject: registration.spec.subject,
622
- holds: registration.holds,
623
- arbitrary,
624
- observe,
625
- options: budget.options
626
- });
627
- const program = (registration) => Effect.gen(function* () {
628
- const budget = resolveBudget(registration.name, registration.spec, providedCheckDefaults());
629
- const arbitrary = arbitraryOf(registration.spec.of);
630
- const coverage = makeCoverage(registration.spec.cover, arbitrary);
631
- const run = checkOf(registration, arbitrary, budget, coverage.observe);
632
- const checked = yield* runCheck(run);
633
- yield* settle(registration, run, budget, coverage, checked);
634
- });
635
- const programOf = (registration) => () => registration.runtime.provide(program(registration));
636
- const selfModelMessage = (name) => `${name}: the model is the subject itself; a model law must compare against an independent oracle.`;
637
- const gatedLedger = makeFileLedger((message) => new VacuousProperty({ detail: message }));
638
- V.afterAll(() => {
639
- gatedLedger.finalise();
640
- });
641
- const registeredName = (name, kind, exempt) => exempt ? `${name} [exempt: ${kind}]` : name;
642
- const isSelfModel = (subject, oracle) => Object.is(subject, oracle);
643
- /**
644
- * The property surface over one runtime: `prop`, `effectProp`, and the R13 law kinds routed through the gate.
645
- *
646
- * @internal
647
- */
648
- const makeProperty = (runtime) => {
649
- const body = (name, spec, holds, lane, gate) => {
650
- const registration = {
651
- runtime,
652
- name,
653
- spec,
654
- holds,
655
- lane,
656
- gate
657
- };
658
- runtime.register(name, programOf(registration));
659
- };
660
- const refuseSelfModel = (name) => {
661
- runtime.register(name, () => Effect.die(new VacuousProperty({ detail: selfModelMessage(name) })));
662
- };
663
- const gated = (name, spec, holds, kind, exempt) => body(registeredName(name, kind, exempt), spec, holds, "sync", exempt === false);
664
- const model = (name, spec, oracle) => isSelfModel(spec.subject, oracle) ? refuseSelfModel(name) : gated(name, spec, modelHolds(oracle), "model", false);
665
- const idempotent = (name, spec) => gated(name, spec, idempotentHolds(), "idempotent", true);
666
- const deterministic = (name, spec) => gated(name, spec, deterministicHolds(), "deterministic", true);
667
- return {
668
- prop: (name, spec, holds) => body(name, spec, holds, "sync", true),
669
- effectProp: (name, spec, holds) => body(name, spec, holds, "effect", true),
670
- law: {
671
- model,
672
- metamorphic: (name, spec, relation) => gated(name, spec, metamorphicHolds(relation), "metamorphic", false),
673
- roundTrip: (name, spec, decode) => gated(name, spec, roundTripHolds(decode), "roundTrip", false),
674
- invariant: (name, spec, holds) => gated(name, spec, invariantHolds(holds), "invariant", false),
675
- idempotent,
676
- deterministic
677
- }
678
- };
679
- };
680
- //#endregion
681
- //#region src/internal/step-boundary.ts
682
- /** @internal */
683
- const makeStopState = (failed) => ({
684
- stopped: /* @__PURE__ */ new WeakSet(),
685
- failed
686
- });
687
- const alreadyStopped = (state, fiber) => state.stopped.has(fiber);
688
- /** @internal */
689
- const stopChecker = Function.dual(2, (state, fiber) => alreadyStopped(state, fiber) === false && state.failed());
690
- const markStopped = (state, fiber) => {
691
- state.stopped.add(fiber);
692
- };
693
- /** @internal */
694
- const stopAfterFailedCheck = Function.dual(2, (scheduler, state) => ({
695
- executionMode: scheduler.executionMode,
696
- makeDispatcher: () => scheduler.makeDispatcher(),
697
- shouldYield: (fiber) => checkFiber(state, scheduler, fiber)
698
- }));
699
- const checkFiber = (state, scheduler, fiber) => {
700
- const steppable = fiber;
701
- if (stopChecker(state, steppable) === false) return scheduler.shouldYield(fiber);
702
- markStopped(state, steppable);
703
- steppable.interruptUnsafe(steppable.id);
704
- return true;
705
- };
706
- //#endregion
707
- //#region src/internal/test-context.ts
708
- /** @internal */
709
- const vitestTestContextKey$1 = "@systemfsoftware/vitest/TestContext";
710
- /**
711
- * The running Vitest test, or `null` outside a run; provided on every test the fork runs.
712
- *
713
- * @internal
714
- */
715
- const VitestTestContext$1 = Context.Reference(vitestTestContextKey$1, { defaultValue: () => null });
716
- //#endregion
717
99
  //#region src/internal/virtual-time.ts
718
100
  const roundNanos = (millis) => BigInt(Math.round(millis * 1e6));
719
101
  const byWakeOrder = (first, second) => {
@@ -885,6 +267,18 @@ const virtualClockOf = (runtime) => {
885
267
  };
886
268
  //#endregion
887
269
  //#region src/internal/runner.ts
270
+ /**
271
+ * The fork's runner: the test lanes, the layer blocks, the property lanes and the run pipeline.
272
+ *
273
+ * A test body is a generator the driver steps (KTD1), so `expect` is the body's own parameter and every check is
274
+ * yielded. Everything #512 promised holds unchanged: a fresh build of a test's layers, virtual time that advances
275
+ * when the fibers are idle, concurrent and shuffled blocks, a declared-shared block as the only sharing, a second
276
+ * run on a fresh build that reports leaked state, and Effect `Equal` in `toEqual`.
277
+ *
278
+ * What is a refusal here: a body that is not a generator, and the habit lanes `it.effect`, `it.scoped` and
279
+ * `it.scopedLive` (R2, R9). Registration marks every task this file adds (KTD8), so the guard in
280
+ * `@systemfsoftware/vitest/guard` can tell the fork's tests from a test registered by Vitest itself.
281
+ */
888
282
  const getCurrentSuite = V.TestRunner.getCurrentSuite;
889
283
  /** @internal */
890
284
  const addEqualityTesters$1 = () => {
@@ -896,80 +290,73 @@ const givenOptions = (timeout) => timeout ?? {};
896
290
  const testOptions = (timeout) => typeof timeout === "number" ? numbered(timeout) : givenOptions(timeout);
897
291
  /** @internal */
898
292
  const hookTimeout = (timeout) => timeout === void 0 ? void 0 : Duration.toMillis(Duration.fromInputUnsafe(timeout));
293
+ const isName = (first) => typeof first === "string";
294
+ const byName = (args) => isName(args[0]);
295
+ const byLayer = (args) => Layer.isLayer(args[0]);
296
+ const byEffect = (args) => Effect.isEffect(args[0]);
899
297
  /**
900
- * The positional form is refused at run time with the R15 rewrite; the public type already refuses it.
298
+ * The positional property form is refused at run time with the R15 rewrite; the public type refuses it first
299
+ * because a `PropertySpec` is an object and the positional shape is not.
901
300
  */
301
+ const refusePositionalProp = "✗ it.prop(name, [arbitraries], predicate) is removed. Name the function under test and pass a budget: it.prop(name, { of, subject, runs }, holds).";
302
+ const isAbsent = (spec) => spec === null || spec === void 0;
303
+ const hasNoOf = (spec) => Object.hasOwn(spec, "of") === false;
304
+ const lacksInputs = (spec) => isAbsent(spec) || hasNoOf(spec);
902
305
  const refusePositional = (spec) => {
903
306
  if (lacksInputs(spec)) throw new Slop({ detail: refusePositionalProp });
904
307
  };
905
- const lacksInputs = (spec) => isAbsent(spec) || hasNoOf(spec);
906
- const isAbsent = (spec) => spec === null || spec === void 0;
907
- const hasNoOf = (spec) => Object.hasOwn(spec, "of") === false;
908
- const stopCheckOf = (ctx) => ctx === void 0 ? void 0 : failedAfter(ctx, errorCount(ctx));
909
- const neverStop = () => void 0;
910
308
  /** @internal */
911
309
  const realTime = {
912
310
  runtime: void 0,
913
- owned: false,
914
- stop: stopCheckOf
311
+ owned: false
915
312
  };
916
- /** @internal */
313
+ /**
314
+ * A run with its own virtual clock: the clock only ever advances when the run's fibers are idle.
315
+ *
316
+ * @internal
317
+ */
917
318
  const perRun = () => ({
918
319
  runtime: makeVirtualRuntime(),
919
- owned: true,
920
- stop: stopCheckOf
320
+ owned: true
921
321
  });
922
- const propertyEnv = {
923
- runtime: void 0,
924
- owned: false,
925
- stop: neverStop
926
- };
927
- const logErrors = (cause) => Effect.forEach(Cause.prettyErrors(cause), (error) => Effect.logError(error), { discard: true });
928
- const failedAfter = (ctx, before) => () => errorCount(ctx) > before;
322
+ const propertyEnv = realTime;
323
+ const payloadOf = (reason) => Cause.isFailReason(reason) ? reason.error : reasonDefectOf(reason);
324
+ const reasonDefectOf = (reason) => Cause.isDieReason(reason) ? reason.defect : void 0;
325
+ const isLoggable = (thrown) => (reason) => !Cause.isInterruptReason(reason) && payloadOf(reason) !== thrown;
326
+ const unthrownOf = (cause) => cause.pipe(Cause.squash, isLoggable, (loggable) => Cause.fromReasons(cause.reasons.filter(loggable)));
327
+ const logUnthrownErrors = (cause) => cause.pipe(unthrownOf, Cause.prettyErrors, Effect.forEach((error) => Effect.logError(error), { discard: true }));
929
328
  const signalOf = (ctx) => ctx?.signal;
930
329
  const signalOnly = (signal) => signal === void 0 ? {} : { signal };
931
- const withScheduler = (signal, scheduler) => ({
932
- ...signalOnly(signal),
933
- scheduler
330
+ const schedulerOf = (env) => env.runtime === void 0 ? {} : { scheduler: env.runtime.scheduler };
331
+ const runOptions = (ctx, env) => ({
332
+ ...signalOnly(signalOf(ctx)),
333
+ ...schedulerOf(env)
934
334
  });
935
- const runOptions = (ctx, scheduler) => scheduler === void 0 ? signalOnly(signalOf(ctx)) : withScheduler(signalOf(ctx), scheduler);
936
- const baseScheduler = (env) => env.runtime?.scheduler;
937
- const stopScheduler = (base, failedCheck) => stopAfterFailedCheck(base ?? new Scheduler.MixedScheduler(), makeStopState(failedCheck));
938
- const pickScheduler = (env, failedCheck) => failedCheck === void 0 ? baseScheduler(env) : stopScheduler(baseScheduler(env), failedCheck);
939
- const runPromise = (effect, ctx, env = realTime) => {
940
- const failedCheck = env.stop(ctx);
941
- return runExit(effect, ctx, env, failedCheck).then(reportExit(failedCheck));
942
- };
943
- const runExit = (effect, ctx, env, failedCheck) => {
944
- const program = wrapOwned(effect, ownedRuntime(env));
945
- return Effect.runPromiseExit(program, runOptions(ctx, pickScheduler(env, failedCheck)));
946
- };
947
335
  const ownedRuntime = (env) => env.owned === false ? void 0 : env.runtime;
948
336
  const observeOf = (runtime) => runtime === void 0 ? Effect.void : runtime.observe;
949
337
  const endOf = (runtime) => runtime === void 0 ? Effect.void : runtime.end;
950
338
  const wrapOwned = (effect, runtime) => observeOf(runtime).pipe(Effect.andThen(effect.pipe(Effect.ensuring(endOf(runtime)))));
951
- const stoppedSettled = () => Promise.resolve(void 0);
952
- const stoppedExit = (cause) => Cause.hasInterruptsOnly(cause) ? stoppedSettled() : failExit(cause);
953
- const stoppedChecked = (cause, failed) => failed ? stoppedExit(cause) : failExit(cause);
954
- const failedOrThrow = (cause, failedCheck) => failedCheck === void 0 ? failExit(cause) : stoppedChecked(cause, failedCheck());
955
- const reportExit = (failedCheck) => (exit) => Exit.isSuccess(exit) ? Promise.resolve(exit.value) : failedOrThrow(exit.cause, failedCheck);
339
+ const runExit = (effect, ctx, env) => Effect.runPromiseExit(wrapOwned(effect, ownedRuntime(env)), runOptions(ctx, env));
956
340
  /**
957
341
  * A body that needs a service nothing provides dies with Effect's own `Service not found`. That is a
958
- * test-authoring error — the layers were never wired — so it is reported as the unprovided rewrite (R9)
959
- * instead of as a raw defect. Failures an `owned` region is responsible for are recovered by their owner
960
- * and never reach here.
342
+ * test-authoring error — the layers were never wired — so it is reported as the unprovided rewrite (R9) instead
343
+ * of as a raw defect.
961
344
  */
962
345
  const isUnprovided = (error) => error instanceof Error && error.message.startsWith("Service not found");
963
- const failExit = (cause) => logErrors(cause).pipe(Effect.runPromise).then(() => rethrowSquashed(cause));
346
+ const refuseUnprovided = (failure) => {
347
+ if (isUnprovided(failure)) throw new Slop({ detail: unprovidedText });
348
+ };
964
349
  const rethrowSquashed = (cause) => {
965
350
  const error = Cause.squash(cause);
966
- if (isUnprovided(error)) throw new Slop({ detail: refuseUnprovided });
351
+ refuseUnprovided(error);
967
352
  throw error;
968
353
  };
969
- const runTest = (ctx, env = realTime) => (effect) => {
970
- const promise = runPromise(effect, ctx, env);
971
- if (ctx !== void 0) trackAbort(ctx, promise);
972
- return promise;
354
+ const failExit = (cause) => logUnthrownErrors(cause).pipe(Effect.runPromise).then(() => rethrowSquashed(cause));
355
+ const reportExit = (exit) => Exit.isSuccess(exit) ? Promise.resolve(exit.value) : failExit(exit.cause);
356
+ const runPromise = (effect, ctx, env = realTime) => runExit(effect, ctx, env).then(reportExit);
357
+ const noop = () => {};
358
+ const detach = (ctx, onAbort) => {
359
+ ctx.signal.removeEventListener("abort", onAbort);
973
360
  };
974
361
  const trackAbort = (ctx, promise) => {
975
362
  const onAbort = () => {
@@ -979,168 +366,126 @@ const trackAbort = (ctx, promise) => {
979
366
  promise.then(() => detach(ctx, onAbort), () => detach(ctx, onAbort));
980
367
  if (ctx.signal.aborted) onAbort();
981
368
  };
982
- const detach = (ctx, onAbort) => {
983
- ctx.signal.removeEventListener("abort", onAbort);
369
+ const rethrowRendered = (failure) => {
370
+ if (isFailureRecordError(failure)) throw failure;
371
+ };
372
+ const throwRecorded = (cause, recorder, ctx) => {
373
+ const failure = Cause.squash(cause);
374
+ rethrowRendered(failure);
375
+ refuseUnprovided(failure);
376
+ return throwFailureRecord({
377
+ failure,
378
+ spans: recorder.spans,
379
+ identity: testIdentityOf(ctx.task),
380
+ replay: replayOfFailure(failure),
381
+ root: providedRoot()
382
+ });
383
+ };
384
+ const failRecorded = (cause, recorder, ctx) => logUnthrownErrors(cause).pipe(Effect.runPromise).then(() => throwRecorded(cause, recorder, ctx));
385
+ const reportRecorded = (exit, recorder, ctx) => Exit.isSuccess(exit) ? Promise.resolve(exit.value) : failRecorded(exit.cause, recorder, ctx);
386
+ /**
387
+ * One test's run, recorded: its own in-memory tracer collects the spans the record is rendered from, and a failure
388
+ * leaves as the rendered record instead of a raw Cause (KTD2, R2, R7, R8).
389
+ */
390
+ const runRecorded = (effect, ctx, env) => {
391
+ const recorder = createSpanRecorder();
392
+ const promise = runExit(effect.pipe(Effect.withTracer(recorder.tracer)), ctx, env).then((exit) => reportRecorded(exit, recorder, ctx));
393
+ trackAbort(ctx, promise);
394
+ return promise;
984
395
  };
985
- const noop = () => {};
986
396
  const clockLayerOf = (env) => env.runtime === void 0 ? TestClock.layer() : virtualClockLayer(env.runtime);
987
397
  /** @internal */
988
398
  const testEnv = (env) => Layer.mergeAll(TestConsole.layer, clockLayerOf(env));
989
399
  /**
990
- * The run a test's fibers execute in: the test's own context as `Running`, and the same context under the
991
- * fork's `VitestTestContext` for every library built on the fork.
992
- */
993
- const bindRun = (effect, ctx, shadow) => effect.pipe(Effect.provideService(Running, {
994
- ctx,
995
- shadow
996
- }), Effect.provideService(VitestTestContext$1, ctx));
997
- const isObject = (output) => typeof output === "object" && output !== null;
998
- const isThenable = (value) => "then" in value ? typeof value.then === "function" : false;
999
- const isPromiseLike = (output) => isObject(output) && isThenable(output);
1000
- const refusePromiseBody = () => {
1001
- throw new Slop({ detail: refuseAsync });
400
+ * The run a test's fibers execute in: the test's own context under the fork's `VitestTestContext`, so a library
401
+ * built on the fork reads the same context the runner does.
402
+ */
403
+ const bindRun = (effect, ctx) => effect.pipe(Effect.provideService(VitestTestContext$1, ctx));
404
+ const refuseBody = (text) => {
405
+ throw refusalOf$1(text);
406
+ };
407
+ /** The constructor names of the function kinds that hand back a promise instead of a generator. */
408
+ const AWAITED_KINDS = {
409
+ AsyncFunction: true,
410
+ AsyncGeneratorFunction: true
411
+ };
412
+ /** The constructor name of the one function kind the driver steps: a generator hands its steps over. */
413
+ const GENERATOR_KIND = "GeneratorFunction";
414
+ const kindOf = (body) => body.constructor.name;
415
+ const isGeneratorBody = (body) => kindOf(body) === GENERATOR_KIND;
416
+ const isSteps = (returned) => "next" in returned && typeof returned.next === "function";
417
+ /** The iterator a generator handed over; only a generator reaches here, so the guard is for the compiler alone. */
418
+ const asSteps = (returned) => isSteps(returned) ? returned : refuseBody(refuseSyncBody);
419
+ /**
420
+ * The refusal a body reaches before it runs (R2). An `async` body hands back a promise and runs outside the test
421
+ * runtime. A plain body can only be told from one by calling it, and nothing in a refused body may run, so it is
422
+ * refused as a sync body, whose rewrite is to pass a generator; the compile channel prints each shape's own text.
423
+ */
424
+ const refusalOf = (body) => AWAITED_KINDS[kindOf(body)] === true ? refuseAsyncBody : refuseSyncBody;
425
+ /** The body's generator, or the refusal for a body the driver cannot step. */
426
+ const stepsOfBody = (body, checks) => isGeneratorBody(body) ? asSteps(body(checks)) : refuseBody(refusalOf(body));
427
+ /** The same for a row body: Vitest's `it.for` order is the row first, then the checks (R9). */
428
+ const stepsOfRow = (row, body, checks) => isGeneratorBody(body) ? asSteps(body(row, checks)) : refuseBody(refusalOf(body));
429
+ const bodyOf = (ledger, body) => () => asSteps(body(checksFor(ledger)));
430
+ /** The body a lane registered, closed over its checks: the generator itself, or the refusal for what it is not. */
431
+ const laneBodyOf = (body) => (checks) => typeof body === "string" ? refuseBody(refuseSyncBody) : stepsOfBody(body, checks);
432
+ /** The row body a lane registered: Vitest's `it.for` order is the row first, then the checks (R9). */
433
+ const rowBodyOf = (row, body) => (checks) => typeof body === "string" ? refuseBody(refuseSyncBody) : stepsOfRow(row, body, checks);
434
+ /** One test's run: a fresh ledger, and the lane's own services around the program the driver built. */
435
+ const runLaned = (ctx, mapEffect, env, body) => {
436
+ const ledger = makeLedger(ctx);
437
+ return runRecorded(mapEffect(bindRun(drive(bodyOf(ledger, body), [], ledger), ctx), env), ctx, env);
1002
438
  };
1003
- const refuseAfter = (promise) => {
1004
- Promise.resolve(promise).catch(noop);
1005
- return refusePromiseBody();
439
+ const outcomeOf = (promise) => promise.then(() => void 0, (error) => error);
440
+ const isSlop = (error) => Schema.is(Slop)(error);
441
+ /** Every failure the fork itself names: the branded refusals, plus the Schema-tagged families. */
442
+ const otherRefusals = [
443
+ isSlop,
444
+ Schema.is(InvalidBudget),
445
+ Schema.is(NonBooleanVerdict),
446
+ Schema.is(LeakedState)
447
+ ];
448
+ const isRefusal = (error) => isRefusal$1(error) || otherRefusals.some((matches) => matches(error));
449
+ const clipped = (message) => message.length > 400 ? message.slice(0, 400) : message;
450
+ const throwLeaked = (outcome) => {
451
+ throw new LeakedState({ detail: "✗ this test passed, then failed when run again on a fresh build of its services: something keeps state outside its layer, so tests here see each other's changes. The second run failed with: " + clipped(outcome.message) });
1006
452
  };
1007
- const checkPromiseBody = (output) => {
1008
- if (isPromiseLike(output)) refuseAfter(output);
453
+ /**
454
+ * The second run's failure: a diagnosis the fork itself made is rethrown as that diagnosis, because the second
455
+ * run exists to catch leaked state, not to relabel a refusal as a leak. Anything else is the leak.
456
+ */
457
+ const throwSecondRun = (error) => {
458
+ if (isRefusal(error)) throw error;
459
+ return throwLeaked(error);
1009
460
  };
1010
- const outputIsEffect = (output) => Effect.isEffect(output);
1011
- const buildSingleRun = (body, mapOne, env, shadow) => (ctx) => {
1012
- const output = withSyncRun({
1013
- ctx,
1014
- shadow
1015
- }, () => body(ctx));
1016
- checkPromiseBody(output);
1017
- return outputIsEffect(output) ? runBuilt(ctx, env, mapOne, output, shadow) : Promise.resolve(void 0);
461
+ const leakedOrNothing = (error) => {
462
+ if (error !== void 0) throwSecondRun(error);
1018
463
  };
1019
- const runBuilt = (ctx, env, mapOne, output, shadow) => runTest(ctx, env)(mapOne(bindRun(Effect.suspend(() => output), ctx, shadow)));
464
+ const NO_ERRORS = [];
465
+ const presentErrors = (errors) => errors === void 0 ? NO_ERRORS : errors;
466
+ /** Whether a run left no failure behind: only a clean first run is worth running a second time. */
467
+ const isClean = (ctx) => presentErrors(ctx.task.result?.errors).length === 0;
1020
468
  /**
1021
- * Runs a property's program as the test that registered it, under the same binding the Effect lanes use, so a
1022
- * check or an annotation inside `holds` belongs to the test that declared the property. The env is the bare
1023
- * `propertyEnv`: no step boundary interrupts a property, so its `false` verdict still shrinks (R6, U4).
469
+ * Runs a property's program as the test that registered it, under the same context the generator lanes use. The
470
+ * env is the bare `propertyEnv`: a `false` verdict is the property's own shrink path, so nothing interrupts it.
1024
471
  */
1025
- const runProperty = (ctx, program) => buildSingleRun(() => program(), (effect) => effect, propertyEnv, false)(ctx);
472
+ const runProperty = (ctx, program) => runRecorded(bindRun(Effect.suspend(program), ctx), ctx, propertyEnv);
1026
473
  const property = makeProperty({
1027
- register: (name, program) => {
1028
- V.it(name, (ctx) => runProperty(ctx, program));
1029
- },
474
+ register: (name, program) => marked(() => V.it(name, (ctx) => runProperty(ctx, program))),
1030
475
  provide: (effect) => effect
1031
476
  });
1032
- const isName = (first) => typeof first === "string";
1033
- const byName = (args) => isName(args[0]);
1034
- const byLayer = (args) => Layer.isLayer(args[0]);
1035
- const byEffect = (args) => Effect.isEffect(args[0]);
1036
477
  const propImpl = (name, spec, holds) => {
1037
478
  refusePositional(spec);
1038
479
  property.prop(name, spec, holds);
1039
480
  };
1040
481
  /** @internal */
1041
482
  const prop$1 = Function.dual(byName, propImpl);
1042
- const outcomeOf = (promise) => promise.then(() => void 0, (error) => error);
1043
- /**
1044
- * The second run's failure: a diagnosis the fork itself made is rethrown as that diagnosis, because the
1045
- * second run exists to catch leaked state, not to relabel a refusal as a leak (F7). Anything else is the leak.
1046
- */
1047
- const throwSecondRun = (error) => {
1048
- if (isRefusal(error)) throw error;
1049
- return throwLeaked(error);
1050
- };
1051
- const leakedOrNothing = (error) => {
1052
- if (error !== void 0) throwSecondRun(error);
1053
- };
1054
- const makeTesterWith = (mapEffect, it, envFor, rerunnable) => {
1055
- const runOne = (ctx, args, self, shadow) => {
1056
- const env = envFor();
1057
- return buildSingleRun(() => self(...args), (effect) => mapEffect(effect, env), env, shadow)(ctx);
1058
- };
1059
- /**
1060
- * The second run: the same layer build the first run used, built again, with the shadow flag set so its
1061
- * checks throw instead of reporting softly (R5, KTD7).
1062
- */
1063
- const rerun = (ctx, args, self) => outcomeOf(runOne(ctx, args, self, true)).then(leakedOrNothing);
1064
- const run = (ctx, args, self) => outcomeOf(runOne(ctx, args, self, false)).then((error) => afterFirst(ctx, args, self, error));
1065
- const afterFirst = (ctx, args, self, error) => {
1066
- if (error !== void 0) throwAfterCheck(ctx, error);
1067
- gateNoAssertion(ctx);
1068
- return rerunIfClean(ctx, args, self);
1069
- };
1070
- const rerunIfClean = (ctx, args, self) => shouldRerun(ctx) ? rerun(ctx, args, self) : Promise.resolve();
1071
- const shouldRerun = (ctx) => rerunnable && isClean(ctx);
1072
- const registerOn = (registrar, name, self, timeout) => {
1073
- registrar(name, testOptions(timeout), (ctx) => run(ctx, [ctx], self));
1074
- };
1075
- const dualTest = (register) => Function.dual(byName, register);
1076
- const f = dualTest((name, self, timeout) => registerOn(it, name, self, timeout));
1077
- const skip = dualTest((name, self, timeout) => registerOn(it.skip, name, self, timeout));
1078
- const skipIf = (condition) => dualTest((name, self, timeout) => registerOn(it.skipIf(condition), name, self, timeout));
1079
- const runIf = (condition) => dualTest((name, self, timeout) => registerOn(it.runIf(condition), name, self, timeout));
1080
- const only = dualTest((name, self, timeout) => registerOn(it.only, name, self, timeout));
1081
- const each = (cases) => (name, self, timeout) => it.for(cases)(name, testOptions(timeout), (row, ctx) => run(ctx, [row], self));
1082
- const fails = dualTest((name, self, timeout) => registerOn(V.it.fails, name, self, timeout));
1083
- const effectProperty = makeProperty({
1084
- register: (name, program) => {
1085
- it(name, (ctx) => runProperty(ctx, program));
1086
- },
1087
- provide: (effect) => mapEffect(effect, envFor())
1088
- });
1089
- const testerProp = (name, spec, holds) => {
1090
- refusePositional(spec);
1091
- effectProperty.effectProp(name, spec, holds);
1092
- };
1093
- return Object.assign(f, {
1094
- skip,
1095
- skipIf,
1096
- runIf,
1097
- only,
1098
- each,
1099
- fails,
1100
- prop: testerProp
1101
- });
1102
- };
1103
- const makeTester = (mapEffect, it = V.it) => makeTesterWith(mapEffect, it, perRun, true);
1104
- const makeSharedTester = (mapEffect, it, envFor) => makeTesterWith(mapEffect, it, envFor, false);
1105
- const isSlop = (error) => Schema$1.is(Slop)(error);
1106
- /** Every failure the fork itself names, as `Schema.is`: the families a second run must not relabel. */
1107
- const refusalChecks = [
1108
- isSlop,
1109
- Schema$1.is(AfterFailedExpect),
1110
- Schema$1.is(InvalidBudget),
1111
- Schema$1.is(NonBooleanVerdict),
1112
- Schema$1.is(LeakedState)
1113
- ];
1114
- const isRefusal = (error) => refusalChecks.some((matches) => matches(error));
1115
- const throwAfterCheck = (ctx, error) => {
1116
- if (isSlop(error)) throw error;
1117
- return afterFailedCheck(ctx, error);
1118
- };
1119
- const afterFailedCheck = (ctx, error) => {
1120
- if (errorCount(ctx) > 0) throwAfterFailed();
1121
- throw error;
1122
- };
1123
- const throwAfterFailed = () => {
1124
- throw new AfterFailedExpect({ detail: "thrown after a failed expect above, most likely caused by it" });
1125
- };
1126
- /** Whether a run left no failure behind: the second run is only worth doing for a clean first one. */
1127
- const isClean = (ctx) => errorCount(ctx) === 0;
1128
- const silentRun = (ctx) => errorCount(ctx) === 0 && assertionCalls(ctx) === 0;
1129
483
  /**
1130
- * The test's own assertion count. Every check the fork runs inside a test binds to that test's `expect`,
1131
- * so a sibling test's checks are never visible here.
484
+ * The law kinds over the sync property family: the top-level `it.law`.
485
+ *
486
+ * @internal
1132
487
  */
1133
- const assertionCalls = (ctx) => ctx.expect.getState().assertionCalls;
1134
- const gateNoAssertion = (ctx) => {
1135
- if (silentRun(ctx)) throwNoAssertion();
1136
- };
1137
- const throwNoAssertion = () => {
1138
- throw new Slop({ detail: refuseNoAssertion });
1139
- };
1140
- const clipped = (message) => message.length > 400 ? message.slice(0, 400) : message;
1141
- const throwLeaked = (outcome) => {
1142
- throw new LeakedState({ detail: "✗ this test passed, then failed when run again on a fresh build of its services: something keeps state outside its layer, so tests here see each other's changes. The second run failed with: " + clipped(outcome.message) });
1143
- };
488
+ const law$1 = property.law;
1144
489
  const CONCURRENT_BLOCK = {
1145
490
  concurrent: true,
1146
491
  shuffle: true
@@ -1158,25 +503,24 @@ const memoMapIn = (options) => options?.memoMap;
1158
503
  const excludeTestServicesOf = (options) => withDefault(excludedIn(options), false);
1159
504
  const blockEnvOf = (inherited) => withDefault(inherited, {
1160
505
  runtime: makeVirtualRuntime(),
1161
- owned: false,
1162
- stop: stopCheckOf
506
+ owned: false
1163
507
  });
1164
508
  const memoMapOf = (options) => withDefault(memoMapIn(options), Effect.runSync(Layer.makeMemoMap));
1165
509
  const blockHookTimeout = (options) => hookTimeout(timeoutIn(options));
1166
510
  const isSharedBlock = (options) => sharedIn(options) === true;
1167
- /**
1168
- * A named block's suite options: its tests run concurrently and in shuffled order (R4). A declared-shared
1169
- * block is the stateful exception — its tests read each other's writes through one build, so it stays in
1170
- * declaration order and runs alone.
1171
- */
1172
- const describeOptions = (options, shared) => mergeBlockOptions(options, shared ? ALONE_BLOCK : CONCURRENT_BLOCK);
1173
511
  const mergeBlockOptions = (options, fallback) => ({
1174
512
  concurrent: withDefault(concurrencyIn(options), fallback.concurrent),
1175
513
  shuffle: fallback.shuffle
1176
514
  });
1177
515
  /**
1178
- * The block's layer with the test's own services merged in, unless the block excludes them. `Layer`'s output
1179
- * is contravariant, so the merged layer is the *narrower* `Layer<R, E>` the callers provide to a run.
516
+ * A named block's suite options: its tests run concurrently and in shuffled order (R4). A declared-shared block
517
+ * is the stateful exception — its tests read each other's writes through one build, so it stays in declaration
518
+ * order and runs alone.
519
+ */
520
+ const describeOptions = (options, shared) => mergeBlockOptions(options, shared ? ALONE_BLOCK : CONCURRENT_BLOCK);
521
+ /**
522
+ * The block's layer with the test's own services merged in, unless the block excludes them. `Layer`'s output is
523
+ * contravariant, so the merged layer is the *narrower* `Layer<R, E>` the callers provide to a run.
1180
524
  */
1181
525
  const mergeTestEnv = (layer_, env, excludeTestServices) => excludeTestServices ? layer_ : Layer.provideMerge(layer_, env);
1182
526
  const sharedContext = (shared, memoMap, scope) => Layer.buildWithMemoMap(shared, memoMap, scope).pipe(Effect.orDie, Effect.cached, Effect.runSync);
@@ -1200,6 +544,31 @@ const routeBlock = (first, second, anonymous, named) => {
1200
544
  const openNamed = (name, f, named) => {
1201
545
  if (f !== void 0) named(name, f);
1202
546
  };
547
+ /** Runs a registration and marks every test task it added, which is how the guard knows the fork registered it. */
548
+ const marked = (register) => {
549
+ const before = suiteTasks(getCurrentSuite()).length;
550
+ register();
551
+ markAdded(before);
552
+ };
553
+ const markAdded = (before) => {
554
+ for (const task of collectTasks(suiteTasks(getCurrentSuite()).slice(before))) markTask(task);
555
+ };
556
+ const blockTasksOf = () => suiteTasks(getCurrentSuite());
557
+ const suiteTasks = (suite) => suite.tasks;
558
+ const collectTasks = (tasks) => {
559
+ const acc = [];
560
+ for (const task of tasks) collectOne(task, acc);
561
+ return acc;
562
+ };
563
+ const collectNested = (task, acc) => {
564
+ if (task.tasks !== void 0) collectTasks(task.tasks).forEach((nested) => acc.push(nested));
565
+ };
566
+ const collectOne = (task, acc) => {
567
+ if (isRunnableTest(task)) acc.push(task);
568
+ else collectNested(task, acc);
569
+ };
570
+ const notSkipped = (task) => task.mode !== "skip" && task.mode !== "todo";
571
+ const isRunnableTest = (task) => task.type === "test" && notSkipped(task);
1203
572
  const layerFor = (layer_, options, inherited) => isSharedBlock(options) ? sharedBlock(layer_, options, inherited) : freshBlock(layer_, options);
1204
573
  const freshBlock = (layer_, options) => {
1205
574
  const excludeTestServices = excludeTestServicesOf(options);
@@ -1209,13 +578,7 @@ const freshBlock = (layer_, options) => {
1209
578
  excludeTestServices
1210
579
  }, void 0);
1211
580
  const nestedLayer = Function.dual(byLayer, nestedOpen);
1212
- const makeIt = (it) => makeItProxy(it, {
1213
- effect: makeTester((effect, env) => effect.pipe(Effect.scoped, (scoped) => Effect.provide(scoped, Layer.orDie(Layer.fresh(withTestEnv(env))))), it),
1214
- prop: prop$1,
1215
- law: property.law,
1216
- flakyTest: flakyTest$1,
1217
- layer: nestedLayer
1218
- }, true);
581
+ const makeIt = (it) => methodsFor((effect, env) => effect.pipe(Effect.scoped, (scoped) => Effect.provide(scoped, Layer.orDie(Layer.fresh(withTestEnv(env))))), it, perRun, true, nestedLayer);
1219
582
  return blockOpener((f) => f(makeIt(V.it)), (name, f) => {
1220
583
  V.describe(name, describeOptions(options, false), () => f(makeIt(V.it)));
1221
584
  });
@@ -1225,24 +588,15 @@ const nestedBlock = (nested, memoMap) => ({
1225
588
  memoMap: Layer.forkMemoMapUnsafe(memoMap),
1226
589
  shared: true
1227
590
  });
1228
- const sharedOverrides = (shared, contextEffect, memoMap, blockEnv) => {
1229
- const nestedOpen = (nestedLayer, nestedOptions) => layerFor(() => Layer.provideMerge(nestedLayer, shared), nestedBlock(nestedOptions, memoMap), blockEnv);
1230
- const nestedLayer = Function.dual(byLayer, nestedOpen);
1231
- return {
1232
- effect: makeSharedTester((effect) => Effect.flatMap(contextEffect, (context) => effect.pipe(Effect.scoped, (scoped) => Effect.provide(scoped, context))), V.it, () => blockEnv),
1233
- prop: prop$1,
1234
- law: property.law,
1235
- flakyTest: flakyTest$1,
1236
- layer: nestedLayer
1237
- };
1238
- };
1239
591
  const sharedBlock = (layer_, options, inherited) => {
1240
592
  const blockEnv = blockEnvOf(inherited);
1241
593
  const memoMap = memoMapOf(options);
1242
594
  const scope = Scope.makeUnsafe();
1243
595
  const shared = mergeTestEnv(layer_(blockEnv), testEnv(blockEnv), excludeTestServicesOf(options));
1244
596
  const contextEffect = sharedContext(shared, memoMap, scope);
1245
- const makeIt = (it) => makeItProxy(it, sharedOverrides(shared, contextEffect, memoMap, blockEnv), false);
597
+ const nestedOpen = (nestedLayer, nestedOptions) => layerFor(() => Layer.provideMerge(nestedLayer, shared), nestedBlock(nestedOptions, memoMap), blockEnv);
598
+ const nestedLayer = Function.dual(byLayer, nestedOpen);
599
+ const makeIt = (it) => methodsFor((effect) => Effect.flatMap(contextEffect, (context) => effect.pipe(Effect.scoped, (scoped) => Effect.provide(scoped, context))), it, () => blockEnv, false, nestedLayer);
1246
600
  const closeScope = makeScopeCloser(scope);
1247
601
  return blockOpener((f) => sharedAnonymous(f, makeIt, options, blockTasksOf(), blockEnv, contextEffect, closeScope), (name, f) => describeShared(name, f, makeIt, options, contextEffect, closeScope, blockEnv));
1248
602
  };
@@ -1253,8 +607,6 @@ const describeShared = (name, f, makeIt, options, contextEffect, closeScope, blo
1253
607
  return f(makeIt(V.it));
1254
608
  });
1255
609
  };
1256
- const blockTasksOf = () => suiteTasks(getCurrentSuite());
1257
- const suiteTasks = (suite) => suite.tasks;
1258
610
  const sharedAnonymous = (register, makeIt, options, blockTasks, blockEnv, contextEffect, closeScope) => {
1259
611
  const previousTasks = new Set(blockTasks);
1260
612
  register(makeIt(V.it));
@@ -1282,20 +634,6 @@ const sharedBeforeEach = (ctx, blockTaskSet, finish, contextEffect, blockEnv) =>
1282
634
  ctx.onTestFinished(() => finish());
1283
635
  return runPromise(Effect.asVoid(contextEffect), ctx, blockEnv);
1284
636
  };
1285
- const collectTasks = (tasks) => {
1286
- const acc = [];
1287
- for (const task of tasks) collectOne(task, acc);
1288
- return acc;
1289
- };
1290
- const collectOne = (task, acc) => {
1291
- if (isRunnableTest(task)) acc.push(task);
1292
- else collectNested(task, acc);
1293
- };
1294
- const collectNested = (task, acc) => {
1295
- if (task.tasks !== void 0) collectTasks(task.tasks).forEach((nested) => acc.push(nested));
1296
- };
1297
- const notSkipped = (task) => task.mode !== "skip" && task.mode !== "todo";
1298
- const isRunnableTest = (task) => task.type === "test" && notSkipped(task);
1299
637
  const layerImpl = (layer_, options) => layerFor(() => layer_, options, void 0);
1300
638
  /** @internal */
1301
639
  const layer$1 = Function.dual(byLayer, layerImpl);
@@ -1304,82 +642,101 @@ const retryWithin = (timeout) => Schedule.while(Schedule.recurs(10), (state) =>
1304
642
  const flakyTestImpl = (self, timeout = Duration.seconds(30)) => self.pipe(Effect.scoped, Effect.sandbox, (sandboxed) => Effect.retry(sandboxed, retryWithin(timeout)), Effect.orDie);
1305
643
  /** @internal */
1306
644
  const flakyTest$1 = Function.dual(byEffect, flakyTestImpl);
1307
- const isFunction = (arg) => typeof arg === "function";
1308
645
  /**
1309
- * The two returns the bare lane refuses: a promise, which runs outside the test runtime (R9), and an Effect,
1310
- * which the bare lane never runs — it belongs on `it.effect`.
646
+ * The lanes a removed habit name reaches: the call is refused with its rewrite, on the `this` type and at run
647
+ * time, and the property the lane used to carry still runs (R9, KTD3).
1311
648
  */
1312
- const refuseBareOutput = (output) => {
1313
- checkPromiseBody(output);
1314
- if (outputIsEffect(output)) throw new Slop({ detail: refuseBareEffect });
649
+ const refuseHabitLane = (text) => {
650
+ throw refusalOf$1(text);
651
+ };
652
+ const effectLaneRefusal = (prop, text) => Object.assign(() => refuseHabitLane(text), { prop });
653
+ /**
654
+ * The lanes over one `mapEffect`: `it`, its modifiers and `it.each` all register the same generator body, and
655
+ * the Effect-lane property is bound to the same services the lane provides (R7, R9).
656
+ */
657
+ const makeTesterWith = (mapEffect, it, envFor, rerunnable) => {
658
+ const runOnce = (ctx, body) => runLaned(ctx, mapEffect, envFor(), body);
659
+ const rerun = (ctx, body) => outcomeOf(runOnce(ctx, body)).then(leakedOrNothing);
660
+ const shouldRerun = (ctx) => rerunnable && isClean(ctx);
661
+ const rerunIfClean = (ctx, body) => shouldRerun(ctx) ? rerun(ctx, body) : Promise.resolve();
662
+ const run = (ctx, body) => outcomeOf(runOnce(ctx, body)).then((error) => {
663
+ if (error !== void 0) throw error;
664
+ return rerunIfClean(ctx, body);
665
+ });
666
+ const registering = (registrar) => (name, body, timeout) => {
667
+ const lane = laneBodyOf(body);
668
+ marked(() => registrar(name, testOptions(timeout), (ctx) => run(ctx, lane)));
669
+ };
670
+ const dualLane = (registrar) => Function.dual(byName, registering(registrar));
671
+ const base = dualLane(it);
672
+ const skip = dualLane(it.skip);
673
+ const skipIf = (condition) => dualLane(it.skipIf(condition));
674
+ const runIf = (condition) => dualLane(it.runIf(condition));
675
+ const only = dualLane(it.only);
676
+ const fails = dualLane(it.fails);
677
+ const each = (cases) => (name, body, timeout) => {
678
+ marked(() => it.for(cases)(name, testOptions(timeout), (row, ctx) => run(ctx, rowBodyOf(row, body))));
679
+ };
680
+ const effectProperty = makeProperty({
681
+ register: (name, program) => marked(() => it(name, (ctx) => runProperty(ctx, program))),
682
+ provide: (effect) => mapEffect(effect, envFor())
683
+ });
684
+ const prop = (name, spec, holds) => {
685
+ refusePositional(spec);
686
+ effectProperty.effectProp(name, spec, holds);
687
+ };
688
+ return Object.assign(base, {
689
+ skip,
690
+ skipIf,
691
+ runIf,
692
+ only,
693
+ fails,
694
+ each,
695
+ prop
696
+ });
1315
697
  };
1316
- /** One pass of the bare body: it runs bound to the run's context, and its return is refused here. */
1317
- const bareRunOnce = (ctx, self, shadow) => {
1318
- const returned = withSyncRun({
1319
- ctx,
1320
- shadow
1321
- }, () => self(ctx));
1322
- if (isObject(returned)) refuseBareOutput(returned);
698
+ /** The block's methods: the generator lanes, the refusing habit lanes, and the block's own nested `layer`. */
699
+ const methodsFor = (mapEffect, it, envFor, rerunnable, layer) => {
700
+ const tester = makeTesterWith(mapEffect, it, envFor, rerunnable);
701
+ return Object.assign(tester, {
702
+ effect: effectLaneRefusal(tester.prop, refuseEffectLane),
703
+ scoped: effectLaneRefusal(tester.prop, refuseScopedLane),
704
+ scopedLive: effectLaneRefusal(tester.prop, refuseScopedLiveLane),
705
+ prop: prop$1,
706
+ law: property.law,
707
+ flakyTest: flakyTest$1,
708
+ layer
709
+ });
710
+ };
711
+ const refuseHookNow = () => {
712
+ throw refusalOf$1(refuseHook);
1323
713
  };
1324
- /** The bare body's outcome: `undefined` when it passed, or the failure it threw or was refused with. */
1325
- const bareOutcome = (ctx, self, shadow) => outcomeOf(Promise.resolve().then(() => bareRunOnce(ctx, self, shadow)));
1326
- const shouldRerunBare = (ctx, rerunnable) => rerunnable && isClean(ctx);
1327
- const afterBareGate = (ctx, self, rerunnable) => shouldRerunBare(ctx, rerunnable) ? bareOutcome(ctx, self, true).then(leakedOrNothing) : Promise.resolve();
1328
- const afterBareFirst = (ctx, self, rerunnable, failure) => {
1329
- if (failure !== void 0) throwAfterCheck(ctx, failure);
1330
- gateNoAssertion(ctx);
1331
- return afterBareGate(ctx, self, rerunnable);
714
+ const takesHook = (args) => typeof args[0] === "function";
715
+ const refuseHookPair = (fn, timeout) => {
716
+ return refuseHookNow();
1332
717
  };
1333
718
  /**
1334
- * The bare `it(name, body)` lane: Vitest's own registration with the fork's defaults instead of an Effect
1335
- * lane. The body is synchronous, so it is gated for a leftover assertion and run a second time on its own,
1336
- * which is what catches a body that only passes while nothing else touched its module (R5, R9).
719
+ * @internal
1337
720
  */
1338
- const runDefault = (ctx, self, rerunnable) => bareOutcome(ctx, self, false).then((failure) => afterBareFirst(ctx, self, rerunnable, failure));
1339
- const defaultLane = (self, rerunnable) => (ctx) => runDefault(ctx, self, rerunnable);
721
+ const beforeEach$2 = Function.dual(takesHook, refuseHookPair);
1340
722
  /**
1341
- * The fork's `it`: Vitest's test API with the fork's Effect lanes and lawful property lanes attached,
1342
- * callable data-first (`it(name, body)`) or data-last (`it(body, options?)(name)`).
1343
- *
1344
723
  * @internal
1345
724
  */
1346
- const makeMethods$1 = (it) => makeItProxy(it, {
1347
- effect: makeTester((effect, env) => effect.pipe(Effect.scoped, (scoped) => Effect.provide(scoped, testEnv(env))), it),
1348
- live: makeTesterWith((effect) => Effect.scoped(effect), it, () => realTime, true),
1349
- flakyTest: flakyTest$1,
1350
- layer: layer$1,
1351
- prop: prop$1,
1352
- law: property.law
1353
- }, true);
1354
- /** @internal */
1355
- const { effect: effect$1, live: live$1 } = makeMethods$1(V.it);
1356
- function makeItProxy(it, overrides, rerunnable) {
1357
- return new Proxy(it, {
1358
- apply: (target, thisArg, args) => routeCall(target, thisArg, args, rerunnable),
1359
- get: (target, property, receiver) => memberOrOverride(target, overrides, property, receiver)
1360
- });
1361
- }
1362
- function routeCall(target, thisArg, args, rerunnable) {
1363
- return isName(args[0]) ? applied(target, thisArg, args, rerunnable) : (name) => applied(target, thisArg, [
1364
- name,
1365
- args[0],
1366
- args[1]
1367
- ], rerunnable);
1368
- }
725
+ const afterEach$1 = Function.dual(takesHook, refuseHookPair);
1369
726
  /**
1370
- * The route's registration. Vitest takes `[name, body]`, `[name, body, options]` or `[name, options, body]`,
1371
- * so the body is the argument that is a function, and it is the one the fork's bare lane wraps. Vitest's own
1372
- * helpers (`it.skip`, `it.each`, ...) come through the proxy's `get` trap and are handed over untouched.
727
+ * The fork's `it`: the generator lanes on virtual time, the real-clock lane, the lawful property lanes and the
728
+ * layer blocks, callable data-first (`it(name, body)`) or data-last (`it(body, timeout?)(name)`).
729
+ *
730
+ * @internal
1373
731
  */
1374
- function applied(target, thisArg, args, rerunnable) {
1375
- Reflect.apply(target, thisArg, args.map((arg) => isFunction(arg) ? defaultLane(arg, rerunnable) : arg));
1376
- }
1377
- function memberOrOverride(target, overrides, property, receiver) {
1378
- return Object.hasOwn(overrides, property) ? memberOf(overrides, property, receiver) : memberOf(target, property, receiver);
1379
- }
1380
- function memberOf(target, property, receiver) {
1381
- return Reflect.get(target, property, receiver);
1382
- }
732
+ const makeMethods$1 = (it) => {
733
+ const base = methodsFor((effect, env) => effect.pipe(Effect.scoped, (scoped) => Effect.provide(scoped, testEnv(env))), it, perRun, true, layer$1);
734
+ const live = makeTesterWith((effect) => Effect.scoped(effect), it, () => realTime, true);
735
+ return Object.assign(base, {
736
+ live,
737
+ layer: layer$1
738
+ });
739
+ };
1383
740
  const describeImpl = (name, f) => V.describe(name, {
1384
741
  concurrent: true,
1385
742
  shuffle: true
@@ -1389,60 +746,50 @@ const describeWrapped$1 = Function.dual(byName, describeImpl);
1389
746
  //#endregion
1390
747
  //#region src/mod.ts
1391
748
  /**
749
+ * The fork's test API, with the generator lanes of R2 and R9.
750
+ *
1392
751
  * @since 4.0.0
1393
752
  */
1394
- const addEqualityTesters = addEqualityTesters$1;
753
+ const it = makeMethods$1(V.it);
1395
754
  /**
755
+ * The same lanes under the other name upstream publishes.
756
+ *
1396
757
  * @since 4.0.0
1397
758
  */
1398
- const effect = effect$1;
759
+ const test = it;
1399
760
  /**
761
+ * The fork's lawful collector: it forces the fork's concurrency and shuffle defaults and hands its body the
762
+ * fork's methods.
763
+ *
1400
764
  * @since 4.0.0
1401
765
  */
1402
- const live = live$1;
766
+ const describeWrapped = describeWrapped$1;
1403
767
  /**
1404
- * Share a `Layer` between multiple tests, optionally wrapping
1405
- * the tests in a `describe` block if a name is provided.
1406
- *
1407
- * Named layers accept `concurrent` to override inherited suite concurrency.
1408
- * Anonymous layers always inherit the enclosing suite's concurrency.
1409
- * Use `ctx.expect` in concurrent tests for test-local snapshots and assertion counts.
1410
- *
1411
768
  * @since 4.0.0
769
+ */
770
+ const describe = describeWrapped$1;
771
+ /**
772
+ * Share a `Layer` between multiple tests, optionally wrapping the tests in a `describe` block if a name is
773
+ * provided. Every test in the block gets its own fresh build of the layer, unless the block declares
774
+ * `shared: true`.
1412
775
  *
1413
776
  * ```ts
1414
- * import { assert, layer } from "@systemfsoftware/vitest"
1415
- * import { Effect, Layer, Context } from "effect"
777
+ * import { it, layer } from "@systemfsoftware/vitest"
778
+ * import { Context, Effect, Layer } from "effect"
1416
779
  *
1417
780
  * class Foo extends Context.Service<Foo, "foo">()("Foo") {
1418
781
  * static layer = Layer.succeed(Foo, "foo")
1419
782
  * }
1420
783
  *
1421
- * class Bar extends Context.Service<Bar, "bar">()("Bar") {
1422
- * static layer = Layer.effect(
1423
- * Bar,
1424
- * Effect.map(Foo, () => "bar" as const)
1425
- * )
1426
- * }
1427
- *
1428
784
  * layer(Foo.layer)("layer", (it) => {
1429
- * it.effect("adds context", () =>
1430
- * Effect.gen(function*() {
1431
- * const foo = yield* Foo
1432
- * assert.strictEqual(foo, "foo")
1433
- * }))
1434
- *
1435
- * it.layer(Bar.layer)("nested", (it) => {
1436
- * it.effect("adds context", () =>
1437
- * Effect.gen(function*() {
1438
- * const foo = yield* Foo
1439
- * const bar = yield* Bar
1440
- * assert.strictEqual(foo, "foo")
1441
- * assert.strictEqual(bar, "bar")
1442
- * }))
785
+ * it("reads the service", function* ({ expect }) {
786
+ * const foo = yield* Foo
787
+ * yield* expect(foo).toEqual("foo")
1443
788
  * })
1444
789
  * })
1445
790
  * ```
791
+ *
792
+ * @since 4.0.0
1446
793
  */
1447
794
  const layer = layer$1;
1448
795
  /**
@@ -1456,68 +803,42 @@ const prop = prop$1;
1456
803
  /**
1457
804
  * @since 4.0.0
1458
805
  */
1459
- const owned = owned$1;
1460
- /**
1461
- * @since 4.0.0
1462
- */
1463
- const recordAssertion = recordAssertion$1;
806
+ const law = law$1;
1464
807
  /**
1465
- * The run binding of the test the effect is executing in. `bind` re-provides it
1466
- * to an effect a library runs on a runtime of its own — its own scheduler, a
1467
- * worker, a simulation kernel — so the checks inside it count as this test's
1468
- * assertions, report softly, and see the same `owned` regions as the test.
808
+ * The fork's test API over another Vitest `it`, so a library can register tests in its own suite.
1469
809
  *
1470
810
  * @since 4.0.0
1471
811
  */
1472
- const captureRunBinding = captureRunBinding$1;
812
+ const makeMethods = makeMethods$1;
1473
813
  /**
1474
- * The key the running test's context is published under. A library that carries its own view of the task
1475
- * context builds that view on this key, so the context a case lane sees and a property lane sees agree.
814
+ * Refused: hooks share state between tests, and services come fresh per test from `layer`.
1476
815
  *
1477
816
  * @since 4.0.0
1478
817
  */
1479
- const vitestTestContextKey = vitestTestContextKey$1;
818
+ const beforeEach = beforeEach$2;
1480
819
  /**
1481
- * The running Vitest `TestContext`, provided on every test the fork runs — the Effect lanes, both property
1482
- * lanes, and each `it.each` row. It is `null` outside a run.
820
+ * Refused: hooks share state between tests, and services come fresh per test from `layer`.
1483
821
  *
1484
822
  * @since 4.0.0
1485
823
  */
1486
- const VitestTestContext = VitestTestContext$1;
1487
- /**
1488
- * @since 4.0.0
1489
- */
1490
- const it = makeMethods$1(V.it);
1491
- /**
1492
- * @since 4.0.0
1493
- */
1494
- const makeMethods = makeMethods$1;
824
+ const afterEach = afterEach$1;
1495
825
  /**
1496
826
  * @since 4.0.0
1497
827
  */
1498
- const describeWrapped = describeWrapped$1;
1499
- const describe = describeWrapped$1;
828
+ const addEqualityTesters = addEqualityTesters$1;
1500
829
  /**
1501
- * The fork's `expect`: soft within one Effect step, refusing the slop forms.
830
+ * The key the running test's context is published under. A library that carries its own view of the task
831
+ * context builds that view on this key, so the context a case lane sees and a property lane sees agree.
1502
832
  *
1503
833
  * @since 4.0.0
1504
834
  */
1505
- const expect = expect$1;
1506
- /**
1507
- * @since 4.0.0
1508
- */
1509
- const beforeEach = beforeEach$1;
1510
- /**
1511
- * @since 4.0.0
1512
- */
1513
- const afterEach = afterEach$1;
1514
- /**
1515
- * @since 4.0.0
1516
- */
1517
- const beforeAll = beforeAll$1;
835
+ const vitestTestContextKey = vitestTestContextKey$1;
1518
836
  /**
837
+ * The running Vitest `TestContext`, provided on every test the fork runs — every generator lane, both property
838
+ * lanes, and each `it.each` row. It is `null` outside a run.
839
+ *
1519
840
  * @since 4.0.0
1520
841
  */
1521
- const afterAll = afterAll$1;
842
+ const VitestTestContext = VitestTestContext$1;
1522
843
  //#endregion
1523
- export { VitestTestContext, addEqualityTesters, afterAll, afterEach, beforeAll, beforeEach, captureRunBinding, describe, describeWrapped, effect, expect, flakyTest, it, layer, live, makeMethods, owned, prop, recordAssertion, vitestTestContextKey };
844
+ export { VitestTestContext, addEqualityTesters, afterAll, afterEach, assertType, beforeAll, beforeEach, describe, describeWrapped, expectTypeOf, flakyTest, inject, it, law, layer, makeMethods, onTestFailed, onTestFinished, prop, recordArtifact, test, vi, vitest, vitestTestContextKey };