@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 +24 -0
- package/README.md +113 -61
- package/dist/TestClock.d.ts +1 -1
- package/dist/checks-Cq9of-Lx.d.ts +336 -0
- package/dist/engine-YsoQ_9we.d.ts +158 -0
- package/dist/failure-BquJJPFZ.mjs +1217 -0
- package/dist/failure.d.ts +80 -0
- package/dist/failure.mjs +4 -0
- package/dist/guard-p_8ARgPm.mjs +250 -0
- package/dist/guard.d.ts +2 -0
- package/dist/guard.mjs +19 -0
- package/dist/index.d.ts +203 -440
- package/dist/index.mjs +376 -1055
- package/dist/integration.d.ts +37 -0
- package/dist/integration.mjs +38 -0
- package/dist/test-context-ChvjVyCo.mjs +328 -0
- package/package.json +17 -12
- package/dist/refusals-BQtDszyU.mjs +0 -32
- package/dist/refusals-_6e3GzVK.d.ts +0 -37
- package/dist/refusals.d.ts +0 -3
- package/dist/refusals.mjs +0 -3
- package/dist/utils.d.ts +0 -197
- package/dist/utils.mjs +0 -227
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import { a as Values, i as PropertySubject, n as Gens, o as engine_d_exports, r as PropertySpec } from "./engine-YsoQ_9we.js";
|
|
3
|
+
import { S as summaryOf, _ as FailureRecord, b as TestIdentity, g as Breach, h as AttributeValue, i as Checks, t as Asserted, v as FailureRecordInput, x as renderFailureRecord, y as ReplayValue } from "./checks-Cq9of-Lx.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Tracer from "effect/Tracer";
|
|
6
|
+
//#region src/internal/call-site.d.ts
|
|
7
|
+
/** @internal */
|
|
8
|
+
export declare const callFrameOutside: (library: RegExp) => string | undefined;
|
|
9
|
+
/** @internal */
|
|
10
|
+
export declare const callSite: () => string | undefined;
|
|
11
|
+
/** @internal */
|
|
12
|
+
export declare const withRaisingFrame: {
|
|
13
|
+
<A extends Error>(error: A, frame: string | undefined): A;
|
|
14
|
+
(frame: string | undefined): <A extends Error>(error: A) => A;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/internal/failure-error.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Renders the record and throws what Vitest prints for it: the record itself when it holds its contract, a
|
|
20
|
+
* `FailureRecordRefused` when it breaks one, and a refusal passed in unchanged, so refusing cannot recurse (R10).
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare const throwFailureRecord: <E>(input: FailureRecordInput<E>) => never;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/internal/failure-identity.d.ts
|
|
27
|
+
/** The fields this reads off a Vitest task: every one optional, so a partial context yields an incomplete identity. */
|
|
28
|
+
interface TaskLike {
|
|
29
|
+
readonly name?: string | undefined;
|
|
30
|
+
readonly fullName?: string | undefined;
|
|
31
|
+
readonly fullTestName?: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The identity of the test being run, or of the task handed in. A run outside a test, or one whose task carries
|
|
35
|
+
* no file, yields an incomplete identity, which the renderer reports as the R6 breach it is.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export declare const testIdentityOf: (task?: TaskLike | null) => TestIdentity;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/internal/provided.d.ts
|
|
42
|
+
/** @internal */
|
|
43
|
+
declare const providedRoot: () => string | undefined;
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/internal/recorded-run.d.ts
|
|
46
|
+
/** @internal */
|
|
47
|
+
interface RecordedRun<A, E> {
|
|
48
|
+
(checks: Checks): Effect.Effect<A, E, Asserted>;
|
|
49
|
+
}
|
|
50
|
+
/** @internal */
|
|
51
|
+
interface RecordedProperty<G extends Gens, S extends PropertySubject, N extends number> {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly spec: PropertySpec<G, S, N>;
|
|
54
|
+
readonly holds: (subject: S, values: Values<G>) => boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Runs one program as the runner runs a lane, with a fresh ledger and its own recorder, and resolves with the
|
|
58
|
+
* record its failure rendered — or `undefined` when it passed.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export declare const recordOfRun: <A, E>(program: RecordedRun<A, E>) => Promise<FailureRecord | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Runs one property the way the runner runs `it.prop`, with a fresh ledger and its own recorder, and resolves with
|
|
65
|
+
* the record its falsification rendered — or `undefined` when it held.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export declare const recordOfProperty: <const G extends Gens, S extends PropertySubject, N extends number>(input: RecordedProperty<G, S, N>) => Promise<FailureRecord | undefined>;
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/internal/span-recorder.d.ts
|
|
72
|
+
/** @internal */
|
|
73
|
+
interface SpanRecorder {
|
|
74
|
+
readonly tracer: Tracer.Tracer;
|
|
75
|
+
readonly spans: ReadonlyArray<Tracer.NativeSpan>;
|
|
76
|
+
}
|
|
77
|
+
/** @internal */
|
|
78
|
+
export declare const createSpanRecorder: () => SpanRecorder;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { type AttributeValue, type Breach, type FailureRecord, type FailureRecordInput, type RecordedProperty, type RecordedRun, type ReplayValue, type SpanRecorder, type TestIdentity, providedRoot as providedWorkspaceRoot, renderFailureRecord, summaryOf };
|
package/dist/failure.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
|
|
2
|
+
import { d as summaryOf, l as throwFailureRecord, n as recordOfRun, o as testIdentityOf, r as createSpanRecorder, s as providedRoot, t as recordOfProperty, u as renderFailureRecord } from "./failure-BquJJPFZ.mjs";
|
|
3
|
+
import { c as callFrameOutside, l as callSite, p as withRaisingFrame } from "./test-context-ChvjVyCo.mjs";
|
|
4
|
+
export { callFrameOutside, callSite, createSpanRecorder, providedRoot as providedWorkspaceRoot, recordOfProperty, recordOfRun, renderFailureRecord, summaryOf, testIdentityOf, throwFailureRecord, withRaisingFrame };
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
|
|
2
|
+
import { chai } from "vitest";
|
|
3
|
+
//#region src/internal/refusals.ts
|
|
4
|
+
/**
|
|
5
|
+
* The brand every run-time refusal carries. A test is run a second time on a fresh build to catch leaked state,
|
|
6
|
+
* so when that second run fails with a refusal the fork itself made, the runner re-throws the refusal rather than
|
|
7
|
+
* relabelling it as a leak (see `throwSecondRun` in `runner.ts`). The brand is a `Symbol.for` value because a
|
|
8
|
+
* worker holds two copies of these modules at once — the setup file's `dist/guard.mjs` beside the test graph's
|
|
9
|
+
* `src/mod.ts` — and a refusal thrown by either copy has to be recognisable to the other, exactly as the guard's
|
|
10
|
+
* window is shared.
|
|
11
|
+
*/
|
|
12
|
+
const refusalBrand = Symbol.for("@systemfsoftware/vitest/refusal");
|
|
13
|
+
/** The error a refusal throws: the refusal text, branded so {@link isRefusal} recognises it. */
|
|
14
|
+
/** @internal */
|
|
15
|
+
const refusalOf = (text) => {
|
|
16
|
+
const error = new Error(text);
|
|
17
|
+
Reflect.set(error, refusalBrand, true);
|
|
18
|
+
return error;
|
|
19
|
+
};
|
|
20
|
+
/** Whether an error is one of the fork's own refusals, rather than a failure of the test under it. */
|
|
21
|
+
/** @internal */
|
|
22
|
+
const isRefusal = (error) => error instanceof Error && Reflect.get(error, refusalBrand) === true;
|
|
23
|
+
/** @internal */
|
|
24
|
+
const refusedText = {
|
|
25
|
+
toBeDefined: "✗ toBeDefined passes for almost any value the code returns. Assert the value: yield* expect(actual).toEqual(expected).",
|
|
26
|
+
toBeTruthy: "✗ toBeTruthy passes for almost any value the code returns. Assert the value: yield* expect(actual).toEqual(expected).",
|
|
27
|
+
toBeFalsy: "✗ toBeFalsy passes for almost any value the code returns. Assert the value: yield* expect(actual).toEqual(expected).",
|
|
28
|
+
toHaveLength: "✗ toHaveLength checks how many, not which. Assert the contents: toEqual([...]); only some of them: toEqual(expect.arrayContaining([...])).",
|
|
29
|
+
toHaveProperty: "✗ toHaveProperty checks one key at a time. Assert the shape once: toMatchObject({ key: value, ... }).",
|
|
30
|
+
toBeInstanceOf: "✗ toBeInstanceOf checks the kind of value, not the value. Assert the value: toEqual(expected); only its shape: toEqual(expect.schemaMatching(Schema)).",
|
|
31
|
+
toBeTypeOf: "✗ toBeTypeOf checks the kind of value, not the value. Assert the value: toEqual(expected); only its shape: toEqual(expect.schemaMatching(Schema)).",
|
|
32
|
+
toHaveBeenCalled: "✗ toHaveBeenCalled passes for any call with any arguments. Assert what the call carried: toHaveBeenCalledExactlyOnceWith(...args).",
|
|
33
|
+
toHaveBeenCalledTimes: "✗ toHaveBeenCalledTimes counts calls, not what they carried. Assert the calls: toHaveBeenNthCalledWith(n, ...args), or all of them: expect(spy.mock.calls).toEqual([[...], [...]]).",
|
|
34
|
+
toMatchSnapshot: "✗ a snapshot records what the code does now, not what it should do, and cannot fail on the run that writes it. State the expected value: toEqual(expected).",
|
|
35
|
+
toMatchInlineSnapshot: "✗ a snapshot records what the code does now, not what it should do, and cannot fail on the run that writes it. State the expected value: toEqual(expected).",
|
|
36
|
+
toMatchFileSnapshot: "✗ a snapshot records what the code does now, not what it should do, and cannot fail on the run that writes it. State the expected value: toEqual(expected).",
|
|
37
|
+
toThrowErrorMatchingSnapshot: "✗ a snapshot records what the code does now, not what it should do, and cannot fail on the run that writes it. Name the error: toThrow(MyError).",
|
|
38
|
+
toThrowErrorMatchingInlineSnapshot: "✗ a snapshot records what the code does now, not what it should do, and cannot fail on the run that writes it. Name the error: toThrow(MyError).",
|
|
39
|
+
resolves: "✗ .resolves/.rejects assert on a Promise outside the test runtime. Yield the Effect: const value = yield* program; its failure: const error = yield* Effect.flip(program); then yield* expect(error).toEqual(new MyError(...)).",
|
|
40
|
+
rejects: "✗ .resolves/.rejects assert on a Promise outside the test runtime. Yield the Effect: const value = yield* program; its failure: const error = yield* Effect.flip(program); then yield* expect(error).toEqual(new MyError(...)).",
|
|
41
|
+
anything: "✗ expect.anything() matches every value. Say what the value must be: expect.any(Money), expect.objectContaining({ ... }), expect.schemaMatching(Schema).",
|
|
42
|
+
poll: "✗ expect.poll waits on the real clock, outside the runner's virtual time. Let time pass inside the test: yield* TestClock.adjust(\"3 seconds\"); then yield* expect(value).toEqual(expected).",
|
|
43
|
+
soft: "✗ expect.soft reports several failures on one state; the fork reports the state once. Assert the state once: yield* expect(actual).toEqual(expected)."
|
|
44
|
+
};
|
|
45
|
+
/** @internal */
|
|
46
|
+
const negatedText = {
|
|
47
|
+
...refusedText,
|
|
48
|
+
toBeNull: "✗ not.toBeNull passes for almost any value the code returns. Assert the value: yield* expect(actual).toEqual(expected).",
|
|
49
|
+
toBeUndefined: "✗ not.toBeUndefined passes for almost any value the code returns. Assert the value: yield* expect(actual).toEqual(expected)."
|
|
50
|
+
};
|
|
51
|
+
/** @internal */
|
|
52
|
+
const neededText = {
|
|
53
|
+
toThrow: "✗ toThrow() passes for any error. Name the one expected: toThrow(MyError) or toThrow(\"message\"). An Effect failure is a value: const error = yield* Effect.flip(program); yield* expect(error).toEqual(new MyError(...)).",
|
|
54
|
+
toThrowError: "✗ toThrowError() passes for any error. Name the one expected: toThrow(MyError) or toThrow(\"message\"). An Effect failure is a value: const error = yield* Effect.flip(program); yield* expect(error).toEqual(new MyError(...)).",
|
|
55
|
+
toSatisfy: "✗ toSatisfy(predicate) fails with 'expected x to satisfy [Function]'. Say what must hold: toSatisfy(predicate, \"every line has a positive quantity\").",
|
|
56
|
+
toMatchObject: "✗ toMatchObject({}) matches every object. Name the fields that must hold: toMatchObject({ status: \"Pending\" })."
|
|
57
|
+
};
|
|
58
|
+
/** @internal */
|
|
59
|
+
const booleanRefusal = "✗ expect(<boolean>) can only report 'expected false to be true'. Pass the two values instead: yield* expect(a).toEqual(b), which uses Effect Equal. A predicate: yield* expect(value).toSatisfy(predicate, \"what must hold\").";
|
|
60
|
+
/** @internal */
|
|
61
|
+
const refusePiecewise = "✗ a second check on the same state. Checking a state piece by piece reports one field at a time and misses the ones never checked. Assert the state once: yield* expect(order).toMatchObject({ id: 1, status: \"Pending\" }), or gather what you observed: yield* expect({ total, status }).toEqual({ total: Money.of(4.4), status: \"Pending\" }). A check inside a loop is the same: assert the whole array once. Several inputs: it.each(rows)(name, function* (row, { expect }) { ... }).";
|
|
62
|
+
/** @internal */
|
|
63
|
+
const refuseUnyielded = "✗ a check was written but never yielded, so it never ran. Yield it: yield* expect(actual).toEqual(expected).";
|
|
64
|
+
/** @internal */
|
|
65
|
+
const noCheckRefusal = "✗ this test yields no check, so it cannot fail. Yield one from the test's own expect: it(name, function* ({ expect }) { yield* expect(actual).toEqual(expected) }). An expect imported from vitest does not count.";
|
|
66
|
+
/** @internal */
|
|
67
|
+
const unprovidedText = "✗ the test needs a service that nothing provides. Write the test inside layer(Service.layer)((it) => { it(name, ...) }); every test gets its own fresh build.";
|
|
68
|
+
/** @internal */
|
|
69
|
+
const refuseAsyncBody = "✗ an async body runs outside the test runtime. Pass a generator: it(name, function* ({ expect }) { yield* expect(actual).toEqual(expected) }).";
|
|
70
|
+
/** @internal */
|
|
71
|
+
const refuseSyncBody = "✗ the body must be a generator that yields its checks: it(name, function* ({ expect }) { const x = yield* program; yield* expect(x).toEqual(expected) }).";
|
|
72
|
+
/** @internal */
|
|
73
|
+
const refuseEffectLane = "✗ it.effect is removed: the body is the generator itself, so the runner sees every step. it(name, function* ({ expect }) { const x = yield* program; yield* expect(x).toEqual(expected) }).";
|
|
74
|
+
/** @internal */
|
|
75
|
+
const refuseScopedLane = "✗ it.scoped is removed: the body is the generator itself, so the runner sees every step. it(name, function* ({ expect }) { const x = yield* program; yield* expect(x).toEqual(expected) }).";
|
|
76
|
+
/** @internal */
|
|
77
|
+
const refuseScopedLiveLane = "✗ it.scopedLive is removed: the body is the generator itself, so the runner sees every step. it(name, function* ({ expect }) { const x = yield* program; yield* expect(x).toEqual(expected) }).";
|
|
78
|
+
/** @internal */
|
|
79
|
+
const refuseHook = "✗ hooks share state between tests. Build what a test needs inside it; services come fresh per test from layer(Service.layer)((it) => { ... }).";
|
|
80
|
+
/** @internal */
|
|
81
|
+
const refuseRawExpect = "✗ an expect imported from vitest ran; take it from the test callback: it(name, function* ({ expect }) { ... })";
|
|
82
|
+
/** @internal */
|
|
83
|
+
const refuseRawIt = "✗ this test was registered with vitest's it; import it from @systemfsoftware/vitest";
|
|
84
|
+
/**
|
|
85
|
+
* The fixed prose each breach of the record contract is refused with (R10, KTD7). A refusal carries this text and
|
|
86
|
+
* nothing from the record itself, and nothing ever reads it back, so a refusal cannot recurse. The breach letter is
|
|
87
|
+
* not printed: a reader acts on what is missing, not on which requirement numbered it.
|
|
88
|
+
*/
|
|
89
|
+
/** @internal */
|
|
90
|
+
const refusedRecordText = {
|
|
91
|
+
R1: "the headline is empty",
|
|
92
|
+
R2: "the record names no source location",
|
|
93
|
+
R6: "a replay value names a run no generator chose, or the rerun line is missing"
|
|
94
|
+
};
|
|
95
|
+
/** The one detail a record refusal carries: every breach the renderer found, in the prose above (R10). */
|
|
96
|
+
/** @internal */
|
|
97
|
+
const refusedRecordDetail = (breaches) => `✗ refused a failure record: ${breaches.map((breach) => refusedRecordText[breach]).join(", and ")}`;
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/internal/guard.ts
|
|
100
|
+
/**
|
|
101
|
+
* The library brands both its checks and its tests, and this module closes the way around both brands.
|
|
102
|
+
*
|
|
103
|
+
* The `Asserted` brand only proves a test yielded a fork check. It cannot see a raw `vitest` `expect` beside
|
|
104
|
+
* that check, and it cannot see a test the fork never registered. Both travel on the one shared
|
|
105
|
+
* `chai.Assertion` prototype, measured on Vitest 5.0.1 and Chai 6.2.2:
|
|
106
|
+
*
|
|
107
|
+
* - raw `expect`, `expect.soft` and `expect.poll` all enter chai through `chai.expect`
|
|
108
|
+
* (`vitest/dist/chunks/index.m3L2HgmY.js:8005`, `:8035`, `:7514`);
|
|
109
|
+
* - the task's own `ctx.expect` is `createExpect(context.task)` (`vitest/dist/chunks/index.m3L2HgmY.js:8513`)
|
|
110
|
+
* and enters through that same `chai.expect` (`:8005`);
|
|
111
|
+
* - chai's `expect` is `new Assertion(val, message)` (`chai/index.js:3272`), and every `assert.*` method is
|
|
112
|
+
* `new Assertion(...)` on the same class (`chai/index.js:3367`, `:3394`).
|
|
113
|
+
*
|
|
114
|
+
* So this module wraps that prototype once, and every method, chainable method and property on it refuses
|
|
115
|
+
* unless a check opened the window with {@link authorize} for the duration of its matcher call. Nothing
|
|
116
|
+
* asynchronous may run inside that window, so a plain synchronous depth counter is enough; the counter is
|
|
117
|
+
* nested-safe because chai's own matchers build further assertions inside the one call.
|
|
118
|
+
*
|
|
119
|
+
* The window, the install flag and the wrapper marks live on `globalThis` under `Symbol.for` keys rather than
|
|
120
|
+
* in module scope. A worker can hold two copies of this file at once — the setup file resolves through Node's
|
|
121
|
+
* conditions to `dist/guard.mjs` while the test graph resolves the fork's entry through the source condition
|
|
122
|
+
* to `src/mod.ts` — and a window opened by one copy has to be the window the other copy reads. Module-scope
|
|
123
|
+
* state made every legitimate check fail with the raw-expect refusal.
|
|
124
|
+
*
|
|
125
|
+
* The other half is {@link markTask}: the fork's `it` marks each task it registers, and the setup-file entry
|
|
126
|
+
* (`@systemfsoftware/vitest/guard`) refuses a task that carries no mark. This setup-file hook is internal plumbing; the
|
|
127
|
+
* user-facing hook refusal is the fork's own `beforeEach`.
|
|
128
|
+
*
|
|
129
|
+
* @since 4.0.0
|
|
130
|
+
*/
|
|
131
|
+
const isCallable = (value) => typeof value === "function";
|
|
132
|
+
const isObject = (value) => typeof value === "object" && value !== null;
|
|
133
|
+
const hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
134
|
+
const hasGuardFields = (value) => hasOwn(value, "depth") && hasOwn(value, "installed");
|
|
135
|
+
const isGuardState = (value) => isObject(value) && hasGuardFields(value);
|
|
136
|
+
const stateKey = Symbol.for("@systemfsoftware/vitest/guard");
|
|
137
|
+
const wrappedKey = Symbol.for("@systemfsoftware/vitest/guard/wrapped");
|
|
138
|
+
const createState = () => {
|
|
139
|
+
const created = {
|
|
140
|
+
depth: 0,
|
|
141
|
+
installed: false,
|
|
142
|
+
extendPatched: false
|
|
143
|
+
};
|
|
144
|
+
Reflect.set(globalThis, stateKey, created);
|
|
145
|
+
return created;
|
|
146
|
+
};
|
|
147
|
+
const stateOf = () => {
|
|
148
|
+
const existing = Reflect.get(globalThis, stateKey);
|
|
149
|
+
return isGuardState(existing) ? existing : createState();
|
|
150
|
+
};
|
|
151
|
+
const state = stateOf();
|
|
152
|
+
const isWrapped = (value) => Reflect.get(value, wrappedKey) === true;
|
|
153
|
+
const isUnwrapped = (value) => isCallable(value) && !isWrapped(value);
|
|
154
|
+
const authorized = () => state.depth > 0;
|
|
155
|
+
const refuse = () => {
|
|
156
|
+
throw refusalOf(refuseRawExpect);
|
|
157
|
+
};
|
|
158
|
+
/** @internal */
|
|
159
|
+
const authorize = (run) => {
|
|
160
|
+
state.depth += 1;
|
|
161
|
+
try {
|
|
162
|
+
return run();
|
|
163
|
+
} finally {
|
|
164
|
+
state.depth -= 1;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
/** @internal */
|
|
168
|
+
const markTask = (task) => {
|
|
169
|
+
task.meta.effectVitestMarked = true;
|
|
170
|
+
};
|
|
171
|
+
/** @internal */
|
|
172
|
+
const isMarked = (task) => task.meta.effectVitestMarked === true;
|
|
173
|
+
const guardedCall = (run) => {
|
|
174
|
+
const guarded = function(...args) {
|
|
175
|
+
if (!authorized()) refuse();
|
|
176
|
+
return Reflect.apply(run, this, args);
|
|
177
|
+
};
|
|
178
|
+
Reflect.defineProperty(guarded, wrappedKey, { value: true });
|
|
179
|
+
return guarded;
|
|
180
|
+
};
|
|
181
|
+
const fieldOf = (descriptor, field) => Reflect.get(descriptor, field);
|
|
182
|
+
const withValue = (descriptor) => {
|
|
183
|
+
const value = fieldOf(descriptor, "value");
|
|
184
|
+
return isUnwrapped(value) ? {
|
|
185
|
+
...descriptor,
|
|
186
|
+
value: guardedCall(value)
|
|
187
|
+
} : descriptor;
|
|
188
|
+
};
|
|
189
|
+
const withRead = (descriptor) => {
|
|
190
|
+
const read = fieldOf(descriptor, "get");
|
|
191
|
+
return isUnwrapped(read) ? {
|
|
192
|
+
...descriptor,
|
|
193
|
+
get: guardedCall(read)
|
|
194
|
+
} : descriptor;
|
|
195
|
+
};
|
|
196
|
+
const withWrite = (descriptor) => {
|
|
197
|
+
const write = fieldOf(descriptor, "set");
|
|
198
|
+
return isUnwrapped(write) ? {
|
|
199
|
+
...descriptor,
|
|
200
|
+
set: guardedCall(write)
|
|
201
|
+
} : descriptor;
|
|
202
|
+
};
|
|
203
|
+
const guardedDescriptor = (descriptor) => withWrite(withRead(withValue(descriptor)));
|
|
204
|
+
const isWrappable = (descriptor) => descriptor?.configurable === true;
|
|
205
|
+
const guardMember = (proto, name) => {
|
|
206
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, name);
|
|
207
|
+
if (!isWrappable(descriptor)) return;
|
|
208
|
+
Object.defineProperty(proto, name, guardedDescriptor(descriptor));
|
|
209
|
+
};
|
|
210
|
+
const bookkeeping = [
|
|
211
|
+
"constructor",
|
|
212
|
+
"__methods",
|
|
213
|
+
"__flags",
|
|
214
|
+
"_obj"
|
|
215
|
+
];
|
|
216
|
+
/**
|
|
217
|
+
* Every own member of the prototype, string- and symbol-keyed, except chai's bookkeeping fields. `Reflect.ownKeys`
|
|
218
|
+
* rather than `Object.getOwnPropertyNames`, so a member chai hangs off a symbol is guarded too: measured on chai
|
|
219
|
+
* 6.2.2, `Assertion.prototype` carries 104 string-keyed own members and no symbol-keyed ones, so the enumeration
|
|
220
|
+
* is unchanged today and stays closed if chai adds one.
|
|
221
|
+
*/
|
|
222
|
+
const assertionMembers = (proto) => Reflect.ownKeys(proto).filter((name) => bookkeeping.includes(name) === false);
|
|
223
|
+
const guardPrototype = () => {
|
|
224
|
+
const proto = chai.Assertion.prototype;
|
|
225
|
+
for (const name of assertionMembers(proto)) guardMember(proto, name);
|
|
226
|
+
};
|
|
227
|
+
const extendOf = () => Reflect.get(chai.expect, "extend");
|
|
228
|
+
const wrapExtend = () => {
|
|
229
|
+
const extend = extendOf();
|
|
230
|
+
if (!isCallable(extend)) return;
|
|
231
|
+
state.extendPatched = true;
|
|
232
|
+
chai.util.addMethod(chai.expect, "extend", function(...args) {
|
|
233
|
+
const result = Reflect.apply(extend, this, args);
|
|
234
|
+
guardPrototype();
|
|
235
|
+
return result;
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
const patchExtend = () => {
|
|
239
|
+
if (state.extendPatched) return;
|
|
240
|
+
wrapExtend();
|
|
241
|
+
};
|
|
242
|
+
/** @internal */
|
|
243
|
+
const installGuard = () => {
|
|
244
|
+
if (state.installed) return;
|
|
245
|
+
state.installed = true;
|
|
246
|
+
guardPrototype();
|
|
247
|
+
patchExtend();
|
|
248
|
+
};
|
|
249
|
+
//#endregion
|
|
250
|
+
export { unprovidedText as S, refuseScopedLiveLane as _, booleanRefusal as a, refusedRecordDetail as b, negatedText as c, refuseAsyncBody as d, refuseEffectLane as f, refuseScopedLane as g, refuseRawIt as h, markTask as i, noCheckRefusal as l, refusePiecewise as m, installGuard as n, isRefusal as o, refuseHook as p, isMarked as r, neededText as s, authorize as t, refusalOf as u, refuseSyncBody as v, refusedText as x, refuseUnyielded as y };
|
package/dist/guard.d.ts
ADDED
package/dist/guard.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import { h as refuseRawIt, n as installGuard, r as isMarked, u as refusalOf } from "./guard-p_8ARgPm.mjs";
|
|
3
|
+
import { beforeEach } from "vitest";
|
|
4
|
+
//#region src/guard.ts
|
|
5
|
+
/**
|
|
6
|
+
* The guard setup file, published as `@systemfsoftware/vitest/guard` and listed in the shared Vitest config's
|
|
7
|
+
* `setupFiles`. It installs the prototype guard once per worker and refuses any task the fork's `it` did not
|
|
8
|
+
* register, so neither a raw `vitest` `expect` nor a raw `vitest` `it` can sit beside a fork check (KTD8).
|
|
9
|
+
*
|
|
10
|
+
* Both halves are internal plumbing; the fork's own `beforeEach` refusal is the user-facing one.
|
|
11
|
+
*
|
|
12
|
+
* @since 4.0.0
|
|
13
|
+
*/
|
|
14
|
+
installGuard();
|
|
15
|
+
beforeEach((context) => {
|
|
16
|
+
if (!isMarked(context.task)) throw refusalOf(refuseRawIt);
|
|
17
|
+
});
|
|
18
|
+
//#endregion
|
|
19
|
+
export {};
|