@jterrazz/test 15.2.0 → 15.3.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/README.md +38 -29
- package/dist/artifacts.js +41 -4
- package/dist/browser/index.js +1364 -1045
- package/dist/checker.js +461 -26
- package/dist/index.d.ts +648 -133
- package/dist/index.js +726 -39
- package/dist/intercept.js +9 -1
- package/dist/oxlint.cjs +47 -14
- package/dist/oxlint.d.cts +1 -1
- package/dist/oxlint.d.ts +1 -1
- package/dist/oxlint.js +47 -14
- package/dist/playwright.adapter.js +1 -0
- package/dist/{queue.js → stream.js} +60 -1
- package/dist/vitest.d.ts +38 -8
- package/dist/vitest.js +59 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,119 +1,7 @@
|
|
|
1
1
|
import { Scalar } from "yaml";
|
|
2
|
-
import { DeepMockProxy } from "vitest-mock-extended";
|
|
2
|
+
import { DeepMockProxy, MockProxy } from "vitest-mock-extended";
|
|
3
3
|
import { ReactNode as ComponentUi } from "react";
|
|
4
4
|
import "vitest/browser";
|
|
5
|
-
//#region src/specification/matching/match.d.ts
|
|
6
|
-
/**
|
|
7
|
-
* Code-side dynamic-value matchers (`match.*`) and the capture scope shared
|
|
8
|
-
* by every assertion of a single spec execution.
|
|
9
|
-
*
|
|
10
|
-
* Matchers are used inside `toMatchRows` rows and any structural comparison;
|
|
11
|
-
* their file-side twins are the `{{placeholder}}` forms in `_expected/*.http`
|
|
12
|
-
* fixtures (body AND headers), `_expected/*.json` files, and text snapshots
|
|
13
|
-
* (`_expected/*.txt`). One grammar, one vocabulary (CONVENTIONS D4).
|
|
14
|
-
*/
|
|
15
|
-
/** The frozen token vocabulary (CONVENTIONS D4) plus the code-only kinds. */
|
|
16
|
-
type MatcherKind = 'any' | 'base64' | 'date' | 'duration' | 'email' | 'float' | 'hex' | 'includes' | 'int' | 'ip' | 'iso8601' | 'number' | 'path' | 'port' | 'ref' | 'regex' | 'semver' | 'sha' | 'string' | 'time' | 'ulid' | 'url' | 'uuid' | 'workdir';
|
|
17
|
-
/** Every kind usable as a `{{token}}` in fixture files (all but ref/regex/includes). */
|
|
18
|
-
declare const TOKEN_KINDS: readonly ["any", "base64", "date", "duration", "email", "float", "hex", "int", "ip", "iso8601", "number", "path", "port", "semver", "sha", "string", "time", "ulid", "url", "uuid", "workdir"];
|
|
19
|
-
/** A token kind valid inside a fixture `{{...}}` placeholder. */
|
|
20
|
-
type TokenKind = (typeof TOKEN_KINDS)[number];
|
|
21
|
-
/**
|
|
22
|
-
* A dynamic-value matcher. Created via the {@link match} factories — never
|
|
23
|
-
* constructed directly by user code.
|
|
24
|
-
*/
|
|
25
|
-
declare class Matcher {
|
|
26
|
-
/** For kind 'includes': the substring the actual value must contain. */
|
|
27
|
-
readonly includes?: string | undefined;
|
|
28
|
-
readonly kind: MatcherKind;
|
|
29
|
-
/** For kind 'ref': the capture to assert inequality against. */
|
|
30
|
-
readonly notRef?: string | undefined;
|
|
31
|
-
/** For kind 'ref': the capture name. */
|
|
32
|
-
readonly refName?: string | undefined;
|
|
33
|
-
/** For kind 'regex': the pattern the actual value must match. */
|
|
34
|
-
readonly regex?: RegExp | undefined;
|
|
35
|
-
constructor(kind: MatcherKind, options?: {
|
|
36
|
-
includes?: string | undefined;
|
|
37
|
-
notRef?: string | undefined;
|
|
38
|
-
refName?: string | undefined;
|
|
39
|
-
regex?: RegExp | undefined;
|
|
40
|
-
});
|
|
41
|
-
/** Placeholder-style rendering used in failure diffs and serialization. */
|
|
42
|
-
toString(): string;
|
|
43
|
-
toJSON(): string;
|
|
44
|
-
}
|
|
45
|
-
/** A no-argument matcher factory — what every typed entry of `match` is. */
|
|
46
|
-
type TypedMatcher = () => Matcher;
|
|
47
|
-
/**
|
|
48
|
-
* Dynamic-value matchers for structural comparisons — the code-side mirror of
|
|
49
|
-
* the `{{token}}` fixture grammar (CONVENTIONS D4).
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* await expect(result.table('users', { database: 'db' })).toMatchRows({
|
|
53
|
-
* columns: ['id', 'name'],
|
|
54
|
-
* rows: [[match.uuid(), 'Alice']],
|
|
55
|
-
* });
|
|
56
|
-
*/
|
|
57
|
-
/**
|
|
58
|
-
* The matcher roster: one no-argument factory per token kind, plus the three
|
|
59
|
-
* code-only forms the `{{token}}` grammar does not carry.
|
|
60
|
-
*/
|
|
61
|
-
type MatchFactories = Record<TokenKind, TypedMatcher> & {
|
|
62
|
-
includes: (substring: string) => Matcher;
|
|
63
|
-
ref: (name: string, options?: {
|
|
64
|
-
not?: string;
|
|
65
|
-
}) => Matcher;
|
|
66
|
-
regex: (regex: RegExp) => Matcher;
|
|
67
|
-
};
|
|
68
|
-
declare const match: MatchFactories;
|
|
69
|
-
/**
|
|
70
|
-
* Named captures recorded by `match.ref()` / `{{type#ref}}` placeholders.
|
|
71
|
-
* One scope lives on each spec result — every assertion chained off the same
|
|
72
|
-
* result shares it, and a new chain starts fresh.
|
|
73
|
-
*/
|
|
74
|
-
declare class CaptureScope {
|
|
75
|
-
private readonly values;
|
|
76
|
-
/**
|
|
77
|
-
* The exact working directory of the current spec, when the framework
|
|
78
|
-
* knows it (cli mode). Drives the `{{workdir}}` token / `match.workdir()`.
|
|
79
|
-
*/
|
|
80
|
-
workdir?: string | undefined;
|
|
81
|
-
constructor(workdir?: string);
|
|
82
|
-
get(name: string): unknown;
|
|
83
|
-
has(name: string): boolean;
|
|
84
|
-
set(name: string, value: unknown): void;
|
|
85
|
-
}
|
|
86
|
-
//#endregion
|
|
87
|
-
//#region src/specification/facets/_common/result/json.d.ts
|
|
88
|
-
/**
|
|
89
|
-
* Read-only accessor for a JSON payload parsed from a text stream (stdout).
|
|
90
|
-
*
|
|
91
|
-
* Lazily parses the JSON on first use; parse errors are deferred until the
|
|
92
|
-
* value is read so that callers can still read the raw stream text without
|
|
93
|
-
* triggering a throw.
|
|
94
|
-
*
|
|
95
|
-
* ANSI escape sequences are stripped by default before `JSON.parse`
|
|
96
|
-
* (CONVENTIONS D6). When a `transform` is configured on the spec runner, it
|
|
97
|
-
* runs on the stripped text — an escape hatch for application noise not
|
|
98
|
-
* covered by the `{{token}}` grammar.
|
|
99
|
-
*
|
|
100
|
-
* Assertions go through `expect()`: `expect(result.json).toMatch('name.json')`
|
|
101
|
-
* (deep-equal against `_expected/<name>`, with `{{placeholder}}` support).
|
|
102
|
-
*/
|
|
103
|
-
declare class JsonAccessor {
|
|
104
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
105
|
-
readonly captures: CaptureScope;
|
|
106
|
-
/** @internal */
|
|
107
|
-
private readonly rawText;
|
|
108
|
-
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
109
|
-
readonly testDir: string;
|
|
110
|
-
/** @internal */
|
|
111
|
-
private readonly transform?;
|
|
112
|
-
constructor(rawText: string, testDir: string, transform?: (text: string) => string, captures?: CaptureScope);
|
|
113
|
-
/** The parsed JSON value. Throws if the text is not valid JSON. */
|
|
114
|
-
get value(): unknown;
|
|
115
|
-
}
|
|
116
|
-
//#endregion
|
|
117
5
|
//#region src/specification/contracts/types.d.ts
|
|
118
6
|
/**
|
|
119
7
|
* The observed outgoing request, reduced to what contract matchers inspect.
|
|
@@ -157,7 +45,11 @@ type ContractRequest = {
|
|
|
157
45
|
type ContractResponse = {
|
|
158
46
|
/** HTTP status code (default: 200). */
|
|
159
47
|
status?: number;
|
|
160
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Response body — an object is JSON, a string is text, `null`/`undefined`
|
|
50
|
+
* is empty, and a {@link StreamBody} (built by `http.stream()` /
|
|
51
|
+
* `http.sse()`) arrives in pieces.
|
|
52
|
+
*/
|
|
161
53
|
body: unknown;
|
|
162
54
|
/** Response headers. */
|
|
163
55
|
headers?: Record<string, string> | undefined;
|
|
@@ -174,6 +66,31 @@ type ContractResponse = {
|
|
|
174
66
|
*/
|
|
175
67
|
transport?: 'network-error' | undefined;
|
|
176
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* The marker that tells a STREAMED body from a value that happens to be an
|
|
71
|
+
* object. A body reaches three different engines (msw's server, msw's worker,
|
|
72
|
+
* the `node:http` stub) and each of them serialises "an object" as JSON, so a
|
|
73
|
+
* stream declared as a plain value arrives as `{}`. The tag is what each
|
|
74
|
+
* engine recognises before it reaches for `JSON.stringify`.
|
|
75
|
+
*/
|
|
76
|
+
declare const STREAM_BODY = "@jterrazz/test:stream";
|
|
77
|
+
/**
|
|
78
|
+
* A body that arrives in pieces — what `http.stream()` and `http.sse()` build.
|
|
79
|
+
*
|
|
80
|
+
* The chunks are written in order, `delayBetweenChunks` milliseconds apart,
|
|
81
|
+
* and the connection closes after the last one. It is the chunks and their
|
|
82
|
+
* ORDER that a streaming subject is specified against: a client that renders
|
|
83
|
+
* tokens as they land, or one that reconnects on a half-read body.
|
|
84
|
+
*/
|
|
85
|
+
type StreamBody = {
|
|
86
|
+
/** The pieces, written in order. */
|
|
87
|
+
chunks: readonly string[];
|
|
88
|
+
/** The `content-type` the stream is served under. */
|
|
89
|
+
contentType: string;
|
|
90
|
+
/** Milliseconds between two chunks. Default 0 — everything at once. */
|
|
91
|
+
delayBetweenChunks: number;
|
|
92
|
+
kind: typeof STREAM_BODY;
|
|
93
|
+
};
|
|
177
94
|
/**
|
|
178
95
|
* A dynamic response: computed from the observed request at the moment the
|
|
179
96
|
* contract is served, rather than fixed ahead of time. Handed the same
|
|
@@ -272,6 +189,144 @@ declare function defineContract(contract: Contract): Contract;
|
|
|
272
189
|
*/
|
|
273
190
|
declare function defineContracts(...items: ContractInput[]): Contracts;
|
|
274
191
|
//#endregion
|
|
192
|
+
//#region src/integrations/msw/scope.d.ts
|
|
193
|
+
/**
|
|
194
|
+
* One block's worth of declared network. Disposed at the end of the scope that
|
|
195
|
+
* declared it: the handlers are dropped, and an unmatched request is thrown.
|
|
196
|
+
*/
|
|
197
|
+
type InterceptScope = AsyncDisposable;
|
|
198
|
+
/**
|
|
199
|
+
* The module-scope network double. Resolves once the engine is listening —
|
|
200
|
+
* which is why it is a promise, and why the canonical form awaits it:
|
|
201
|
+
*
|
|
202
|
+
* ```typescript
|
|
203
|
+
* await using _ = await intercept(http.get(url), http.json({ ok: true }));
|
|
204
|
+
* ```
|
|
205
|
+
*
|
|
206
|
+
* Without that `await` the subject could reach the real network before the
|
|
207
|
+
* first handler is in place, so the shape refuses the racy spelling: a promise
|
|
208
|
+
* is not an `AsyncDisposable`, and the compiler says so.
|
|
209
|
+
*/
|
|
210
|
+
type Intercept = ((contracts: ContractInput) => Promise<InterceptScope>) & ((request: ContractRequest, response: ContractResponder | ContractResponse) => Promise<InterceptScope>);
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/specification/matching/match.d.ts
|
|
213
|
+
/**
|
|
214
|
+
* Code-side dynamic-value matchers (`match.*`) and the capture scope shared
|
|
215
|
+
* by every assertion of a single spec execution.
|
|
216
|
+
*
|
|
217
|
+
* Matchers are used inside `toMatchRows` rows and any structural comparison;
|
|
218
|
+
* their file-side twins are the `{{placeholder}}` forms in `_expected/*.http`
|
|
219
|
+
* fixtures (body AND headers), `_expected/*.json` files, and text snapshots
|
|
220
|
+
* (`_expected/*.txt`). One grammar, one vocabulary (CONVENTIONS D4).
|
|
221
|
+
*/
|
|
222
|
+
/** The frozen token vocabulary (CONVENTIONS D4) plus the code-only kinds. */
|
|
223
|
+
type MatcherKind = 'any' | 'base64' | 'date' | 'duration' | 'email' | 'float' | 'hex' | 'includes' | 'int' | 'ip' | 'iso8601' | 'number' | 'path' | 'port' | 'ref' | 'regex' | 'semver' | 'sha' | 'string' | 'time' | 'ulid' | 'url' | 'uuid' | 'workdir';
|
|
224
|
+
/** Every kind usable as a `{{token}}` in fixture files (all but ref/regex/includes). */
|
|
225
|
+
declare const TOKEN_KINDS: readonly ["any", "base64", "date", "duration", "email", "float", "hex", "int", "ip", "iso8601", "number", "path", "port", "semver", "sha", "string", "time", "ulid", "url", "uuid", "workdir"];
|
|
226
|
+
/** A token kind valid inside a fixture `{{...}}` placeholder. */
|
|
227
|
+
type TokenKind = (typeof TOKEN_KINDS)[number];
|
|
228
|
+
/**
|
|
229
|
+
* A dynamic-value matcher. Created via the {@link match} factories — never
|
|
230
|
+
* constructed directly by user code.
|
|
231
|
+
*/
|
|
232
|
+
declare class Matcher {
|
|
233
|
+
/** For kind 'includes': the substring the actual value must contain. */
|
|
234
|
+
readonly includes?: string | undefined;
|
|
235
|
+
readonly kind: MatcherKind;
|
|
236
|
+
/** For kind 'ref': the capture to assert inequality against. */
|
|
237
|
+
readonly notRef?: string | undefined;
|
|
238
|
+
/** For kind 'ref': the capture name. */
|
|
239
|
+
readonly refName?: string | undefined;
|
|
240
|
+
/** For kind 'regex': the pattern the actual value must match. */
|
|
241
|
+
readonly regex?: RegExp | undefined;
|
|
242
|
+
constructor(kind: MatcherKind, options?: {
|
|
243
|
+
includes?: string | undefined;
|
|
244
|
+
notRef?: string | undefined;
|
|
245
|
+
refName?: string | undefined;
|
|
246
|
+
regex?: RegExp | undefined;
|
|
247
|
+
});
|
|
248
|
+
/** Placeholder-style rendering used in failure diffs and serialization. */
|
|
249
|
+
toString(): string;
|
|
250
|
+
toJSON(): string;
|
|
251
|
+
}
|
|
252
|
+
/** A no-argument matcher factory — what every typed entry of `match` is. */
|
|
253
|
+
type TypedMatcher = () => Matcher;
|
|
254
|
+
/**
|
|
255
|
+
* Dynamic-value matchers for structural comparisons — the code-side mirror of
|
|
256
|
+
* the `{{token}}` fixture grammar (CONVENTIONS D4).
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* await expect(result.table('users', { database: 'db' })).toMatchRows({
|
|
260
|
+
* columns: ['id', 'name'],
|
|
261
|
+
* rows: [[match.uuid(), 'Alice']],
|
|
262
|
+
* });
|
|
263
|
+
*/
|
|
264
|
+
/**
|
|
265
|
+
* The matcher roster: one no-argument factory per token kind, plus the three
|
|
266
|
+
* code-only forms the `{{token}}` grammar does not carry.
|
|
267
|
+
*/
|
|
268
|
+
type MatchFactories = Record<TokenKind, TypedMatcher> & {
|
|
269
|
+
includes: (substring: string) => Matcher;
|
|
270
|
+
ref: (name: string, options?: {
|
|
271
|
+
not?: string;
|
|
272
|
+
}) => Matcher;
|
|
273
|
+
regex: (regex: RegExp) => Matcher;
|
|
274
|
+
};
|
|
275
|
+
declare const match: MatchFactories;
|
|
276
|
+
/**
|
|
277
|
+
* Named captures recorded by `match.ref()` / `{{type#ref}}` placeholders.
|
|
278
|
+
* One scope lives on each spec result — every assertion chained off the same
|
|
279
|
+
* result shares it, and a new chain starts fresh.
|
|
280
|
+
*/
|
|
281
|
+
declare class CaptureScope {
|
|
282
|
+
private readonly values;
|
|
283
|
+
/**
|
|
284
|
+
* The exact working directory of the current spec, when the framework
|
|
285
|
+
* knows it (cli mode). Drives the `{{workdir}}` token / `match.workdir()`.
|
|
286
|
+
*/
|
|
287
|
+
workdir?: string | undefined;
|
|
288
|
+
constructor(workdir?: string);
|
|
289
|
+
get(name: string): unknown;
|
|
290
|
+
has(name: string): boolean;
|
|
291
|
+
set(name: string, value: unknown): void;
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/specification/facets/_common/result/json.d.ts
|
|
295
|
+
/**
|
|
296
|
+
* Read-only accessor for a JSON payload parsed from a text stream (stdout).
|
|
297
|
+
*
|
|
298
|
+
* Lazily parses the JSON on first use; parse errors are deferred until the
|
|
299
|
+
* value is read so that callers can still read the raw stream text without
|
|
300
|
+
* triggering a throw.
|
|
301
|
+
*
|
|
302
|
+
* ANSI escape sequences are stripped by default before `JSON.parse`
|
|
303
|
+
* (CONVENTIONS D6). When a `transform` is configured on the spec runner, it
|
|
304
|
+
* runs on the stripped text — an escape hatch for application noise not
|
|
305
|
+
* covered by the `{{token}}` grammar.
|
|
306
|
+
*
|
|
307
|
+
* Assertions go through `expect()`: `expect(result.json).toMatch('name.json')`
|
|
308
|
+
* (deep-equal against `_expected/<name>`, with `{{placeholder}}` support).
|
|
309
|
+
*/
|
|
310
|
+
declare class JsonAccessor<Value = unknown> {
|
|
311
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
312
|
+
readonly captures: CaptureScope;
|
|
313
|
+
/** @internal */
|
|
314
|
+
private readonly rawText;
|
|
315
|
+
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
316
|
+
readonly testDir: string;
|
|
317
|
+
/** @internal */
|
|
318
|
+
private readonly transform?;
|
|
319
|
+
constructor(rawText: string, testDir: string, transform?: (text: string) => string, captures?: CaptureScope);
|
|
320
|
+
/**
|
|
321
|
+
* The parsed JSON value. Throws if the text is not valid JSON.
|
|
322
|
+
*
|
|
323
|
+
* `Value` is `unknown` unless the subject that produced this accessor
|
|
324
|
+
* knows better — `.call<T>()` does, and hands its T straight through so a
|
|
325
|
+
* one-field reading needs no cast in the test.
|
|
326
|
+
*/
|
|
327
|
+
get value(): Value;
|
|
328
|
+
}
|
|
329
|
+
//#endregion
|
|
275
330
|
//#region src/specification/ports/browser.port.d.ts
|
|
276
331
|
/**
|
|
277
332
|
* The landmark roles — the standard set of page regions, and the only
|
|
@@ -424,6 +479,11 @@ type BrowserOpenOptions = {
|
|
|
424
479
|
* and the boundary of the `external` policy.
|
|
425
480
|
*/
|
|
426
481
|
baseUrl: string;
|
|
482
|
+
/**
|
|
483
|
+
* Pin the page's own `Date` at this instant before the first byte is
|
|
484
|
+
* parsed — the calendar the site's scripts read. `.clock()` on the chain.
|
|
485
|
+
*/
|
|
486
|
+
clock?: string | undefined;
|
|
427
487
|
/**
|
|
428
488
|
* Cross-origin request policy. `'block'` aborts every request leaving
|
|
429
489
|
* the site under test (analytics, CDNs) — the browser-side analog of
|
|
@@ -978,6 +1038,62 @@ type LiterateRunFlags = {
|
|
|
978
1038
|
frozen?: boolean | undefined;
|
|
979
1039
|
};
|
|
980
1040
|
//#endregion
|
|
1041
|
+
//#region src/specification/facets/integration/result.d.ts
|
|
1042
|
+
/** What `.call()` produced: the value it returned, or the error it threw. */
|
|
1043
|
+
type CallOutcome = {
|
|
1044
|
+
error: unknown;
|
|
1045
|
+
threw: boolean;
|
|
1046
|
+
value: unknown;
|
|
1047
|
+
};
|
|
1048
|
+
type CallResultOptions = BaseResultOptions & {
|
|
1049
|
+
outcome: CallOutcome;
|
|
1050
|
+
};
|
|
1051
|
+
/**
|
|
1052
|
+
* What `result.value` is, decided by what the call returned: a string reads as
|
|
1053
|
+
* a stream, anything else as JSON. `.call<T>()` already knows T, so the test
|
|
1054
|
+
* reads one field (`result.value.value.ok`) without narrowing a union first.
|
|
1055
|
+
*/
|
|
1056
|
+
type CallValue<Returned> = Returned extends string ? TextAccessor : JsonAccessor<Returned>;
|
|
1057
|
+
/**
|
|
1058
|
+
* The result of an in-process call — the integration facet's whole surface.
|
|
1059
|
+
*
|
|
1060
|
+
* Two readings, never both: `value` is what the call RETURNED, `error` is
|
|
1061
|
+
* what it THREW. A module that refuses is specified the same way a module
|
|
1062
|
+
* that answers is, and neither needs a `try`/`catch` in the test — which is
|
|
1063
|
+
* the shape that used to make a refusal spec three times the size of the
|
|
1064
|
+
* happy one, and the shape that quietly passes when nothing throws at all.
|
|
1065
|
+
*
|
|
1066
|
+
* Both are the package's ordinary accessors, so the golden mechanism reaches
|
|
1067
|
+
* them, and `Returned` is what `.call<T>()` returned — `result.value.value` is
|
|
1068
|
+
* that type, not `unknown`, so a one-field reading needs no golden file: `toMatch('<name>.json'|'<name>.txt')`, the `{{token}}` grammar,
|
|
1069
|
+
* `{ frozen }` and `TEST_UPDATE=1` all work here exactly as on an HTTP
|
|
1070
|
+
* response or a command's stdout.
|
|
1071
|
+
*/
|
|
1072
|
+
declare class CallResult<Returned = unknown> extends BaseResult {
|
|
1073
|
+
private readonly outcome;
|
|
1074
|
+
constructor(options: CallResultOptions);
|
|
1075
|
+
/**
|
|
1076
|
+
* What the call THREW, as text — the message of an `Error`, the value
|
|
1077
|
+
* itself when something else was thrown, and empty when it returned.
|
|
1078
|
+
*
|
|
1079
|
+
* Empty rather than absent on purpose: `await expect(result.error).toBeEmpty()`
|
|
1080
|
+
* is how a spec says "and it did not refuse" — awaited, because the
|
|
1081
|
+
* matcher answers a promise on every subject it takes, and a dropped one
|
|
1082
|
+
* passes the test while the failure surfaces as an unhandled rejection
|
|
1083
|
+
* (D2 refuses the bare form).
|
|
1084
|
+
*/
|
|
1085
|
+
get error(): TextAccessor;
|
|
1086
|
+
/**
|
|
1087
|
+
* What the call RETURNED. A string reads as a stream (`<name>.txt`),
|
|
1088
|
+
* anything else as JSON (`<name>.json`) — the same two subjects every
|
|
1089
|
+
* other facet's results are built from.
|
|
1090
|
+
*
|
|
1091
|
+
* A call that threw returns the empty value: what it produced is
|
|
1092
|
+
* {@link CallResult.error}.
|
|
1093
|
+
*/
|
|
1094
|
+
get value(): CallValue<Returned>;
|
|
1095
|
+
}
|
|
1096
|
+
//#endregion
|
|
981
1097
|
//#region src/specification/facets/mobile/result.d.ts
|
|
982
1098
|
/** Result from a `.open()` action — the screen as the device saw it, final state. */
|
|
983
1099
|
declare class ScreenResult extends BaseResult {
|
|
@@ -1194,11 +1310,23 @@ type SpecificationConfig = {
|
|
|
1194
1310
|
* facet factories auto-populate this when `dockerConfig` is present.
|
|
1195
1311
|
*/
|
|
1196
1312
|
dockerTestRunId?: string | undefined;
|
|
1313
|
+
/**
|
|
1314
|
+
* Environment applied to every run of the declared binary
|
|
1315
|
+
* (`specification.cli({ defaults })`) — under a chain's own `.env()` and
|
|
1316
|
+
* under a document's `env:`, both of which win.
|
|
1317
|
+
*/
|
|
1318
|
+
defaultEnv?: CliEnv | undefined;
|
|
1197
1319
|
/**
|
|
1198
1320
|
* Named environment SETS a spec document may name by bare word
|
|
1199
1321
|
* (`env: frozen`). Declared once per app in `specification.cli()`.
|
|
1200
1322
|
*/
|
|
1201
1323
|
envSets?: Record<string, CliEnv> | undefined;
|
|
1324
|
+
/**
|
|
1325
|
+
* When set, `.clock()` is unavailable on this runner and throws this
|
|
1326
|
+
* reason immediately (compose mode — the calendar it would pin is this
|
|
1327
|
+
* process's, and the app runs in a container).
|
|
1328
|
+
*/
|
|
1329
|
+
clockDisabledReason?: string | undefined;
|
|
1202
1330
|
/**
|
|
1203
1331
|
* When set, `.intercept()` is unavailable on this runner and throws this
|
|
1204
1332
|
* reason immediately (compose mode — MSW is in-process, CONVENTIONS I3).
|
|
@@ -1240,6 +1368,8 @@ type InterceptMethod<T> = ((contracts: ContractInput) => T) & ((request: Contrac
|
|
|
1240
1368
|
* of the declared services record that are databases.
|
|
1241
1369
|
*/
|
|
1242
1370
|
type ApiSpecification<DatabaseKey extends string = string> = {
|
|
1371
|
+
/** Pin the app's `Date` at `iso` for this chain — the calendar the app reads. */
|
|
1372
|
+
clock: (iso: string) => ApiSpecification<DatabaseKey>;
|
|
1243
1373
|
/** Set HTTP headers for the request. Multiple calls merge. */
|
|
1244
1374
|
headers: (headers: Record<string, string>) => ApiSpecification<DatabaseKey>;
|
|
1245
1375
|
/** Declare outgoing calls — a contract, a list, a composite, or an inline request + response pair. */
|
|
@@ -1264,6 +1394,8 @@ type ApiSpecification<DatabaseKey extends string = string> = {
|
|
|
1264
1394
|
* Jobs run in-process by definition (CONVENTIONS A5/A8).
|
|
1265
1395
|
*/
|
|
1266
1396
|
type JobsSpecification<DatabaseKey extends string = string> = {
|
|
1397
|
+
/** Pin the job's `Date` at `iso` for this chain — the calendar the job reads. */
|
|
1398
|
+
clock: (iso: string) => JobsSpecification<DatabaseKey>;
|
|
1267
1399
|
/** Declare outgoing calls — a contract, a list, a composite, or an inline request + response pair. */
|
|
1268
1400
|
intercept: InterceptMethod<JobsSpecification<DatabaseKey>>;
|
|
1269
1401
|
/** Queue a SQL seed file from `_seeds/` to run before the action. */
|
|
@@ -1273,6 +1405,31 @@ type JobsSpecification<DatabaseKey extends string = string> = {
|
|
|
1273
1405
|
/** Execute the named job registered via the `jobs` option and resolve with the result. */
|
|
1274
1406
|
trigger: (name: string) => Promise<BaseResult>;
|
|
1275
1407
|
};
|
|
1408
|
+
/**
|
|
1409
|
+
* The `integration` facet — the in-process chain handed out by
|
|
1410
|
+
* `specification.integration()`.
|
|
1411
|
+
*
|
|
1412
|
+
* The subject is a MODULE, not an entry: no HTTP, no binary, no page. What
|
|
1413
|
+
* makes it a spec rather than a module test is what it stands on — real
|
|
1414
|
+
* services, declared contracts, a golden — so it takes the same setups as
|
|
1415
|
+
* every other facet and ends on the one action a module has: being called.
|
|
1416
|
+
*/
|
|
1417
|
+
type IntegrationSpecification<Services extends Record<string, unknown> = Record<string, unknown>, DatabaseKey extends string = string> = {
|
|
1418
|
+
/** Pin the module's `Date` at `iso` for this chain. */
|
|
1419
|
+
clock: (iso: string) => IntegrationSpecification<Services, DatabaseKey>;
|
|
1420
|
+
/** Declare outgoing calls — a contract, a list, a composite, or an inline request + response pair. */
|
|
1421
|
+
intercept: InterceptMethod<IntegrationSpecification<Services, DatabaseKey>>;
|
|
1422
|
+
/** Queue a SQL seed file from `_seeds/` to run before the call. */
|
|
1423
|
+
seed: (file: string, options?: {
|
|
1424
|
+
database?: DatabaseKey;
|
|
1425
|
+
}) => IntegrationSpecification<Services, DatabaseKey>;
|
|
1426
|
+
/**
|
|
1427
|
+
* Call the module and resolve with what it produced. The started services
|
|
1428
|
+
* record is handed in, so the subject is constructed with the real
|
|
1429
|
+
* connection strings rather than with a double.
|
|
1430
|
+
*/
|
|
1431
|
+
call: <T>(subject: (services: Services) => Promise<T> | T) => Promise<CallResult<T>>;
|
|
1432
|
+
};
|
|
1276
1433
|
/**
|
|
1277
1434
|
* The `cli` facet — command chain entry handed out by `specification.cli()`.
|
|
1278
1435
|
* Setup methods chain; `.exec()` is the single terminal action (CONVENTIONS
|
|
@@ -1317,6 +1474,11 @@ type CliSpecification<DatabaseKey extends string = string> = {
|
|
|
1317
1474
|
* performs one raw HTTP exchange and never follows redirects.
|
|
1318
1475
|
*/
|
|
1319
1476
|
type WebsiteSpecification = {
|
|
1477
|
+
/**
|
|
1478
|
+
* Pin the PAGE's `Date` at `iso` for this chain — what the site's own
|
|
1479
|
+
* scripts read. A `.fetch()` opens no page and refuses the setup.
|
|
1480
|
+
*/
|
|
1481
|
+
clock: (iso: string) => WebsiteSpecification;
|
|
1320
1482
|
/** Set HTTP headers for the exchange (incl. User-Agent overrides). Multiple calls merge. */
|
|
1321
1483
|
headers: (headers: Record<string, string>) => WebsiteSpecification;
|
|
1322
1484
|
/**
|
|
@@ -1863,6 +2025,28 @@ type HttpContractFilter = {
|
|
|
1863
2025
|
/** Query-param subset. string = exact value, RegExp = `test()`. */
|
|
1864
2026
|
query?: Record<string, RegExp | string>;
|
|
1865
2027
|
};
|
|
2028
|
+
/** Init options for a streamed reply. */
|
|
2029
|
+
type HttpStreamInit = {
|
|
2030
|
+
/** The `content-type` the stream is served under. Default `application/octet-stream`. */
|
|
2031
|
+
contentType?: string;
|
|
2032
|
+
/** Milliseconds between two chunks. Default 0 — every chunk at once. */
|
|
2033
|
+
delay?: number;
|
|
2034
|
+
/** Response headers, merged over the builder's own. */
|
|
2035
|
+
headers?: Record<string, string>;
|
|
2036
|
+
/** HTTP status code. Default 200. */
|
|
2037
|
+
status?: number;
|
|
2038
|
+
};
|
|
2039
|
+
/** One server-sent event, as `http.sse()` frames it. */
|
|
2040
|
+
type SseEvent = {
|
|
2041
|
+
/** The `data:` payload. An object is serialised as JSON; a string is sent as-is. */
|
|
2042
|
+
data: unknown;
|
|
2043
|
+
/** The `event:` name. Omitted for an unnamed (default `message`) event. */
|
|
2044
|
+
event?: string;
|
|
2045
|
+
/** The `id:` the client echoes as `Last-Event-ID` when it reconnects. */
|
|
2046
|
+
id?: string;
|
|
2047
|
+
/** The `retry:` hint, in milliseconds. */
|
|
2048
|
+
retry?: number;
|
|
2049
|
+
};
|
|
1866
2050
|
/** Init options shared by the response builders. */
|
|
1867
2051
|
type HttpResponseInit = {
|
|
1868
2052
|
/** Delay in ms before responding (for timeout testing). */
|
|
@@ -1902,6 +2086,27 @@ declare const http: {
|
|
|
1902
2086
|
* server that is not running fails it. `fetch` rejects; no status is served.
|
|
1903
2087
|
*/
|
|
1904
2088
|
unreachable(): ContractResponse;
|
|
2089
|
+
/**
|
|
2090
|
+
* Response: a body that arrives in PIECES, written in order.
|
|
2091
|
+
*
|
|
2092
|
+
* A subject that reads a response as it lands — a token-by-token render, a
|
|
2093
|
+
* progress bar, a reconnect on a half-read body — is specified by the
|
|
2094
|
+
* chunks and their order, which a single serialised body cannot state.
|
|
2095
|
+
* Honoured by all three engines: msw's server, msw's worker, and the
|
|
2096
|
+
* `node:http` stub the website and mobile facets serve from.
|
|
2097
|
+
*
|
|
2098
|
+
* @example
|
|
2099
|
+
* http.stream(['Hel', 'lo, ', 'world'], { contentType: 'text/plain', delay: 10 })
|
|
2100
|
+
*/
|
|
2101
|
+
stream(chunks: readonly string[], init?: HttpStreamInit): ContractResponse;
|
|
2102
|
+
/**
|
|
2103
|
+
* Response: a server-sent event stream — one chunk per event, framed the
|
|
2104
|
+
* way an `EventSource` reads them, served as `text/event-stream`.
|
|
2105
|
+
*
|
|
2106
|
+
* @example
|
|
2107
|
+
* http.sse([{ data: { token: 'Hel' } }, { data: { token: 'lo' } }, { event: 'done', data: '' }])
|
|
2108
|
+
*/
|
|
2109
|
+
sse(events: readonly SseEvent[], init?: Omit<HttpStreamInit, "contentType">): ContractResponse;
|
|
1905
2110
|
/** Response: a text body, served as `text/plain` (200 by default). */
|
|
1906
2111
|
text(body: string, init?: HttpResponseInit): ContractResponse;
|
|
1907
2112
|
};
|
|
@@ -1965,13 +2170,35 @@ declare const openai: {
|
|
|
1965
2170
|
};
|
|
1966
2171
|
//#endregion
|
|
1967
2172
|
//#region src/vitest/mock-of.d.ts
|
|
1968
|
-
/**
|
|
1969
|
-
type
|
|
2173
|
+
/** How much of the port the double answers for. */
|
|
2174
|
+
type MockOfOptions = {
|
|
2175
|
+
/**
|
|
2176
|
+
* Deep (the default): reading a member that was never stubbed hands back
|
|
2177
|
+
* another double, however far down the port nests. `false` gives the flat
|
|
2178
|
+
* proxy — every member of `T` and nothing beneath them, which is what a
|
|
2179
|
+
* port with a nested namespace wants when the test only ever touches the
|
|
2180
|
+
* first level.
|
|
2181
|
+
*/
|
|
2182
|
+
deep?: boolean;
|
|
2183
|
+
};
|
|
1970
2184
|
/**
|
|
1971
|
-
*
|
|
1972
|
-
*
|
|
1973
|
-
|
|
1974
|
-
|
|
2185
|
+
* A typed double for an injected port — the fourth rung of the doubles ladder
|
|
2186
|
+
* (docs/12-conventions.md), and the only one a module test may build itself.
|
|
2187
|
+
*
|
|
2188
|
+
* The type argument is the port: `T` is constrained to `object` and has no
|
|
2189
|
+
* default, so a double is always asked for something. A call with no argument
|
|
2190
|
+
* answers for `object`, which satisfies no port and fails where it is used.
|
|
2191
|
+
*
|
|
2192
|
+
* @example
|
|
2193
|
+
* const gateway = mockOf<PaymentGateway>(); // deep
|
|
2194
|
+
* const clock = mockOf<ClockPort>({ deep: false }); // flat
|
|
2195
|
+
*/
|
|
2196
|
+
declare function mockOf<T extends object>(options: {
|
|
2197
|
+
deep: false;
|
|
2198
|
+
}): MockProxy<T>;
|
|
2199
|
+
declare function mockOf<T extends object>(options?: MockOfOptions): DeepMockProxy<T>;
|
|
2200
|
+
/** The factory's own shape — kept as a name so a consumer can annotate with it. */
|
|
2201
|
+
type MockPort = typeof mockOf;
|
|
1975
2202
|
//#endregion
|
|
1976
2203
|
//#region src/vitest/mock-of-date.d.ts
|
|
1977
2204
|
/** Interface for freezing and resetting the global Date in tests. */
|
|
@@ -1987,6 +2214,115 @@ type MockDatePort = {
|
|
|
1987
2214
|
*/
|
|
1988
2215
|
declare const mockOfDate: MockDatePort;
|
|
1989
2216
|
//#endregion
|
|
2217
|
+
//#region src/vitest/clock.d.ts
|
|
2218
|
+
/**
|
|
2219
|
+
* The one time primitive.
|
|
2220
|
+
*
|
|
2221
|
+
* Time is the determinism the framework owns (CONVENTIONS D16): a test that
|
|
2222
|
+
* samples `Date.now()` and asserts on what comes back is a test that fails on
|
|
2223
|
+
* the next leap second, and a test that reaches for `vi.useFakeTimers()` owns a
|
|
2224
|
+
* teardown it will one day forget. `clock` is both answers in one name — the
|
|
2225
|
+
* instant is stated, and the scope restores itself.
|
|
2226
|
+
*
|
|
2227
|
+
* ```typescript
|
|
2228
|
+
* using _ = clock.at('2026-03-04T09:30:00Z'); // Date is pinned until the scope ends
|
|
2229
|
+
* ```
|
|
2230
|
+
*
|
|
2231
|
+
* `at()` takes the CALENDAR and leaves the scheduler alone: a UI framework's
|
|
2232
|
+
* own loop, a `setTimeout` inside the subject and every promise still resolve
|
|
2233
|
+
* on their own. `run()` takes the scheduler too, for a subject whose behaviour
|
|
2234
|
+
* IS the passage of time — a debounce, a retry backoff, a poll.
|
|
2235
|
+
*/
|
|
2236
|
+
/** A pinned clock — released when the `using` scope that declared it ends. */
|
|
2237
|
+
type PinnedClock = Disposable;
|
|
2238
|
+
declare const clock: {
|
|
2239
|
+
/**
|
|
2240
|
+
* Move the pinned clock forward by `ms` — and, under {@link clock.run},
|
|
2241
|
+
* fire every callback that came due, awaiting what each of them started.
|
|
2242
|
+
*
|
|
2243
|
+
* Only a pinned clock moves: called with no clock taken, it says so rather
|
|
2244
|
+
* than silently doing nothing to the real one.
|
|
2245
|
+
*/
|
|
2246
|
+
advance(ms: number): Promise<void>;
|
|
2247
|
+
/**
|
|
2248
|
+
* Pin `Date` at `iso` for the current scope. `Date` ONLY: the scheduler
|
|
2249
|
+
* stays real, so a subject that awaits, renders or polls keeps running.
|
|
2250
|
+
*
|
|
2251
|
+
* @example
|
|
2252
|
+
* using _ = clock.at('2026-03-04T09:30:00Z');
|
|
2253
|
+
* expect(stampedAt()).toBe('2026-03-04T09:30:00.000Z');
|
|
2254
|
+
*/
|
|
2255
|
+
at(iso: string): PinnedClock;
|
|
2256
|
+
/**
|
|
2257
|
+
* Take the scheduler as well as the calendar: `setTimeout`, `setInterval`
|
|
2258
|
+
* and their friends queue instead of firing, and {@link clock.advance} is
|
|
2259
|
+
* what makes them due. For a subject whose behaviour IS elapsed time — a
|
|
2260
|
+
* debounce, a retry backoff, a poll — where waiting for real would be the
|
|
2261
|
+
* arbitrary sleep J2 forbids.
|
|
2262
|
+
*
|
|
2263
|
+
* @example
|
|
2264
|
+
* using _ = clock.run('2026-03-04T09:30:00Z');
|
|
2265
|
+
* const pending = retrying(failsTwice);
|
|
2266
|
+
* await clock.advance(5_000);
|
|
2267
|
+
* await expect(pending).resolves.toBe('ok');
|
|
2268
|
+
*/
|
|
2269
|
+
run(iso?: string): PinnedClock;
|
|
2270
|
+
};
|
|
2271
|
+
//#endregion
|
|
2272
|
+
//#region src/specification/assertions/required.d.ts
|
|
2273
|
+
/**
|
|
2274
|
+
* The value, or a failure that says what was missing and why it mattered.
|
|
2275
|
+
*
|
|
2276
|
+
* A test reaching into a captured structure — a row, a header, the third
|
|
2277
|
+
* element of a list — meets `T | undefined` and has to do something about it.
|
|
2278
|
+
* The two things it usually does are both wrong: `!` erases the question, and
|
|
2279
|
+
* an `if (!x) throw new Error('missing')` re-states the same three lines in
|
|
2280
|
+
* every repository (the estate had two hand-written copies of exactly this).
|
|
2281
|
+
* `required` is that gesture with the reason attached, so the failure reads as
|
|
2282
|
+
* a sentence rather than as a `TypeError` two frames further down.
|
|
2283
|
+
*
|
|
2284
|
+
* @example
|
|
2285
|
+
* const id = required(result.response.body.id, 'the creation reply carries the new id');
|
|
2286
|
+
*/
|
|
2287
|
+
declare function required<T>(value: null | T | undefined, why: string): T;
|
|
2288
|
+
//#endregion
|
|
2289
|
+
//#region src/specification/assertions/wait-until.d.ts
|
|
2290
|
+
/** How long to wait, and how often to look. */
|
|
2291
|
+
type WaitUntilOptions = {
|
|
2292
|
+
/** Milliseconds between two readings. Default 50. */
|
|
2293
|
+
interval?: number;
|
|
2294
|
+
/** How long the condition has to become true. Default 5 000 ms. */
|
|
2295
|
+
timeout?: number;
|
|
2296
|
+
/** What the condition is, named in the failure. Default: the predicate's source. */
|
|
2297
|
+
why?: string;
|
|
2298
|
+
};
|
|
2299
|
+
/**
|
|
2300
|
+
* Wait until a condition holds — the sanctioned answer to "the thing I am
|
|
2301
|
+
* specifying is not ready yet".
|
|
2302
|
+
*
|
|
2303
|
+
* A spec that sleeps for a guessed duration is a spec that is slow on a fast
|
|
2304
|
+
* machine and flaky on a slow one, which is why J2 refuses `setTimeout` under
|
|
2305
|
+
* `specs/`. Every facet that drives something already waits on a CONDITION —
|
|
2306
|
+
* a served page, a banner on stdout, an element the visitor can see — and this
|
|
2307
|
+
* is that primitive where no facet owns the waiting: a background write to
|
|
2308
|
+
* land, a queue to drain, a file to appear.
|
|
2309
|
+
*
|
|
2310
|
+
* The failure names the condition and how long it was given, so a timeout is a
|
|
2311
|
+
* sentence rather than "expected true, got false".
|
|
2312
|
+
*
|
|
2313
|
+
* The budget is measured on `performance.now()`, not on `Date` — `clock.at()`
|
|
2314
|
+
* freezes the calendar, and a deadline read off a frozen calendar never
|
|
2315
|
+
* arrives: the poll would run until vitest killed the test with a sentence
|
|
2316
|
+
* about the file rather than about the condition.
|
|
2317
|
+
*
|
|
2318
|
+
* Not for a subject under `clock.run()`: there the scheduler is the test's, and
|
|
2319
|
+
* `clock.advance(ms)` is what makes time pass.
|
|
2320
|
+
*
|
|
2321
|
+
* @example
|
|
2322
|
+
* await waitUntil(() => outbox.length === 1, { why: 'the job wrote its one message' });
|
|
2323
|
+
*/
|
|
2324
|
+
declare function waitUntil(predicate: () => boolean | Promise<boolean>, options?: WaitUntilOptions): Promise<void>;
|
|
2325
|
+
//#endregion
|
|
1990
2326
|
//#region src/surface.d.ts
|
|
1991
2327
|
declare module 'vitest' {
|
|
1992
2328
|
interface Assertion<T = any> {
|
|
@@ -2188,6 +2524,14 @@ type CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
|
2188
2524
|
* containers get force-removed at scope exit (CONVENTIONS B5).
|
|
2189
2525
|
*/
|
|
2190
2526
|
docker?: DockerSpecConfig;
|
|
2527
|
+
/**
|
|
2528
|
+
* Environment applied to EVERY run of this binary — the variables the
|
|
2529
|
+
* product needs to be deterministic at all (`TZ`, `NO_COLOR`, `LANG`,
|
|
2530
|
+
* a config home under the spec's own workdir). Stated once per app
|
|
2531
|
+
* instead of repeated on every chain and in every document; a chained
|
|
2532
|
+
* `.env()` and a document's `env:` both win over it.
|
|
2533
|
+
*/
|
|
2534
|
+
defaults?: CliEnv;
|
|
2191
2535
|
/**
|
|
2192
2536
|
* Named environment SETS for `<case>.spec.yaml` documents. An `env:` entry
|
|
2193
2537
|
* `frozen` applies the whole `frozen` record; `$WORKDIR` expands and `null`
|
|
@@ -2245,6 +2589,42 @@ type CliHandle<DatabaseKey extends string = string> = {
|
|
|
2245
2589
|
};
|
|
2246
2590
|
declare function startCli<Services extends ServiceRecord>(bin: string, options?: CliSpecificationOptions<Services>): Promise<CliHandle<DatabaseKeys<Services>>>;
|
|
2247
2591
|
//#endregion
|
|
2592
|
+
//#region src/specification/facets/integration/start-integration.d.ts
|
|
2593
|
+
/** Options for {@link startIntegration | specification.integration}. */
|
|
2594
|
+
type IntegrationSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2595
|
+
/** Project-root override — see the `root` option of `specification.api()`. */
|
|
2596
|
+
root?: string;
|
|
2597
|
+
/**
|
|
2598
|
+
* Named infrastructure the module is specified AGAINST — a real database,
|
|
2599
|
+
* a real cache, a `process()` it talks to. Omit it for the other half of
|
|
2600
|
+
* the facet: a pure module whose oracle is a golden.
|
|
2601
|
+
*/
|
|
2602
|
+
services?: Services;
|
|
2603
|
+
};
|
|
2604
|
+
/**
|
|
2605
|
+
* The record returned by {@link startIntegration | specification.integration}.
|
|
2606
|
+
* Destructure with the canonical names (CONVENTIONS A3):
|
|
2607
|
+
*
|
|
2608
|
+
* const { integration, cleanup } = await specification.integration(…);
|
|
2609
|
+
*/
|
|
2610
|
+
type IntegrationHandle<Services extends ServiceRecord = ServiceRecord, DatabaseKey extends string = string> = {
|
|
2611
|
+
/** Stop all infrastructure started by this specification. */
|
|
2612
|
+
cleanup: () => Promise<void>;
|
|
2613
|
+
integration: IntegrationSpecification<Services, DatabaseKey>;
|
|
2614
|
+
};
|
|
2615
|
+
/**
|
|
2616
|
+
* Specify a module the way the assembled product is specified: against real
|
|
2617
|
+
* services and declared contracts, with the golden mechanism on what it
|
|
2618
|
+
* produces.
|
|
2619
|
+
*
|
|
2620
|
+
* The fork is the SUBJECT. A module alone is a module test, beside its code,
|
|
2621
|
+
* with no runner at all. A module that needs a real database, or whose oracle
|
|
2622
|
+
* is a golden file, is this — and it used to be neither: a `specs/` folder
|
|
2623
|
+
* with plain vitest, a hand-built sqlite template, a `beforeAll` that started
|
|
2624
|
+
* a container, and a `toMatchSnapshot` where the package has a golden engine.
|
|
2625
|
+
*/
|
|
2626
|
+
declare function startIntegration<Services extends ServiceRecord>(options?: IntegrationSpecificationOptions<Services>): Promise<IntegrationHandle<Services, DatabaseKeys<Services>>>;
|
|
2627
|
+
//#endregion
|
|
2248
2628
|
//#region src/specification/facets/jobs/start-jobs.d.ts
|
|
2249
2629
|
/** Options for {@link startJobs | specification.jobs}. */
|
|
2250
2630
|
type JobsSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
@@ -2291,7 +2671,7 @@ type MobileBackendOptions = {
|
|
|
2291
2671
|
port?: number;
|
|
2292
2672
|
};
|
|
2293
2673
|
/** Options for {@link startMobile | specification.mobile}. */
|
|
2294
|
-
type MobileSpecificationOptions = {
|
|
2674
|
+
type MobileSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2295
2675
|
/** The app under test — terminated and relaunched by every `.open()`. */
|
|
2296
2676
|
app: {
|
|
2297
2677
|
/** Bundle id of the installed app (`com.example.app`). */
|
|
@@ -2324,6 +2704,14 @@ type MobileSpecificationOptions = {
|
|
|
2324
2704
|
* driving a DEV build states the wait its bundler's cold boot needs
|
|
2325
2705
|
* (`{ action: 45_000 }`) instead of sleeping inside its scenarios.
|
|
2326
2706
|
*/
|
|
2707
|
+
/**
|
|
2708
|
+
* Named services started with the runner and stopped with it — a
|
|
2709
|
+
* `process()` for the bundler the app loads from, a database a dev build
|
|
2710
|
+
* reads. Metro stops being a `beforeAll` the repository maintains: it is
|
|
2711
|
+
* `process({ command: 'expo start', ready: /Metro waiting on .*:(\\d+)/ })`
|
|
2712
|
+
* like every other external process the framework owns.
|
|
2713
|
+
*/
|
|
2714
|
+
services?: Services;
|
|
2327
2715
|
timeouts?: DeviceTimeouts;
|
|
2328
2716
|
};
|
|
2329
2717
|
/**
|
|
@@ -2345,13 +2733,35 @@ type MobileHandle = {
|
|
|
2345
2733
|
/** The resolved simulator UDID the specs run against. */
|
|
2346
2734
|
udid: string;
|
|
2347
2735
|
};
|
|
2348
|
-
declare function startMobile(options: MobileSpecificationOptions): Promise<MobileHandle>;
|
|
2736
|
+
declare function startMobile<Services extends ServiceRecord>(options: MobileSpecificationOptions<Services>): Promise<MobileHandle>;
|
|
2349
2737
|
//#endregion
|
|
2350
2738
|
//#region src/specification/facets/website/serve.adapter.d.ts
|
|
2351
|
-
/**
|
|
2352
|
-
|
|
2353
|
-
|
|
2739
|
+
/**
|
|
2740
|
+
* The ONE shape an external process takes — the site under test, a backend a
|
|
2741
|
+
* page talks to, a bundler a simulator loads from. Built by `process()`
|
|
2742
|
+
* (chapter 11) and accepted wherever a facet starts something outside this
|
|
2743
|
+
* process: `website({ server })`, `services: { api: process(…) }`, and the
|
|
2744
|
+
* literate `serve:` registry.
|
|
2745
|
+
*/
|
|
2746
|
+
type ProcessOptions = {
|
|
2747
|
+
/**
|
|
2748
|
+
* A one-shot command run ONCE per specification, before the process is
|
|
2749
|
+
* spawned, which must exit 0 — a build, a migration, a fixture load. Its
|
|
2750
|
+
* failure is the specification's, and it says so.
|
|
2751
|
+
*/
|
|
2752
|
+
before?: string;
|
|
2753
|
+
/** Shell command that starts the process. Receives the chosen port as `PORT`. */
|
|
2354
2754
|
command: string;
|
|
2755
|
+
/** Working directory, relative to the project root. Default: the root itself. */
|
|
2756
|
+
cwd?: string;
|
|
2757
|
+
/**
|
|
2758
|
+
* Environment for the child, on top of the runner's own. A function
|
|
2759
|
+
* receives the services already started beside this one, so a process can
|
|
2760
|
+
* be handed a sibling's connection string.
|
|
2761
|
+
*/
|
|
2762
|
+
env?: ((services: Record<string, {
|
|
2763
|
+
connectionString: string;
|
|
2764
|
+
}>) => Record<string, string>) | Record<string, string>;
|
|
2355
2765
|
/** Fixed port. Default: an OS-assigned free port, injected as `PORT`. */
|
|
2356
2766
|
port?: number;
|
|
2357
2767
|
/**
|
|
@@ -2369,8 +2779,96 @@ type ServeOptions = {
|
|
|
2369
2779
|
/** Readiness budget in milliseconds. Default 30 000. */
|
|
2370
2780
|
timeout?: number;
|
|
2371
2781
|
};
|
|
2782
|
+
/**
|
|
2783
|
+
* What `ProcessOptions` was called in 15.2, when the only process the
|
|
2784
|
+
* framework owned was a website's server.
|
|
2785
|
+
*
|
|
2786
|
+
* @deprecated Renamed to {@link ProcessOptions} in 15.3 — the shape is the
|
|
2787
|
+
* one external process every facet starts, not a website's alone. The alias
|
|
2788
|
+
* is removed in 16.0.
|
|
2789
|
+
*/
|
|
2790
|
+
type ServeOptions = ProcessOptions;
|
|
2791
|
+
//#endregion
|
|
2792
|
+
//#region src/specification/facets/_common/process.d.ts
|
|
2793
|
+
/**
|
|
2794
|
+
* An external process, declared like any other service.
|
|
2795
|
+
*
|
|
2796
|
+
* Before this there were three shapes for the same thing: `website({ server })`
|
|
2797
|
+
* took a `ServeOptions`, a literate document took a `serve:` entry, and a
|
|
2798
|
+
* project that needed a second process — an API behind the site, Metro behind
|
|
2799
|
+
* the simulator — started one by hand in a `beforeAll` and killed it in an
|
|
2800
|
+
* `afterAll` it sometimes forgot. `process()` is the one shape, and the
|
|
2801
|
+
* framework owns the lifetime.
|
|
2802
|
+
*
|
|
2803
|
+
* It sits in a `services` record beside `postgres()`, `redis()` and
|
|
2804
|
+
* `sqlite()`, so it starts after them and can be handed their connection
|
|
2805
|
+
* strings through `env`, and it is stopped with the specification.
|
|
2806
|
+
*/
|
|
2807
|
+
declare class ProcessHandle implements ServiceHandle {
|
|
2808
|
+
/** Embedded: nothing to pull, nothing to run in a container. */
|
|
2809
|
+
readonly defaultImage = "";
|
|
2810
|
+
readonly defaultPort = 0;
|
|
2811
|
+
readonly environment: Record<string, string>;
|
|
2812
|
+
readonly type = "process";
|
|
2813
|
+
composeName: null | string;
|
|
2814
|
+
connectionString: string;
|
|
2815
|
+
started: boolean;
|
|
2816
|
+
private adapter;
|
|
2817
|
+
private readonly options;
|
|
2818
|
+
constructor(options: ProcessOptions);
|
|
2819
|
+
buildConnectionString(host: string, port: number): string;
|
|
2820
|
+
createDatabaseAdapter(): DatabasePort | null;
|
|
2821
|
+
healthcheck(): Promise<void>;
|
|
2822
|
+
/**
|
|
2823
|
+
* A process is not a container, so the orchestrator never starts it: the
|
|
2824
|
+
* facet does, once its siblings are up and their connection strings are
|
|
2825
|
+
* readable ({@link startProcessServices}).
|
|
2826
|
+
*/
|
|
2827
|
+
initialize(): Promise<void>;
|
|
2828
|
+
/**
|
|
2829
|
+
* Isolation is the RUN's, not the worker's — and a run here is one
|
|
2830
|
+
* EVALUATION of the specification module. Under vitest's default
|
|
2831
|
+
* isolation that is once per test file, so two files that import the same
|
|
2832
|
+
* specification get a child each, with the id the facet minted for it.
|
|
2833
|
+
* Nothing is acquired or reset between tests: the id is what tells two
|
|
2834
|
+
* runs apart, never a label a spec samples.
|
|
2835
|
+
*/
|
|
2836
|
+
isolation(): IsolationStrategy;
|
|
2837
|
+
reset(): Promise<void>;
|
|
2838
|
+
/**
|
|
2839
|
+
* Spawn it and wait until it answers. `services` is every handle declared
|
|
2840
|
+
* beside this one, already started — what an `env` function reads.
|
|
2841
|
+
*
|
|
2842
|
+
* @internal
|
|
2843
|
+
*/
|
|
2844
|
+
spawnWith(root: string, services: Record<string, {
|
|
2845
|
+
connectionString: string;
|
|
2846
|
+
}>, runId: string): Promise<void>;
|
|
2847
|
+
/** Terminate the process group. Idempotent. */
|
|
2848
|
+
shutdown(): Promise<void>;
|
|
2849
|
+
/** What it was declared as — what a facet reads to start it in a slot of its own. */
|
|
2850
|
+
get spec(): ProcessOptions;
|
|
2851
|
+
/** The base URL it came up on — known only once it is started. */
|
|
2852
|
+
get url(): string;
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Declare an external process the framework owns for the life of the
|
|
2856
|
+
* specification.
|
|
2857
|
+
*
|
|
2858
|
+
* @example
|
|
2859
|
+
* const { website, cleanup } = await specification.website({
|
|
2860
|
+
* services: { api: process({ command: 'bin/server web', ready: '/health' }) },
|
|
2861
|
+
* server: ({ api }) => process({
|
|
2862
|
+
* command: 'next dev',
|
|
2863
|
+
* env: { NEXT_PUBLIC_API_URL: api.connectionString },
|
|
2864
|
+
* }),
|
|
2865
|
+
* });
|
|
2866
|
+
*/
|
|
2867
|
+
declare function processService(options: ProcessOptions): ProcessHandle;
|
|
2372
2868
|
//#endregion
|
|
2373
2869
|
//#region src/specification/facets/website/start-website.d.ts
|
|
2870
|
+
/** What `server` may be handed as, and what it resolves to. */
|
|
2871
|
+
type ServerSpec<Services extends ServiceRecord> = ((services: Services) => ProcessHandle | ProcessOptions) | ProcessHandle | ProcessOptions;
|
|
2374
2872
|
/**
|
|
2375
2873
|
* The declared stub backend behind the site under test — started before the
|
|
2376
2874
|
* server command, torn down with the runner. Its URL is injected into the
|
|
@@ -2391,7 +2889,7 @@ type WebsiteBackendOptions = {
|
|
|
2391
2889
|
* mode for the same reason: a deployed site cannot be pointed at a local
|
|
2392
2890
|
* stub.
|
|
2393
2891
|
*/
|
|
2394
|
-
type WebsiteSpecificationOptions = {
|
|
2892
|
+
type WebsiteSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2395
2893
|
/**
|
|
2396
2894
|
* Cross-origin request policy for visits. Default: `'block'` with a
|
|
2397
2895
|
* local `server` (deterministic — analytics and CDNs never leave the
|
|
@@ -2403,6 +2901,14 @@ type WebsiteSpecificationOptions = {
|
|
|
2403
2901
|
* `server` command. Auto-discovered from the calling file when absent.
|
|
2404
2902
|
*/
|
|
2405
2903
|
root?: string;
|
|
2904
|
+
/**
|
|
2905
|
+
* Named services started with the runner, before the site is: a database
|
|
2906
|
+
* the site reads, a `process()` backend it calls. They are started in
|
|
2907
|
+
* declaration order and stopped with the specification, and `server` may
|
|
2908
|
+
* be a function of them — which is how a site is handed the URL of the
|
|
2909
|
+
* API it was started beside.
|
|
2910
|
+
*/
|
|
2911
|
+
services?: Services;
|
|
2406
2912
|
} & ({
|
|
2407
2913
|
/**
|
|
2408
2914
|
* Declared stub backend: started BEFORE the server command, its
|
|
@@ -2411,10 +2917,11 @@ type WebsiteSpecificationOptions = {
|
|
|
2411
2917
|
*/
|
|
2412
2918
|
backend?: WebsiteBackendOptions;
|
|
2413
2919
|
/**
|
|
2414
|
-
* Start the site locally: a shell command receiving
|
|
2415
|
-
* as `PORT`, polled on `ready` (default `/`) until it
|
|
2920
|
+
* Start the site locally: a `process()` — a shell command receiving
|
|
2921
|
+
* a free port as `PORT`, polled on `ready` (default `/`) until it
|
|
2922
|
+
* answers — or a function of the started services returning one.
|
|
2416
2923
|
*/
|
|
2417
|
-
server:
|
|
2924
|
+
server: ServerSpec<Services>;
|
|
2418
2925
|
url?: never;
|
|
2419
2926
|
} | {
|
|
2420
2927
|
backend?: never;
|
|
@@ -2435,11 +2942,11 @@ type WebsiteHandle = {
|
|
|
2435
2942
|
url: string;
|
|
2436
2943
|
website: WebsiteSpecification;
|
|
2437
2944
|
};
|
|
2438
|
-
declare function startWebsite(options: WebsiteSpecificationOptions): Promise<WebsiteHandle>;
|
|
2945
|
+
declare function startWebsite<Services extends ServiceRecord>(options: WebsiteSpecificationOptions<Services>): Promise<WebsiteHandle>;
|
|
2439
2946
|
//#endregion
|
|
2440
2947
|
//#region src/specification/facets/_common/specification.d.ts
|
|
2441
2948
|
/**
|
|
2442
|
-
* The
|
|
2949
|
+
* The six specification constructors (CONVENTIONS A2) — created in a
|
|
2443
2950
|
* `*.specification.ts` file under `specs/`, destructured with canonical
|
|
2444
2951
|
* names, and cleaned up via `afterAll(cleanup)` (A1/A3/A4).
|
|
2445
2952
|
*
|
|
@@ -2462,10 +2969,11 @@ declare function startWebsite(options: WebsiteSpecificationOptions): Promise<Web
|
|
|
2462
2969
|
* export const { cli, cleanup } = await specification.cli('my-cli');
|
|
2463
2970
|
* afterAll(cleanup);
|
|
2464
2971
|
*/
|
|
2465
|
-
/** The
|
|
2972
|
+
/** The six constructors, and only six — the framework's whole entry surface. */
|
|
2466
2973
|
type Specification = {
|
|
2467
2974
|
api: typeof startApi;
|
|
2468
2975
|
cli: typeof startCli;
|
|
2976
|
+
integration: typeof startIntegration;
|
|
2469
2977
|
jobs: typeof startJobs;
|
|
2470
2978
|
mobile: typeof startMobile;
|
|
2471
2979
|
website: typeof startWebsite;
|
|
@@ -2774,4 +3282,11 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
|
|
|
2774
3282
|
*/
|
|
2775
3283
|
declare function sqlite(options?: SqliteOptions): SqliteHandle;
|
|
2776
3284
|
//#endregion
|
|
2777
|
-
|
|
3285
|
+
//#region src/index.d.ts
|
|
3286
|
+
/**
|
|
3287
|
+
* The module-scope network double, on msw's node server — what a test with no
|
|
3288
|
+
* chain to hang contracts on reaches for (chapter 10).
|
|
3289
|
+
*/
|
|
3290
|
+
declare const intercept: Intercept;
|
|
3291
|
+
//#endregion
|
|
3292
|
+
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, CallResult, type CaptureScope, type CliEnv, type CliHandle, type CliInput, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, type ComponentChain, type ComponentScenario, type ComponentUi, type ComponentVisitor, ContainerAccessor, type ContainerPort, type Contract, type ContractInput, type ContractRequest, type ContractResponder, type ContractResponse, type ContractResponseValue, type Contracts, type DatabaseKeys, type DatabasePort, type DeviceOpenOptions, type DevicePort, type DeviceScreen, type DeviceTimeouts, DirectoryAccessor, type DockerSpecConfig, type DomMount, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, HttpResult, type HttpStreamInit, type IntegrationHandle, type IntegrationSpecification, type IntegrationSpecificationOptions, type Intercept, type InterceptScope, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type LandmarkKind, type LiterateRunFlags, type LiterateServeRegistration, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MobileBackendOptions, type MobileElementKind, type MobileElementMatch, type MobileElementRef, type MobileHandle, type MobileScenario, type MobileSpecification, type MobileSpecificationOptions, type MobileVisitor, type MockDatePort, type MockOfOptions, type MockPort, Orchestrator, PageResult, type PinnedClock, type PostgresOptions, ProcessHandle, type ProcessOptions, type RedisOptions, type RenderResult, type RenderSubject, type RenderedText, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, type ServerSpec, type ServiceHandle, type ServiceRecord, type SpecDocument, type SpecEnvToken, type SpecFileAssertion, type SpecFixture, type SpecKind, type SpecRun, type SpecServeEntry, type SpecStream, type SpecificationConfig, type SpecificationMode, type SqliteOptions, type SseEvent, type StreamBody, TableAccessor, TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WaitUntilOptions, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, clock, complementary, component, content, contentinfo, defineContract, defineContracts, dialog, disabled, enabled, field, findContainersByLabel, focused, form, heading, http, inspectContainer, intercept, link, listitem, main, match, mockOf, mockOfDate, navigation, openai, option, postgres, processService as process, redis, region, removeContainers, required, row, search, selected, specification, sqlite, status, table, testId, text, valued, waitUntil, within };
|