@jterrazz/test 15.1.0 → 15.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/ambiguity.js +23 -4
- package/dist/artifacts.js +68 -160
- package/dist/browser/index.js +2477 -0
- package/dist/checker.js +9 -3
- package/dist/index.d.ts +807 -497
- package/dist/index.js +3034 -2770
- package/dist/intercept.js +108 -58
- package/dist/oxlint.cjs +461 -34
- package/dist/oxlint.d.cts +13 -1
- package/dist/oxlint.d.ts +13 -1
- package/dist/oxlint.js +461 -34
- package/dist/playwright.adapter.js +95 -22
- package/dist/queue.js +1 -1
- package/dist/vitest.d.ts +100 -2
- package/dist/vitest.js +375 -3
- package/package.json +37 -5
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Scalar } from "yaml";
|
|
2
2
|
import { DeepMockProxy } from "vitest-mock-extended";
|
|
3
|
+
import { ReactNode as ComponentUi } from "react";
|
|
4
|
+
import "vitest/browser";
|
|
3
5
|
//#region src/specification/matching/match.d.ts
|
|
4
6
|
/**
|
|
5
7
|
* Code-side dynamic-value matchers (`match.*`) and the capture scope shared
|
|
@@ -161,6 +163,16 @@ type ContractResponse = {
|
|
|
161
163
|
headers?: Record<string, string> | undefined;
|
|
162
164
|
/** Delay in ms before responding (for timeout testing). */
|
|
163
165
|
delay?: number | undefined;
|
|
166
|
+
/**
|
|
167
|
+
* A transport failure instead of a reply: the request never reached a
|
|
168
|
+
* server, so `fetch` rejects. `status`, `body` and `headers` say nothing
|
|
169
|
+
* here — there is no response to carry them.
|
|
170
|
+
*
|
|
171
|
+
* The one thing a status code cannot express. A component's "the server did
|
|
172
|
+
* not answer, check that it is running" branch is reached by a rejected
|
|
173
|
+
* `fetch`, and a 503 stand-in tests the other branch entirely.
|
|
174
|
+
*/
|
|
175
|
+
transport?: 'network-error' | undefined;
|
|
164
176
|
};
|
|
165
177
|
/**
|
|
166
178
|
* A dynamic response: computed from the observed request at the moment the
|
|
@@ -267,8 +279,15 @@ declare function defineContracts(...items: ContractInput[]): Contracts;
|
|
|
267
279
|
* so `within()` never becomes a second selector language.
|
|
268
280
|
*/
|
|
269
281
|
type LandmarkKind = 'banner' | 'complementary' | 'contentinfo' | 'form' | 'main' | 'navigation' | 'region' | 'search';
|
|
270
|
-
/**
|
|
271
|
-
|
|
282
|
+
/**
|
|
283
|
+
* The interactive/textual element kinds — what a visitor actually acts on.
|
|
284
|
+
*
|
|
285
|
+
* `dialog`, `status`, `table`, `row`, `listitem` and `option` are ARIA roles
|
|
286
|
+
* like the landmarks, but they are not CONTAINERS of a page: they are things a
|
|
287
|
+
* visitor reads and acts on, and they are the roles the estate's UI specs were
|
|
288
|
+
* already reaching for through `getByRole` or, worse, through `.first()`.
|
|
289
|
+
*/
|
|
290
|
+
type ElementKind = 'button' | 'dialog' | 'field' | 'heading' | 'link' | 'listitem' | 'option' | 'row' | 'status' | 'table' | 'testId' | 'text';
|
|
272
291
|
/**
|
|
273
292
|
* A user-facing element descriptor — pure data, built by the element
|
|
274
293
|
* vocabulary (`button()`, `link()`, `field()`, …) and translated into
|
|
@@ -285,9 +304,37 @@ type ElementRef = {
|
|
|
285
304
|
* "Read Articles".
|
|
286
305
|
*/
|
|
287
306
|
exact?: boolean;
|
|
307
|
+
/**
|
|
308
|
+
* Assert about the element's FOCUS rather than its presence — built by
|
|
309
|
+
* `focused(element)`. The accessibility tree carries no focus state, so
|
|
310
|
+
* where the keyboard is cannot be goldened; it is asserted by a verb.
|
|
311
|
+
*/
|
|
312
|
+
focused?: boolean;
|
|
313
|
+
/**
|
|
314
|
+
* Narrows to "and it is (not) accepting input" — built by `disabled(x)` /
|
|
315
|
+
* `enabled(x)`. Like `focused`, a STATE the vocabulary can already name the
|
|
316
|
+
* element of; unlike it, one the ARIA tree carries too (`[disabled]`), so
|
|
317
|
+
* this modifier is what names the ONE control a test is about, and it
|
|
318
|
+
* reaches the direction a behavioural substitute cannot — clicking a
|
|
319
|
+
* disabled control is a timeout, never an answer.
|
|
320
|
+
*/
|
|
321
|
+
disabled?: boolean;
|
|
288
322
|
kind: ElementKind | LandmarkKind;
|
|
289
323
|
/** Landmarks may be anonymous (`main()`, `banner()`); everything else is named. */
|
|
290
324
|
name?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Narrows to "and it is (not) the chosen one" — built by `selected(x)`.
|
|
327
|
+
* An option of a COLLAPSED select is in the tree and in the document, and
|
|
328
|
+
* it has no box on the screen, so this facet of it is read from the node
|
|
329
|
+
* (`:checked`) rather than from what a visitor could point at.
|
|
330
|
+
*/
|
|
331
|
+
selected?: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* The value the field must hold — built by `valued(field('Title'), '…')`.
|
|
334
|
+
* A field's value is a PROPERTY of the node: a controlled input's `value`
|
|
335
|
+
* attribute never moves, so nothing but the live value answers for it.
|
|
336
|
+
*/
|
|
337
|
+
value?: string;
|
|
291
338
|
/**
|
|
292
339
|
* Restrict the search to the elements of another descriptor — built by
|
|
293
340
|
* `within(scope, target)`. Chains: a scope may itself carry a scope.
|
|
@@ -300,6 +347,12 @@ type ElementRef = {
|
|
|
300
347
|
* without opening a browser.
|
|
301
348
|
*/
|
|
302
349
|
type ElementMatch = {
|
|
350
|
+
/**
|
|
351
|
+
* The accessible name, when it differs from the text — an `aria-label` is
|
|
352
|
+
* what a role descriptor MATCHED on, and printing the text alone sends the
|
|
353
|
+
* author to disambiguate a name they cannot see.
|
|
354
|
+
*/
|
|
355
|
+
accessibleName?: string | undefined;
|
|
303
356
|
/** Nearest landmark ancestor (`nav`, `footer`, `main`…), when there is one. */
|
|
304
357
|
context?: string | undefined;
|
|
305
358
|
/** The attribute that disambiguates most — `href` for links, `name` for fields. */
|
|
@@ -340,6 +393,12 @@ type Visitor = {
|
|
|
340
393
|
click: (element: ElementRef) => Promise<void>;
|
|
341
394
|
/** Fill a form field with a value. */
|
|
342
395
|
fill: (element: ElementRef, value: string) => Promise<void>;
|
|
396
|
+
/**
|
|
397
|
+
* Wait until the element is GONE — hidden, or never in the document. The
|
|
398
|
+
* absence primitive: `see()` cannot answer "the dialog closed", because an
|
|
399
|
+
* element that never appears and one that disappeared read the same to it.
|
|
400
|
+
*/
|
|
401
|
+
gone: (element: ElementRef) => Promise<void>;
|
|
343
402
|
/** Navigate to a path of the site under test. */
|
|
344
403
|
goto: (path: string) => Promise<void>;
|
|
345
404
|
/** Hover the element. */
|
|
@@ -396,6 +455,8 @@ type BrowserPage = {
|
|
|
396
455
|
status: number;
|
|
397
456
|
/** Rendered `document.body.innerText`. */
|
|
398
457
|
text: string;
|
|
458
|
+
/** The ARIA snapshot of the rendered `<body>` — the outline a golden pins. */
|
|
459
|
+
tree: string;
|
|
399
460
|
/** `document.title` after rendering. */
|
|
400
461
|
title: string;
|
|
401
462
|
/** Final URL after redirects and scenario navigation. */
|
|
@@ -813,24 +874,6 @@ declare class TextAccessor {
|
|
|
813
874
|
toString(): string;
|
|
814
875
|
valueOf(): string;
|
|
815
876
|
}
|
|
816
|
-
/**
|
|
817
|
-
* Wrap an arbitrary string into a {@link TextAccessor} anchored on the calling
|
|
818
|
-
* test's directory — the same caller-detection the builders use.
|
|
819
|
-
*
|
|
820
|
-
* The product surface of a test framework is its own error messages, checker
|
|
821
|
-
* output, and reports; those deserve the same goldening as any other output.
|
|
822
|
-
* `text()` makes an ad-hoc string a first-class snapshot subject:
|
|
823
|
-
*
|
|
824
|
-
* ```typescript
|
|
825
|
-
* const message = await catchMessage(() => expect(result.response).toMatch('wrong-body.http'));
|
|
826
|
-
* expect(text(message)).toMatch('wrong-body-error.txt'); // resolves to _expected/
|
|
827
|
-
* ```
|
|
828
|
-
*
|
|
829
|
-
* ANSI is stripped before every comparison (the raw form stays on `.text`),
|
|
830
|
-
* the `{{token}}` grammar applies to the fixture, and `.grep()` composition
|
|
831
|
-
* works exactly as on any stream accessor.
|
|
832
|
-
*/
|
|
833
|
-
declare function text(value: string): TextAccessor;
|
|
834
877
|
//#endregion
|
|
835
878
|
//#region src/specification/facets/cli/result.d.ts
|
|
836
879
|
/**
|
|
@@ -1027,6 +1070,13 @@ declare class PageResult extends BaseResult {
|
|
|
1027
1070
|
get status(): number;
|
|
1028
1071
|
/** Rendered body text as a text accessor. */
|
|
1029
1072
|
get content(): TextAccessor;
|
|
1073
|
+
/**
|
|
1074
|
+
* The ARIA snapshot of the rendered `<body>` — the page's outline, as the
|
|
1075
|
+
* accessibility tree draws it, and the one golden a rendered surface pins.
|
|
1076
|
+
* Deterministic where a screenshot is not, and the SAME dialect a component
|
|
1077
|
+
* spec asserts, so a page and the components inside it read alike.
|
|
1078
|
+
*/
|
|
1079
|
+
get tree(): TextAccessor;
|
|
1030
1080
|
/** The document title as a text accessor. */
|
|
1031
1081
|
get title(): TextAccessor;
|
|
1032
1082
|
/** Final URL after redirects. */
|
|
@@ -1441,7 +1491,16 @@ declare class ContainerAccessor {
|
|
|
1441
1491
|
private containerExec;
|
|
1442
1492
|
}
|
|
1443
1493
|
//#endregion
|
|
1444
|
-
//#region src/
|
|
1494
|
+
//#region src/specification/facets/_common/result/match-options.d.ts
|
|
1495
|
+
/**
|
|
1496
|
+
* The per-call option both `toMatch` implementations honour.
|
|
1497
|
+
*
|
|
1498
|
+
* A golden is read from disk under node and through a server command from
|
|
1499
|
+
* inside a page, and BOTH builds must answer `{ frozen: true }` the same way —
|
|
1500
|
+
* so the option is declared here, where the node matchers and the page's
|
|
1501
|
+
* matchers can each reach it, rather than in the runner's own folder, which a
|
|
1502
|
+
* seam may not import (rule I1).
|
|
1503
|
+
*/
|
|
1445
1504
|
/**
|
|
1446
1505
|
* Per-call options for the fixture-file `toMatch` subjects. `frozen` opts a
|
|
1447
1506
|
* single fixture OUT of update-mode rewriting: a frozen fixture is NEVER
|
|
@@ -1456,194 +1515,707 @@ type MatchFixtureOptions = {
|
|
|
1456
1515
|
frozen?: boolean;
|
|
1457
1516
|
};
|
|
1458
1517
|
//#endregion
|
|
1459
|
-
//#region src/
|
|
1460
|
-
type OrchestratorOptions = {
|
|
1461
|
-
/**
|
|
1462
|
-
* Named infrastructure record. Keys become the database vocabulary of the
|
|
1463
|
-
* spec (`.seed()` / `.table()` `database` option) and drive the compose
|
|
1464
|
-
* binding: a handle with no explicit `composeService` links to the compose
|
|
1465
|
-
* service named exactly like its key, else the kebab-case conversion of the
|
|
1466
|
-
* key (`analyticsDb` → `analytics-db`) — see {@link resolveComposeBinding}.
|
|
1467
|
-
*/
|
|
1468
|
-
services: Record<string, ServiceHandle>;
|
|
1469
|
-
mode: 'e2e' | 'integration';
|
|
1470
|
-
/**
|
|
1471
|
-
* The project root, already resolved. REQUIRED: the constructors derive it
|
|
1472
|
-
* by walking up from the calling specification file (CONVENTIONS A9), and
|
|
1473
|
-
* the orchestrator is handed the answer. Defaulting to `process.cwd()` here
|
|
1474
|
-
* meant one silent second opinion about what the project is — the runner
|
|
1475
|
-
* resolving from the caller, this from wherever the runner happened to be
|
|
1476
|
-
* launched, which in a workspace is rarely the same directory.
|
|
1477
|
-
*/
|
|
1478
|
-
root: string;
|
|
1479
|
-
/** Compose project name — used for per-worker stack isolation. */
|
|
1480
|
-
projectName?: string;
|
|
1481
|
-
};
|
|
1518
|
+
//#region src/integrations/vitest-browser/ui.d.ts
|
|
1482
1519
|
/**
|
|
1483
|
-
*
|
|
1484
|
-
*
|
|
1485
|
-
* E2E: runs full docker compose up.
|
|
1520
|
+
* A vanilla DOM subject: a function handed the container it must fill. Its
|
|
1521
|
+
* optional return is the teardown, the way a DOM library states one.
|
|
1486
1522
|
*/
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
private resolveBindings;
|
|
1505
|
-
/**
|
|
1506
|
-
* Start declared services via testcontainers (integration mode).
|
|
1507
|
-
* Phase 1: start all containers in parallel (the slow part).
|
|
1508
|
-
* Phase 2: wire connections, healthcheck, and init sequentially (fast).
|
|
1509
|
-
*/
|
|
1510
|
-
start(): Promise<void>;
|
|
1511
|
-
/**
|
|
1512
|
-
* Stop testcontainers (integration mode).
|
|
1513
|
-
*/
|
|
1514
|
-
stop(): Promise<void>;
|
|
1515
|
-
/**
|
|
1516
|
-
* Start full docker compose stack (e2e mode).
|
|
1517
|
-
* Auto-detects infra services and creates handles for them.
|
|
1518
|
-
*/
|
|
1519
|
-
startCompose(): Promise<void>;
|
|
1520
|
-
/**
|
|
1521
|
-
* Stop docker compose stack (e2e mode).
|
|
1522
|
-
*/
|
|
1523
|
-
stopCompose(): Promise<void>;
|
|
1524
|
-
/**
|
|
1525
|
-
* Get the default database — the first declared handle that is one.
|
|
1526
|
-
*/
|
|
1527
|
-
getDatabase(): DatabasePort | null;
|
|
1528
|
-
/**
|
|
1529
|
-
* Get all database services keyed by their record key (declared services)
|
|
1530
|
-
* or compose service name (stack-detected services).
|
|
1531
|
-
*/
|
|
1532
|
-
getDatabases(): Map<string, DatabasePort>;
|
|
1533
|
-
private allHandles;
|
|
1534
|
-
/**
|
|
1535
|
-
* Get app URL from compose (e2e mode).
|
|
1536
|
-
*/
|
|
1537
|
-
getAppUrl(): null | string;
|
|
1523
|
+
type DomMount = (container: HTMLElement) => (() => void) | void;
|
|
1524
|
+
/**
|
|
1525
|
+
* The `server.commands` this seam registers, declared in the module every part
|
|
1526
|
+
* of the seam already imports.
|
|
1527
|
+
*
|
|
1528
|
+
* A `declare module` merges only as an interface, and only from a file that is
|
|
1529
|
+
* itself a module and part of the program — a lone `.d.ts` would have to be
|
|
1530
|
+
* pulled in by an empty import, and its single `export {}` is exactly what a
|
|
1531
|
+
* formatter is free to drop, turning the augmentation into an ambient
|
|
1532
|
+
* declaration that SHADOWS the module it meant to extend.
|
|
1533
|
+
*/
|
|
1534
|
+
declare module 'vitest/browser' {
|
|
1535
|
+
interface BrowserCommands {
|
|
1536
|
+
ariaTree: () => Promise<string>;
|
|
1537
|
+
goldenRead: (name: string) => Promise<null | string>;
|
|
1538
|
+
goldenWrite: (name: string, content: string) => Promise<string>;
|
|
1539
|
+
}
|
|
1538
1540
|
}
|
|
1539
1541
|
//#endregion
|
|
1540
|
-
//#region src/
|
|
1542
|
+
//#region src/integrations/vitest-browser/vitest-browser.adapter.d.ts
|
|
1543
|
+
/** What a mounted subject offers the chain, React or vanilla DOM alike. */
|
|
1544
|
+
type MountedSurface = {
|
|
1545
|
+
/** The element the subject was mounted into. */
|
|
1546
|
+
container: HTMLElement;
|
|
1547
|
+
/** Replace the rendered tree — a prop change, as a parent makes one. */
|
|
1548
|
+
rerender: (ui: ComponentUi) => Promise<void>;
|
|
1549
|
+
/** Take it off the screen, as a parent does. */
|
|
1550
|
+
unmount: () => Promise<void>;
|
|
1551
|
+
};
|
|
1552
|
+
/** The verbs a rendered surface shares with a page, plus the two a parent owns. */
|
|
1553
|
+
declare function componentVerbs(surface: MountedSurface): {
|
|
1554
|
+
check: (element: ElementRef) => Promise<void>;
|
|
1555
|
+
click: (element: ElementRef) => Promise<void>;
|
|
1556
|
+
fill: (element: ElementRef, value: string) => Promise<void>;
|
|
1557
|
+
gone: (element: ElementRef) => Promise<void>;
|
|
1558
|
+
hover: (element: ElementRef) => Promise<void>;
|
|
1559
|
+
press: (key: string) => Promise<void>;
|
|
1560
|
+
rerender: (ui: ComponentUi) => Promise<void>;
|
|
1561
|
+
see: (element: ElementRef) => Promise<void>;
|
|
1562
|
+
select: (element: ElementRef, option: string) => Promise<void>;
|
|
1563
|
+
unmount: () => Promise<void>;
|
|
1564
|
+
};
|
|
1565
|
+
//#endregion
|
|
1566
|
+
//#region src/specification/facets/component/rendered-text.d.ts
|
|
1541
1567
|
/**
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
1544
|
-
*
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1568
|
+
* A stream captured INSIDE the page.
|
|
1569
|
+
*
|
|
1570
|
+
* Structurally identical to any other {@link TextAccessor} — the same
|
|
1571
|
+
* `{{token}}` grammar, the same ANSI stripping, the same `.grep()` — and
|
|
1572
|
+
* marked so the matcher augmentation can say the one thing that differs: its
|
|
1573
|
+
* golden lives on the server, so `toMatch` is awaited here where it is
|
|
1574
|
+
* synchronous everywhere else.
|
|
1575
|
+
*/
|
|
1576
|
+
declare class RenderedText extends TextAccessor {
|
|
1577
|
+
/** The marker the `toMatch` overload reads. Never asserted on directly. */
|
|
1578
|
+
readonly capturedInPage: true;
|
|
1579
|
+
}
|
|
1549
1580
|
//#endregion
|
|
1550
|
-
//#region src/specification/facets/
|
|
1551
|
-
/**
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1581
|
+
//#region src/specification/facets/component/component.result.d.ts
|
|
1582
|
+
/**
|
|
1583
|
+
* What a render resolves to — the component as the page saw it, after the
|
|
1584
|
+
* scenario ran.
|
|
1585
|
+
*
|
|
1586
|
+
* Five accessors and no more: the ARIA outline a golden pins, the two
|
|
1587
|
+
* renderings a probe reads, and the two streams a spec wants silent. Nothing
|
|
1588
|
+
* here reaches back into React — a spec that needs the component's internals
|
|
1589
|
+
* is asserting the implementation, not the surface a user meets.
|
|
1590
|
+
*/
|
|
1591
|
+
declare class RenderResult {
|
|
1592
|
+
/** The browser console as a stream — one `[type] text` line per message. */
|
|
1593
|
+
readonly console: RenderedText;
|
|
1594
|
+
/** Rendered text of the whole document body (`innerText`). */
|
|
1595
|
+
readonly content: RenderedText;
|
|
1596
|
+
/** Console messages of type `error` only, plus any uncaught page error. */
|
|
1597
|
+
readonly errors: RenderedText;
|
|
1598
|
+
/** The mounted markup, serialised — the escape hatch for a class or an attribute. */
|
|
1599
|
+
readonly html: RenderedText;
|
|
1600
|
+
/** The ARIA snapshot of the rendered body — the outline a golden pins. */
|
|
1601
|
+
readonly tree: RenderedText;
|
|
1602
|
+
constructor(streams: {
|
|
1603
|
+
console: string;
|
|
1604
|
+
content: string;
|
|
1605
|
+
errors: string;
|
|
1606
|
+
html: string;
|
|
1607
|
+
tree: string;
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
//#endregion
|
|
1611
|
+
//#region src/specification/facets/component/component.types.d.ts
|
|
1612
|
+
/**
|
|
1613
|
+
* The component facet's shape, stated where BOTH runtimes can read it.
|
|
1614
|
+
*
|
|
1615
|
+
* The page gets the working chain; node gets a stub under the same name and
|
|
1616
|
+
* the same type, so a component test that ran in the wrong project fails on
|
|
1617
|
+
* the line that rendered rather than on a missing export. The published types
|
|
1618
|
+
* are the node build's, so this file is the one description of the facet.
|
|
1619
|
+
*/
|
|
1620
|
+
/** The interaction vocabulary a render scenario is handed — the When. */
|
|
1621
|
+
type ComponentVisitor = ReturnType<typeof componentVerbs>;
|
|
1622
|
+
/** The behaviour of a render; assertions stay in the Then, on the result (W1). */
|
|
1623
|
+
type ComponentScenario = (visitor: ComponentVisitor) => Promise<void>;
|
|
1624
|
+
/**
|
|
1625
|
+
* What `.render()` mounts. A React tree is an element (`<PostTable />`); a
|
|
1626
|
+
* vanilla DOM subject is the FUNCTION that fills a container, which is how a
|
|
1627
|
+
* DOM-only module is called in production too.
|
|
1628
|
+
*/
|
|
1629
|
+
type RenderSubject = ComponentUi | DomMount;
|
|
1630
|
+
/**
|
|
1631
|
+
* The chain. Every setup returns a new one, so the handle a spec imports never
|
|
1632
|
+
* carries the previous test's contracts, wrapper or clock.
|
|
1633
|
+
*/
|
|
1634
|
+
type ComponentChain = {
|
|
1635
|
+
/** Pin the page's `Date` for this render — the clock the component reads. */
|
|
1636
|
+
clock: (iso: string) => ComponentChain;
|
|
1566
1637
|
/**
|
|
1567
|
-
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
* `docker/compose.test.yaml`, else the first containing `package.json`
|
|
1571
|
-
* (CONVENTIONS A9).
|
|
1638
|
+
* Declare what the network replies — the same contracts, the same queue
|
|
1639
|
+
* and the same strictness as every other facet (D7), served by msw's
|
|
1640
|
+
* worker instead of its node interceptor.
|
|
1572
1641
|
*/
|
|
1573
|
-
|
|
1642
|
+
intercept: {
|
|
1643
|
+
(contracts: ContractInput): ComponentChain;
|
|
1644
|
+
(request: ContractRequest, response: ContractResponder | ContractResponse): ComponentChain;
|
|
1645
|
+
};
|
|
1646
|
+
/** Mount the subject, run the scenario, and capture what the page shows. */
|
|
1647
|
+
render: (subject: RenderSubject, scenario?: ComponentScenario) => Promise<RenderResult>;
|
|
1574
1648
|
/**
|
|
1575
|
-
* The
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1649
|
+
* The page size THIS render gets — the Given of a component that reads
|
|
1650
|
+
* `matchMedia` or a container query. The project's size is restored when
|
|
1651
|
+
* the test ends, so a narrow render never leaks into the next one.
|
|
1578
1652
|
*/
|
|
1579
|
-
|
|
1653
|
+
viewport: (size: {
|
|
1654
|
+
height: number;
|
|
1655
|
+
width: number;
|
|
1656
|
+
}) => ComponentChain;
|
|
1580
1657
|
/**
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1584
|
-
* its key, else the kebab-case conversion of the key (CONVENTIONS A6).
|
|
1658
|
+
* Wrap every render of this chain — the test-local Given a router stub or
|
|
1659
|
+
* a `<StrictMode>` is. The project's own `wrap` stays outermost: it is the
|
|
1660
|
+
* app's frame, and one test does not get to sit outside it.
|
|
1585
1661
|
*/
|
|
1586
|
-
|
|
1662
|
+
wrap: (wrapper: (ui: ComponentUi) => ComponentUi) => ComponentChain;
|
|
1587
1663
|
};
|
|
1664
|
+
//#endregion
|
|
1665
|
+
//#region src/specification/facets/website/elements.d.ts
|
|
1588
1666
|
/**
|
|
1589
|
-
* The
|
|
1590
|
-
*
|
|
1667
|
+
* The element vocabulary — user-facing descriptors for visit scenarios.
|
|
1668
|
+
* Reads like English (`click(link('Articles'))`), translates to
|
|
1669
|
+
* accessibility-first locators in the browser integration. CSS/XPath is
|
|
1670
|
+
* deliberately not expressible; `testId()` is the single escape hatch.
|
|
1591
1671
|
*
|
|
1592
|
-
*
|
|
1672
|
+
* A descriptor must designate exactly ONE element (CONVENTIONS W3). Two knobs
|
|
1673
|
+
* narrow it, in this order of preference:
|
|
1674
|
+
*
|
|
1675
|
+
* 1. `within(scope, target)` — search inside a landmark, the way a person
|
|
1676
|
+
* would say "the Articles link *in the nav*";
|
|
1677
|
+
* 2. `{ exact: true }` — match the accessible name whole rather than as a
|
|
1678
|
+
* substring, when two names genuinely overlap.
|
|
1593
1679
|
*/
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
/** Stop all infrastructure started by this specification. */
|
|
1597
|
-
cleanup: () => Promise<void>;
|
|
1680
|
+
/** Options accepted by every named descriptor. */
|
|
1681
|
+
type ElementOptions = {
|
|
1598
1682
|
/**
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1683
|
+
* Match the accessible name as a whole string. Default is substring —
|
|
1684
|
+
* `link('Articles')` also matches "Read Articles".
|
|
1601
1685
|
*/
|
|
1602
|
-
|
|
1603
|
-
/** The orchestrator managing the test infrastructure lifecycle. */
|
|
1604
|
-
orchestrator: Orchestrator;
|
|
1686
|
+
exact?: boolean;
|
|
1605
1687
|
};
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1688
|
+
/** A user-facing element factory — what every name in this module is. */
|
|
1689
|
+
type ElementFactory = (name: string, options?: ElementOptions) => ElementRef;
|
|
1690
|
+
/** A button (or element with the button role), by accessible name. */
|
|
1691
|
+
declare const button: ElementFactory;
|
|
1692
|
+
/** A form field, by label. */
|
|
1693
|
+
declare const field: ElementFactory;
|
|
1694
|
+
/** A heading, by accessible name. */
|
|
1695
|
+
declare const heading: ElementFactory;
|
|
1696
|
+
/** A link, by accessible name. */
|
|
1697
|
+
declare const link: ElementFactory;
|
|
1698
|
+
/** An element containing the given text. */
|
|
1699
|
+
declare const content: ElementFactory;
|
|
1700
|
+
/** The escape hatch: an element by `data-testid`. Prefer user-facing elements. */
|
|
1701
|
+
declare const testId: (id: string) => ElementRef;
|
|
1702
|
+
/** A landmark factory — the optionally-named containers `within()` accepts. */
|
|
1703
|
+
type LandmarkFactory = (name?: string, options?: ElementOptions) => ElementRef;
|
|
1704
|
+
/** The `banner` landmark — the page header. */
|
|
1705
|
+
declare const banner: LandmarkFactory;
|
|
1706
|
+
/** The `complementary` landmark — an `<aside>`, a sidebar. */
|
|
1707
|
+
declare const complementary: LandmarkFactory;
|
|
1708
|
+
/** The `contentinfo` landmark — the page footer. */
|
|
1709
|
+
declare const contentinfo: LandmarkFactory;
|
|
1710
|
+
/** The `form` landmark — a form carrying an accessible name. */
|
|
1711
|
+
declare const form: LandmarkFactory;
|
|
1712
|
+
/** The `main` landmark — the primary content of the document. */
|
|
1713
|
+
declare const main: LandmarkFactory;
|
|
1714
|
+
/** The `navigation` landmark — a `<nav>`. Name it when a page has several. */
|
|
1715
|
+
declare const navigation: LandmarkFactory;
|
|
1716
|
+
/** The `region` landmark — a `<section>` carrying an accessible name. */
|
|
1717
|
+
declare const region: LandmarkFactory;
|
|
1718
|
+
/** The `search` landmark. */
|
|
1719
|
+
declare const search: LandmarkFactory;
|
|
1720
|
+
/**
|
|
1721
|
+
* Restrict a descriptor to the inside of another — the answer to ambiguity,
|
|
1722
|
+
* and the one the framework prefers over a test id.
|
|
1723
|
+
*
|
|
1724
|
+
* click(within(navigation(), link('Articles')))
|
|
1725
|
+
*
|
|
1726
|
+
* Composes outside-in: the scope may itself be scoped, so a deeply nested
|
|
1727
|
+
* target reads `within(main(), within(region('Series'), link('Part 2')))`.
|
|
1728
|
+
* Any descriptor works as a scope, including `testId()` when a container has
|
|
1729
|
+
* no landmark role to stand on.
|
|
1730
|
+
*/
|
|
1731
|
+
declare const within: (scope: ElementRef, target: ElementRef) => ElementRef;
|
|
1732
|
+
/** A modal or non-modal `<dialog>`, by accessible name. A closed one is absent. */
|
|
1733
|
+
declare const dialog: LandmarkFactory;
|
|
1734
|
+
/** A live region announcing a result — `role="status"`, an `<output>`. */
|
|
1735
|
+
declare const status: LandmarkFactory;
|
|
1736
|
+
/** A table, by its caption or accessible name. */
|
|
1737
|
+
declare const table: LandmarkFactory;
|
|
1738
|
+
/** A row of a table or grid, by the text of its cells. */
|
|
1739
|
+
declare const row: LandmarkFactory;
|
|
1740
|
+
/** An item of a list, by its text. Retires the `.first()` of an unnamed `<li>`. */
|
|
1741
|
+
declare const listitem: LandmarkFactory;
|
|
1742
|
+
/**
|
|
1743
|
+
* An option of a select or a listbox, by its label — named inside the field
|
|
1744
|
+
* that holds it: `within(field('Channel'), option('LinkedIn'))`.
|
|
1745
|
+
*
|
|
1746
|
+
* Asserted by PRESENCE, not by visibility: the options of a collapsed
|
|
1747
|
+
* `<select>` are in the document and in the accessibility tree, and none of
|
|
1748
|
+
* them has a box on the screen until it is opened. `see(option('LinkedIn'))`
|
|
1749
|
+
* therefore asks "is it offered", and `see(selected(option('LinkedIn')))`
|
|
1750
|
+
* asks which one the field is on.
|
|
1751
|
+
*/
|
|
1752
|
+
declare const option: ElementFactory;
|
|
1753
|
+
/**
|
|
1754
|
+
* Where the keyboard is: `see(focused(button('Open')))` asserts the element has
|
|
1755
|
+
* focus, `gone(focused(x))` that it does not.
|
|
1756
|
+
*
|
|
1757
|
+
* A modifier rather than a descriptor of its own, because focus is a STATE of
|
|
1758
|
+
* an element the vocabulary can already name. It is a verb's business and never
|
|
1759
|
+
* a golden's: the accessibility tree carries no focus, so a tree snapshot of a
|
|
1760
|
+
* dialog that took the keyboard and one that did not are byte-identical.
|
|
1761
|
+
*/
|
|
1762
|
+
declare const focused: (element: ElementRef) => ElementRef;
|
|
1763
|
+
/**
|
|
1764
|
+
* Whether the element accepts input: `see(disabled(button('Delete')))` asserts
|
|
1765
|
+
* it refuses, `see(enabled(button('Delete')))` that it takes it.
|
|
1766
|
+
*
|
|
1767
|
+
* Modifiers rather than descriptors, for the same reason as `focused`: being
|
|
1768
|
+
* disabled is a state of an element the vocabulary already names. Unlike focus,
|
|
1769
|
+
* the ARIA tree carries it, so a golden pins a whole outline's enablement and
|
|
1770
|
+
* these name the ONE control a test is about. They also buy the direction a
|
|
1771
|
+
* behavioural substitute cannot reach — clicking a disabled control is a
|
|
1772
|
+
* timeout, never an assertion.
|
|
1773
|
+
*/
|
|
1774
|
+
declare const disabled: (element: ElementRef) => ElementRef;
|
|
1775
|
+
/** The other direction of {@link disabled} — the element takes input. */
|
|
1776
|
+
declare const enabled: (element: ElementRef) => ElementRef;
|
|
1777
|
+
/**
|
|
1778
|
+
* Which option the field is on: `see(selected(option('LinkedIn')))`, and
|
|
1779
|
+
* `gone(selected(option('X')))` for the one it left.
|
|
1780
|
+
*
|
|
1781
|
+
* A modifier for the same reason as {@link disabled}: being chosen is a state
|
|
1782
|
+
* of an element the vocabulary already names. No matcher answers for it — a
|
|
1783
|
+
* `<option>` is not a checkbox — so both adapters read the `:checked` the CSS
|
|
1784
|
+
* spec gives a selected option.
|
|
1785
|
+
*/
|
|
1786
|
+
declare const selected: (element: ElementRef) => ElementRef;
|
|
1787
|
+
/**
|
|
1788
|
+
* What the field holds: `see(valued(field('Title'), 'Launch teaser'))`, and
|
|
1789
|
+
* `gone(valued(field('Title'), '…'))` for a value it no longer holds.
|
|
1790
|
+
*
|
|
1791
|
+
* A modifier for the same reason as {@link disabled}: what a field holds is a
|
|
1792
|
+
* state of an element the vocabulary already names, and this is how the ONE
|
|
1793
|
+
* field a test is about is waited for — a golden pins a whole outline instead.
|
|
1794
|
+
* Read from the node's live value, never from the `value` ATTRIBUTE, which a
|
|
1795
|
+
* controlled input never moves.
|
|
1796
|
+
*/
|
|
1797
|
+
declare const valued: (element: ElementRef, value: string) => ElementRef;
|
|
1798
|
+
//#endregion
|
|
1799
|
+
//#region src/specification/contracts/filters.d.ts
|
|
1800
|
+
/**
|
|
1801
|
+
* A text filter on a provider request builder (`openai.chat({ user })`,
|
|
1802
|
+
* `anthropic.messages({ system })`, …).
|
|
1803
|
+
*
|
|
1804
|
+
* A **string is EXACT**. That is deliberate: a substring filter silently
|
|
1805
|
+
* cross-matches — two prompts sharing a preamble both satisfy the first
|
|
1806
|
+
* contract, and the spec goes green on the wrong reply. Looser matching is
|
|
1807
|
+
* explicit, and says which looseness it means:
|
|
1808
|
+
*
|
|
1809
|
+
* - a `RegExp` — pattern matching;
|
|
1810
|
+
* - `match.includes('…')` — containment, the code-only matcher (it never joins
|
|
1811
|
+
* the `{{token}}` file vocabulary, CONVENTIONS D4);
|
|
1812
|
+
* - any other `match.*` matcher, evaluated by the same structural engine the
|
|
1813
|
+
* assertions use.
|
|
1814
|
+
*/
|
|
1815
|
+
type TextFilter = Matcher | RegExp | string;
|
|
1816
|
+
//#endregion
|
|
1817
|
+
//#region src/integrations/anthropic/anthropic.d.ts
|
|
1818
|
+
type AnthropicMessagesFilter = {
|
|
1819
|
+
model?: TextFilter;
|
|
1820
|
+
system?: TextFilter;
|
|
1821
|
+
user?: TextFilter;
|
|
1822
|
+
tools?: string[];
|
|
1823
|
+
};
|
|
1824
|
+
/**
|
|
1825
|
+
* Anthropic API intercept helpers.
|
|
1826
|
+
*/
|
|
1827
|
+
declare const anthropic: {
|
|
1828
|
+
/**
|
|
1829
|
+
* Request: match Messages API calls, optionally routed through a
|
|
1830
|
+
* custom gateway URL. When used with a JSON fixture file, the data is
|
|
1831
|
+
* returned as-is (no wrapping) because Anthropic fixtures are typically
|
|
1832
|
+
* already in the Messages API response shape.
|
|
1833
|
+
*
|
|
1834
|
+
* @example
|
|
1835
|
+
* anthropic.messages()
|
|
1836
|
+
* anthropic.messages({ system: /classify/ })
|
|
1837
|
+
* anthropic.messages({ user: buildPrompt() }) // string = EXACT equality
|
|
1838
|
+
* anthropic.messages({ user: /classify/ }, GATEWAY)
|
|
1839
|
+
*/
|
|
1840
|
+
messages(filter?: AnthropicMessagesFilter, url?: string): ContractRequest;
|
|
1841
|
+
/** Response: wrap data in Anthropic messages format. */
|
|
1842
|
+
reply(data: unknown): ContractResponse;
|
|
1843
|
+
/** Response: return an Anthropic error. */
|
|
1844
|
+
error(status: number, message?: string): ContractResponse;
|
|
1845
|
+
/** Response: simulate a timeout. */
|
|
1846
|
+
timeout(): ContractResponse;
|
|
1847
|
+
};
|
|
1848
|
+
//#endregion
|
|
1849
|
+
//#region src/specification/contracts/http.d.ts
|
|
1850
|
+
/**
|
|
1851
|
+
* Request filters for the generic HTTP provider. Every field is a subset
|
|
1852
|
+
* constraint — a request matches when all provided fields match.
|
|
1853
|
+
*/
|
|
1854
|
+
type HttpContractFilter = {
|
|
1855
|
+
/**
|
|
1856
|
+
* Body constraint. An object is a deep SUBSET match (toMatchObject-style)
|
|
1857
|
+
* whose leaf values may be `match.*` matchers; a string is a containment
|
|
1858
|
+
* test and a RegExp a `test()` over the raw text body.
|
|
1859
|
+
*/
|
|
1860
|
+
body?: object | RegExp | string;
|
|
1861
|
+
/** Header subset. Names are case-insensitive; string = exact value, RegExp = `test()`. */
|
|
1862
|
+
headers?: Record<string, RegExp | string>;
|
|
1863
|
+
/** Query-param subset. string = exact value, RegExp = `test()`. */
|
|
1864
|
+
query?: Record<string, RegExp | string>;
|
|
1865
|
+
};
|
|
1866
|
+
/** Init options shared by the response builders. */
|
|
1867
|
+
type HttpResponseInit = {
|
|
1868
|
+
/** Delay in ms before responding (for timeout testing). */
|
|
1869
|
+
delay?: number;
|
|
1870
|
+
/** Response headers, merged over the builder's own. */
|
|
1871
|
+
headers?: Record<string, string>;
|
|
1872
|
+
/** HTTP status code. */
|
|
1873
|
+
status?: number;
|
|
1874
|
+
};
|
|
1875
|
+
/**
|
|
1876
|
+
* Generic HTTP contract helpers for any URL. The url is absolute (string or
|
|
1877
|
+
* RegExp), or a PATH FORM starting with `/` — `http.get('/articles/{{uuid}}')`
|
|
1878
|
+
* matches that path on ANY origin, which is what an app calling its own
|
|
1879
|
+
* backend needs. An optional {@link HttpContractFilter} narrows matching by
|
|
1880
|
+
* body, headers, or query — a request that hits the URL/method but fails the
|
|
1881
|
+
* filter counts as unmatched (strict contracts, CONVENTIONS D7).
|
|
1882
|
+
*
|
|
1883
|
+
* @example
|
|
1884
|
+
* defineContract({ request: http.get('/articles/{{uuid}}'), response: http.json(article) })
|
|
1885
|
+
* defineContract({ request: http.post(URL, { body: { user: 'alice' } }), response: http.empty() })
|
|
1886
|
+
*/
|
|
1887
|
+
declare const http: {
|
|
1888
|
+
any(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
1889
|
+
delete(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
1890
|
+
get(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
1891
|
+
patch(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
1892
|
+
post(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
1893
|
+
put(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
1894
|
+
/** Response: a body-less reply (204 by default). */
|
|
1895
|
+
empty(status?: number): ContractResponse;
|
|
1896
|
+
/** Response: an error status. Without a body, `{ error: 'HTTP <status>' }`. */
|
|
1897
|
+
error(status: number, body?: unknown): ContractResponse;
|
|
1898
|
+
/** Response: a JSON body (200 by default). */
|
|
1899
|
+
json(body: unknown, init?: HttpResponseInit): ContractResponse;
|
|
1900
|
+
/**
|
|
1901
|
+
* Response: no response at all — the request fails in transport, the way a
|
|
1902
|
+
* server that is not running fails it. `fetch` rejects; no status is served.
|
|
1903
|
+
*/
|
|
1904
|
+
unreachable(): ContractResponse;
|
|
1905
|
+
/** Response: a text body, served as `text/plain` (200 by default). */
|
|
1906
|
+
text(body: string, init?: HttpResponseInit): ContractResponse;
|
|
1907
|
+
};
|
|
1908
|
+
//#endregion
|
|
1909
|
+
//#region src/integrations/openai/openai.d.ts
|
|
1910
|
+
type OpenAIChatFilter = {
|
|
1911
|
+
model?: TextFilter;
|
|
1912
|
+
system?: TextFilter;
|
|
1913
|
+
user?: TextFilter;
|
|
1914
|
+
tools?: string[];
|
|
1915
|
+
temperature?: number;
|
|
1916
|
+
};
|
|
1917
|
+
type OpenAIResponsesFilter = {
|
|
1918
|
+
model?: TextFilter;
|
|
1919
|
+
system?: TextFilter;
|
|
1920
|
+
user?: TextFilter;
|
|
1921
|
+
tools?: string[];
|
|
1922
|
+
};
|
|
1923
|
+
/**
|
|
1924
|
+
* OpenAI API intercept helpers.
|
|
1925
|
+
*/
|
|
1926
|
+
declare const openai: {
|
|
1927
|
+
/**
|
|
1928
|
+
* Request: match Chat Completions API calls. STRING filters mean EXACT
|
|
1929
|
+
* equality (pass the app's own prompt builder); loosen deliberately with a
|
|
1930
|
+
* RegExp or `match.includes(...)`.
|
|
1931
|
+
*
|
|
1932
|
+
* @example
|
|
1933
|
+
* openai.chat() // any chat call
|
|
1934
|
+
* openai.chat({ model: 'gpt-4o' }) // exact model
|
|
1935
|
+
* openai.chat({ system: buildPrompt() }) // exact system prompt
|
|
1936
|
+
* openai.chat({ system: /classify/ }) // pattern
|
|
1937
|
+
*/
|
|
1938
|
+
chat(filter?: OpenAIChatFilter): ContractRequest;
|
|
1939
|
+
/**
|
|
1940
|
+
* Request: match Responses API calls (AI SDK v5+) with auto-wrapping.
|
|
1941
|
+
* String filters mean EXACT equality (see {@link openai.chat}).
|
|
1942
|
+
* When used with a JSON file, the data is automatically wrapped in the
|
|
1943
|
+
* Responses API envelope.
|
|
1944
|
+
*
|
|
1945
|
+
* @param filter - Optional body filters.
|
|
1946
|
+
* @param url - Custom gateway URL (default: api.openai.com).
|
|
1947
|
+
*
|
|
1948
|
+
* @example
|
|
1949
|
+
* openai.responses({ user: /Report Ingestion/ }, GATEWAY)
|
|
1950
|
+
*/
|
|
1951
|
+
responses(filter?: OpenAIResponsesFilter, url?: string): ContractRequest;
|
|
1952
|
+
/**
|
|
1953
|
+
* Response: wrap data in Chat Completions format.
|
|
1954
|
+
*
|
|
1955
|
+
* @example
|
|
1956
|
+
* openai.reply({ categories: ['TECH'] })
|
|
1957
|
+
*/
|
|
1958
|
+
reply(data: unknown): ContractResponse;
|
|
1959
|
+
/** Response: return an OpenAI error. */
|
|
1960
|
+
error(status: number, message?: string): ContractResponse;
|
|
1961
|
+
/** Response: return malformed content. */
|
|
1962
|
+
malformed(content: string): ContractResponse;
|
|
1963
|
+
/** Response: simulate a timeout. */
|
|
1964
|
+
timeout(): ContractResponse;
|
|
1965
|
+
};
|
|
1966
|
+
//#endregion
|
|
1967
|
+
//#region src/vitest/mock-of.d.ts
|
|
1968
|
+
/** Factory signature that creates a deep mock proxy for any interface. */
|
|
1969
|
+
type MockPort = <T>() => DeepMockProxy<T>;
|
|
1970
|
+
/**
|
|
1971
|
+
* Create a deep mock proxy for a given type.
|
|
1972
|
+
* Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.
|
|
1973
|
+
*/
|
|
1974
|
+
declare const mockOf: MockPort;
|
|
1975
|
+
//#endregion
|
|
1976
|
+
//#region src/vitest/mock-of-date.d.ts
|
|
1977
|
+
/** Interface for freezing and resetting the global Date in tests. */
|
|
1978
|
+
type MockDatePort = {
|
|
1979
|
+
/** Restore the real Date object. */
|
|
1980
|
+
reset: () => void;
|
|
1981
|
+
/** Freeze `Date.now()` and `new Date()` to the given value. */
|
|
1982
|
+
set: (date: Date | number | string) => void;
|
|
1983
|
+
};
|
|
1984
|
+
/**
|
|
1985
|
+
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
1986
|
+
* Wraps the `mockdate` package.
|
|
1987
|
+
*/
|
|
1988
|
+
declare const mockOfDate: MockDatePort;
|
|
1989
|
+
//#endregion
|
|
1990
|
+
//#region src/surface.d.ts
|
|
1991
|
+
declare module 'vitest' {
|
|
1992
|
+
interface Assertion<T = any> {
|
|
1993
|
+
/**
|
|
1994
|
+
* Assert the subject is empty — zero rows for a table (async), an
|
|
1995
|
+
* empty stream for a text accessor (console, errors, stdout).
|
|
1996
|
+
*/
|
|
1997
|
+
toBeEmpty: T extends TableAccessor ? () => Promise<void> : T extends {
|
|
1998
|
+
readonly comparableText: string;
|
|
1999
|
+
} ? () => Promise<void> : never;
|
|
2000
|
+
/** Assert the container is running. Async — docker-backed subject. */
|
|
2001
|
+
toBeRunning: T extends ContainerAccessor ? () => Promise<void> : never;
|
|
2002
|
+
/**
|
|
2003
|
+
* On `@jterrazz/test` accessors: assert the subject matches a fixture
|
|
2004
|
+
* file under `_expected/<name>` (flat — a slash creates a subfolder;
|
|
2005
|
+
* `.http` format for `result.response`). Async for filesystem/directory
|
|
2006
|
+
* subjects (tree compare on disk), and for a subject captured INSIDE a
|
|
2007
|
+
* page (`result.tree`, `result.html`, … — the file crosses the browser
|
|
2008
|
+
* seam through a server command).
|
|
2009
|
+
*
|
|
2010
|
+
* Pass `{ frozen: true }` to opt a single fixture OUT of update-mode
|
|
2011
|
+
* rewriting: a frozen fixture is never overwritten under `TEST_UPDATE=1`
|
|
2012
|
+
* (or vitest `-u`), and a frozen mismatch/missing fixture still throws.
|
|
2013
|
+
* Use it for a deliberately-wrong fixture whose diff/error rendering is
|
|
2014
|
+
* the behaviour under test.
|
|
2015
|
+
*/
|
|
2016
|
+
toMatch: T extends DirectoryAccessor | FilesystemAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends {
|
|
2017
|
+
readonly capturedInPage: true;
|
|
2018
|
+
} ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends JsonAccessor | ResponseAccessor | TextAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => void : (expected: RegExp | string, options?: MatchFixtureOptions) => void;
|
|
2019
|
+
/**
|
|
2020
|
+
* Assert the table contains exactly the given rows for the given
|
|
2021
|
+
* columns. Cells accept `match.*` dynamic-value matchers. Async —
|
|
2022
|
+
* queries the database.
|
|
2023
|
+
*/
|
|
2024
|
+
toMatchRows: T extends TableAccessor ? (expected: {
|
|
2025
|
+
columns: string[];
|
|
2026
|
+
rows: readonly (readonly unknown[])[];
|
|
2027
|
+
}) => Promise<void> : never;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
//#endregion
|
|
2031
|
+
//#region src/specification/facets/_common/orchestrator.d.ts
|
|
2032
|
+
type OrchestratorOptions = {
|
|
2033
|
+
/**
|
|
2034
|
+
* Named infrastructure record. Keys become the database vocabulary of the
|
|
2035
|
+
* spec (`.seed()` / `.table()` `database` option) and drive the compose
|
|
2036
|
+
* binding: a handle with no explicit `composeService` links to the compose
|
|
2037
|
+
* service named exactly like its key, else the kebab-case conversion of the
|
|
2038
|
+
* key (`analyticsDb` → `analytics-db`) — see {@link resolveComposeBinding}.
|
|
2039
|
+
*/
|
|
2040
|
+
services: Record<string, ServiceHandle>;
|
|
2041
|
+
mode: 'e2e' | 'integration';
|
|
2042
|
+
/**
|
|
2043
|
+
* The project root, already resolved. REQUIRED: the constructors derive it
|
|
2044
|
+
* by walking up from the calling specification file (CONVENTIONS A9), and
|
|
2045
|
+
* the orchestrator is handed the answer. Defaulting to `process.cwd()` here
|
|
2046
|
+
* meant one silent second opinion about what the project is — the runner
|
|
2047
|
+
* resolving from the caller, this from wherever the runner happened to be
|
|
2048
|
+
* launched, which in a workspace is rarely the same directory.
|
|
2049
|
+
*/
|
|
2050
|
+
root: string;
|
|
2051
|
+
/** Compose project name — used for per-worker stack isolation. */
|
|
2052
|
+
projectName?: string;
|
|
2053
|
+
};
|
|
2054
|
+
/**
|
|
2055
|
+
* Orchestrator for test infrastructure.
|
|
2056
|
+
* Integration: starts services via testcontainers.
|
|
2057
|
+
* E2E: runs full docker compose up.
|
|
2058
|
+
*/
|
|
2059
|
+
declare class Orchestrator {
|
|
2060
|
+
private readonly services;
|
|
2061
|
+
private readonly mode;
|
|
2062
|
+
private readonly root;
|
|
2063
|
+
private readonly projectName;
|
|
2064
|
+
private running;
|
|
2065
|
+
private composeStack;
|
|
2066
|
+
private composeHandles;
|
|
2067
|
+
private started;
|
|
2068
|
+
constructor(options: OrchestratorOptions);
|
|
2069
|
+
/**
|
|
2070
|
+
* Bind each declared handle to a compose service (CONVENTIONS A6): a record
|
|
2071
|
+
* key resolves to the service named exactly like it, else the kebab-case
|
|
2072
|
+
* conversion of the key; an explicit `composeService` wins. Throws on an
|
|
2073
|
+
* ambiguous binding (both names present). Runs once compose config is known
|
|
2074
|
+
* so the two-step resolution can consult the real service list.
|
|
2075
|
+
*/
|
|
2076
|
+
private resolveBindings;
|
|
2077
|
+
/**
|
|
2078
|
+
* Start declared services via testcontainers (integration mode).
|
|
2079
|
+
* Phase 1: start all containers in parallel (the slow part).
|
|
2080
|
+
* Phase 2: wire connections, healthcheck, and init sequentially (fast).
|
|
2081
|
+
*/
|
|
2082
|
+
start(): Promise<void>;
|
|
2083
|
+
/**
|
|
2084
|
+
* Stop testcontainers (integration mode).
|
|
2085
|
+
*/
|
|
2086
|
+
stop(): Promise<void>;
|
|
2087
|
+
/**
|
|
2088
|
+
* Start full docker compose stack (e2e mode).
|
|
2089
|
+
* Auto-detects infra services and creates handles for them.
|
|
2090
|
+
*/
|
|
2091
|
+
startCompose(): Promise<void>;
|
|
2092
|
+
/**
|
|
2093
|
+
* Stop docker compose stack (e2e mode).
|
|
2094
|
+
*/
|
|
2095
|
+
stopCompose(): Promise<void>;
|
|
2096
|
+
/**
|
|
2097
|
+
* Get the default database — the first declared handle that is one.
|
|
2098
|
+
*/
|
|
2099
|
+
getDatabase(): DatabasePort | null;
|
|
2100
|
+
/**
|
|
2101
|
+
* Get all database services keyed by their record key (declared services)
|
|
2102
|
+
* or compose service name (stack-detected services).
|
|
2103
|
+
*/
|
|
2104
|
+
getDatabases(): Map<string, DatabasePort>;
|
|
2105
|
+
private allHandles;
|
|
2106
|
+
/**
|
|
2107
|
+
* Get app URL from compose (e2e mode).
|
|
2108
|
+
*/
|
|
2109
|
+
getAppUrl(): null | string;
|
|
2110
|
+
}
|
|
2111
|
+
//#endregion
|
|
2112
|
+
//#region src/specification/facets/_common/services.d.ts
|
|
2113
|
+
/**
|
|
2114
|
+
* Infrastructure services declared as a named record. Keys become the typed
|
|
2115
|
+
* vocabulary of the whole spec: the server factory receives the same record,
|
|
2116
|
+
* and `.seed()` / `.table()` target databases by key.
|
|
2117
|
+
*/
|
|
2118
|
+
type ServiceRecord = Record<string, ServiceHandle>;
|
|
2119
|
+
/** Keys of a services record whose handles are databases. */
|
|
2120
|
+
type DatabaseKeys<Services extends ServiceRecord> = { [K in keyof Services]: Services[K] extends DatabasePort ? K & string : never; }[keyof Services];
|
|
2121
|
+
//#endregion
|
|
2122
|
+
//#region src/specification/facets/api/start-api.d.ts
|
|
2123
|
+
/** Any object with a request method compatible with Hono's app.request(). */
|
|
2124
|
+
type HonoApp = {
|
|
2125
|
+
request: (path: string, init?: RequestInit) => Promise<Response> | Response;
|
|
2126
|
+
};
|
|
2127
|
+
/** Execution mode — exists ONLY on `specification.api()` (CONVENTIONS A5). */
|
|
2128
|
+
type SpecificationMode = 'compose' | 'node';
|
|
2129
|
+
/** Options for {@link startApi | specification.api}. */
|
|
2130
|
+
type ApiSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2131
|
+
/**
|
|
2132
|
+
* Execution mode override. Resolution: `options.mode` >
|
|
2133
|
+
* `process.env.TEST_MODE` > `'node'`. Never hardcode this in a
|
|
2134
|
+
* specification file when `server` is defined — set it per vitest
|
|
2135
|
+
* project via `env: { TEST_MODE: 'compose' }` (CONVENTIONS A5).
|
|
2136
|
+
*/
|
|
2137
|
+
mode?: SpecificationMode;
|
|
2138
|
+
/**
|
|
2139
|
+
* Project root override for compose detection and init scripts. When
|
|
2140
|
+
* absent, the root is auto-discovered by walking up from the calling
|
|
2141
|
+
* specification file to the first directory containing
|
|
2142
|
+
* `docker/compose.test.yaml`, else the first containing `package.json`
|
|
2143
|
+
* (CONVENTIONS A9).
|
|
2144
|
+
*/
|
|
2145
|
+
root?: string;
|
|
2146
|
+
/**
|
|
2147
|
+
* The app factory — receives the started services record (fully typed)
|
|
2148
|
+
* and returns the Hono app. Required in node mode, ignored in compose
|
|
2149
|
+
* mode (the app runs as a compose service there).
|
|
2150
|
+
*/
|
|
2151
|
+
server?: (services: Services) => HonoApp;
|
|
2152
|
+
/**
|
|
2153
|
+
* Named infrastructure record. Keys become the `database` vocabulary of
|
|
2154
|
+
* `.seed()` / `.table()` and drive the compose binding: a handle with no
|
|
2155
|
+
* `composeService` option links to the compose service named exactly like
|
|
2156
|
+
* its key, else the kebab-case conversion of the key (CONVENTIONS A6).
|
|
2157
|
+
*/
|
|
2158
|
+
services?: Services;
|
|
2159
|
+
};
|
|
2160
|
+
/**
|
|
2161
|
+
* The record returned by {@link startApi | specification.api}. Destructure
|
|
2162
|
+
* with the canonical names (CONVENTIONS A3):
|
|
2163
|
+
*
|
|
2164
|
+
* const { api, cleanup, docker } = await specification.api(…);
|
|
2165
|
+
*/
|
|
2166
|
+
type ApiHandle<DatabaseKey extends string = string> = {
|
|
2167
|
+
api: ApiSpecification<DatabaseKey>;
|
|
2168
|
+
/** Stop all infrastructure started by this specification. */
|
|
2169
|
+
cleanup: () => Promise<void>;
|
|
2170
|
+
/**
|
|
2171
|
+
* Read a running container by id — returns a {@link ContainerAccessor}
|
|
2172
|
+
* usable with `await expect(...).toBeRunning()` and read accessors.
|
|
2173
|
+
*/
|
|
2174
|
+
docker: (containerId: string) => ContainerAccessor;
|
|
2175
|
+
/** The orchestrator managing the test infrastructure lifecycle. */
|
|
2176
|
+
orchestrator: Orchestrator;
|
|
2177
|
+
};
|
|
2178
|
+
declare function startApi<Services extends ServiceRecord>(options: ApiSpecificationOptions<Services>): Promise<ApiHandle<DatabaseKeys<Services>>>;
|
|
2179
|
+
//#endregion
|
|
2180
|
+
//#region src/specification/facets/cli/start-cli.d.ts
|
|
2181
|
+
/** Options for {@link startCli | specification.cli}. */
|
|
2182
|
+
type CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2183
|
+
/**
|
|
2184
|
+
* Opt-in Docker awareness. When set, every spec generates a unique
|
|
2185
|
+
* test-run id, injects it into the child process env under `envVar`,
|
|
2186
|
+
* and exposes `.container(name)` accessors on the result that lazily
|
|
2187
|
+
* query Docker. Always declare results with `await using` so leaked
|
|
2188
|
+
* containers get force-removed at scope exit (CONVENTIONS B5).
|
|
2189
|
+
*/
|
|
2190
|
+
docker?: DockerSpecConfig;
|
|
2191
|
+
/**
|
|
2192
|
+
* Named environment SETS for `<case>.spec.yaml` documents. An `env:` entry
|
|
2193
|
+
* `frozen` applies the whole `frozen` record; `$WORKDIR` expands and `null`
|
|
2194
|
+
* unsets, exactly as in `.env()`. Declared once per app so a document
|
|
2195
|
+
* states WHICH ground it stands on, not how to build it.
|
|
2196
|
+
*/
|
|
2197
|
+
env?: Record<string, CliEnv>;
|
|
2198
|
+
/**
|
|
2199
|
+
* Named servers a `<case>.spec.yaml` document may start (`serve: [mcp]`, or
|
|
2200
|
+
* `- mcp: { KEY: value }` to add env to that one). Each entry names the
|
|
2201
|
+
* shell `command`, the `ready` regex whose capture group holds the port the
|
|
2202
|
+
* server announces, the `url(port)` builder, and the `env` variable that
|
|
2203
|
+
* URL is bound to in every run's child.
|
|
2204
|
+
*/
|
|
2205
|
+
serve?: Record<string, LiterateServeRegistration>;
|
|
2206
|
+
/**
|
|
2207
|
+
* Project-root override (CONVENTIONS A9) — the single meaning of `root`:
|
|
2208
|
+
* it anchors compose detection and local-bin resolution for the tested
|
|
2209
|
+
* binary. It is NOT a fixtures root; `.fixture()` resolves feature-local
|
|
2210
|
+
* or `$FIXTURES/` paths on its own.
|
|
2211
|
+
*/
|
|
2212
|
+
root?: string;
|
|
2213
|
+
/**
|
|
2214
|
+
* Named infrastructure record started via testcontainers. Connection
|
|
2215
|
+
* URLs are injected automatically into the child env: `<KEY>_URL` per
|
|
2216
|
+
* service, plus `DATABASE_URL` / `REDIS_URL` when unambiguous
|
|
2217
|
+
* (CONVENTIONS B6). `.env()` overrides.
|
|
2218
|
+
*/
|
|
1647
2219
|
services?: Services;
|
|
1648
2220
|
/**
|
|
1649
2221
|
* Escape hatch: normaliser applied to result.stdout / result.stderr
|
|
@@ -1900,6 +2472,10 @@ type Specification = {
|
|
|
1900
2472
|
};
|
|
1901
2473
|
declare const specification: Specification;
|
|
1902
2474
|
//#endregion
|
|
2475
|
+
//#region src/specification/facets/component/component.node.d.ts
|
|
2476
|
+
/** Present, typed, and refusing — every setup and the terminal action alike. */
|
|
2477
|
+
declare const component: ComponentChain;
|
|
2478
|
+
//#endregion
|
|
1903
2479
|
//#region src/specification/literate/spec-document.d.ts
|
|
1904
2480
|
/**
|
|
1905
2481
|
* The facets a document may describe. `cli` is the only one implemented; the
|
|
@@ -1988,93 +2564,51 @@ declare function inspectContainer(id: string): unknown;
|
|
|
1988
2564
|
/** Force-remove the given container IDs in a single call. Errors are swallowed. */
|
|
1989
2565
|
declare function removeContainers(ids: string[]): void;
|
|
1990
2566
|
//#endregion
|
|
1991
|
-
//#region src/specification/
|
|
1992
|
-
/**
|
|
1993
|
-
* Abstract container interface.
|
|
1994
|
-
* Represents a running service (database, cache, etc.)
|
|
1995
|
-
*/
|
|
1996
|
-
type ContainerPort = {
|
|
1997
|
-
/** Start the container and wait until ready. */
|
|
1998
|
-
start: () => Promise<void>;
|
|
1999
|
-
/** Stop and remove the container. */
|
|
2000
|
-
stop: () => Promise<void>;
|
|
2001
|
-
/** Get the mapped host port for a container port. */
|
|
2002
|
-
getMappedPort: (containerPort: number) => number;
|
|
2003
|
-
/** Get the host to connect to. */
|
|
2004
|
-
getHost: () => string;
|
|
2005
|
-
/** Get a full connection string for this service. */
|
|
2006
|
-
getConnectionString: () => string;
|
|
2007
|
-
/** Get container logs (stdout + stderr). */
|
|
2008
|
-
getLogs: () => Promise<string>;
|
|
2009
|
-
};
|
|
2010
|
-
//#endregion
|
|
2011
|
-
//#region src/specification/facets/website/elements.d.ts
|
|
2567
|
+
//#region src/specification/facets/_common/result/text-subject.d.ts
|
|
2012
2568
|
/**
|
|
2013
|
-
*
|
|
2014
|
-
*
|
|
2015
|
-
* accessibility-first locators in the browser integration. CSS/XPath is
|
|
2016
|
-
* deliberately not expressible; `testId()` is the single escape hatch.
|
|
2017
|
-
*
|
|
2018
|
-
* A descriptor must designate exactly ONE element (CONVENTIONS W3). Two knobs
|
|
2019
|
-
* narrow it, in this order of preference:
|
|
2569
|
+
* Wrap an arbitrary string into a {@link TextAccessor} anchored on the calling
|
|
2570
|
+
* test's directory — the same caller-detection the builders use.
|
|
2020
2571
|
*
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
*
|
|
2024
|
-
* substring, when two names genuinely overlap.
|
|
2025
|
-
*/
|
|
2026
|
-
/** Options accepted by every named descriptor. */
|
|
2027
|
-
type ElementOptions = {
|
|
2028
|
-
/**
|
|
2029
|
-
* Match the accessible name as a whole string. Default is substring —
|
|
2030
|
-
* `link('Articles')` also matches "Read Articles".
|
|
2031
|
-
*/
|
|
2032
|
-
exact?: boolean;
|
|
2033
|
-
};
|
|
2034
|
-
/** A user-facing element factory — what every name in this module is. */
|
|
2035
|
-
type ElementFactory = (name: string, options?: ElementOptions) => ElementRef;
|
|
2036
|
-
/** A button (or element with the button role), by accessible name. */
|
|
2037
|
-
declare const button: ElementFactory;
|
|
2038
|
-
/** A form field, by label. */
|
|
2039
|
-
declare const field: ElementFactory;
|
|
2040
|
-
/** A heading, by accessible name. */
|
|
2041
|
-
declare const heading: ElementFactory;
|
|
2042
|
-
/** A link, by accessible name. */
|
|
2043
|
-
declare const link: ElementFactory;
|
|
2044
|
-
/** An element containing the given text. */
|
|
2045
|
-
declare const content: ElementFactory;
|
|
2046
|
-
/** The escape hatch: an element by `data-testid`. Prefer user-facing elements. */
|
|
2047
|
-
declare const testId: (id: string) => ElementRef;
|
|
2048
|
-
/** A landmark factory — the optionally-named containers `within()` accepts. */
|
|
2049
|
-
type LandmarkFactory = (name?: string, options?: ElementOptions) => ElementRef;
|
|
2050
|
-
/** The `banner` landmark — the page header. */
|
|
2051
|
-
declare const banner: LandmarkFactory;
|
|
2052
|
-
/** The `complementary` landmark — an `<aside>`, a sidebar. */
|
|
2053
|
-
declare const complementary: LandmarkFactory;
|
|
2054
|
-
/** The `contentinfo` landmark — the page footer. */
|
|
2055
|
-
declare const contentinfo: LandmarkFactory;
|
|
2056
|
-
/** The `form` landmark — a form carrying an accessible name. */
|
|
2057
|
-
declare const form: LandmarkFactory;
|
|
2058
|
-
/** The `main` landmark — the primary content of the document. */
|
|
2059
|
-
declare const main: LandmarkFactory;
|
|
2060
|
-
/** The `navigation` landmark — a `<nav>`. Name it when a page has several. */
|
|
2061
|
-
declare const navigation: LandmarkFactory;
|
|
2062
|
-
/** The `region` landmark — a `<section>` carrying an accessible name. */
|
|
2063
|
-
declare const region: LandmarkFactory;
|
|
2064
|
-
/** The `search` landmark. */
|
|
2065
|
-
declare const search: LandmarkFactory;
|
|
2066
|
-
/**
|
|
2067
|
-
* Restrict a descriptor to the inside of another — the answer to ambiguity,
|
|
2068
|
-
* and the one the framework prefers over a test id.
|
|
2572
|
+
* The product surface of a test framework is its own error messages, checker
|
|
2573
|
+
* output, and reports; those deserve the same goldening as any other output.
|
|
2574
|
+
* `text()` makes an ad-hoc string a first-class snapshot subject:
|
|
2069
2575
|
*
|
|
2070
|
-
*
|
|
2576
|
+
* ```typescript
|
|
2577
|
+
* const message = await catchMessage(() => expect(result.response).toMatch('wrong-body.http'));
|
|
2578
|
+
* expect(text(message)).toMatch('wrong-body-error.txt'); // resolves to _expected/
|
|
2579
|
+
* ```
|
|
2071
2580
|
*
|
|
2072
|
-
*
|
|
2073
|
-
*
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2581
|
+
* ANSI is stripped before every comparison (the raw form stays on `.text`),
|
|
2582
|
+
* the `{{token}}` grammar applies to the fixture, and `.grep()` composition
|
|
2583
|
+
* works exactly as on any stream accessor.
|
|
2584
|
+
*
|
|
2585
|
+
* The ANCHOR is why this sits apart from the accessor it builds: reading the
|
|
2586
|
+
* caller's frame means reading a stack and a real path, which is node's alone.
|
|
2587
|
+
* {@link TextAccessor} itself is a pure projection over a captured string, so
|
|
2588
|
+
* it loads in a page — and the browser entry builds its own `text()`, where
|
|
2589
|
+
* the golden commands resolve `_expected/` from the test's path server-side.
|
|
2076
2590
|
*/
|
|
2077
|
-
declare
|
|
2591
|
+
declare function text(value: string): TextAccessor;
|
|
2592
|
+
//#endregion
|
|
2593
|
+
//#region src/specification/ports/container.port.d.ts
|
|
2594
|
+
/**
|
|
2595
|
+
* Abstract container interface.
|
|
2596
|
+
* Represents a running service (database, cache, etc.)
|
|
2597
|
+
*/
|
|
2598
|
+
type ContainerPort = {
|
|
2599
|
+
/** Start the container and wait until ready. */
|
|
2600
|
+
start: () => Promise<void>;
|
|
2601
|
+
/** Stop and remove the container. */
|
|
2602
|
+
stop: () => Promise<void>;
|
|
2603
|
+
/** Get the mapped host port for a container port. */
|
|
2604
|
+
getMappedPort: (containerPort: number) => number;
|
|
2605
|
+
/** Get the host to connect to. */
|
|
2606
|
+
getHost: () => string;
|
|
2607
|
+
/** Get a full connection string for this service. */
|
|
2608
|
+
getConnectionString: () => string;
|
|
2609
|
+
/** Get container logs (stdout + stderr). */
|
|
2610
|
+
getLogs: () => Promise<string>;
|
|
2611
|
+
};
|
|
2078
2612
|
//#endregion
|
|
2079
2613
|
//#region src/integrations/postgres/postgres.d.ts
|
|
2080
2614
|
type PostgresOptions = {
|
|
@@ -2240,228 +2774,4 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
|
|
|
2240
2774
|
*/
|
|
2241
2775
|
declare function sqlite(options?: SqliteOptions): SqliteHandle;
|
|
2242
2776
|
//#endregion
|
|
2243
|
-
|
|
2244
|
-
/**
|
|
2245
|
-
* A text filter on a provider request builder (`openai.chat({ user })`,
|
|
2246
|
-
* `anthropic.messages({ system })`, …).
|
|
2247
|
-
*
|
|
2248
|
-
* A **string is EXACT**. That is deliberate: a substring filter silently
|
|
2249
|
-
* cross-matches — two prompts sharing a preamble both satisfy the first
|
|
2250
|
-
* contract, and the spec goes green on the wrong reply. Looser matching is
|
|
2251
|
-
* explicit, and says which looseness it means:
|
|
2252
|
-
*
|
|
2253
|
-
* - a `RegExp` — pattern matching;
|
|
2254
|
-
* - `match.includes('…')` — containment, the code-only matcher (it never joins
|
|
2255
|
-
* the `{{token}}` file vocabulary, CONVENTIONS D4);
|
|
2256
|
-
* - any other `match.*` matcher, evaluated by the same structural engine the
|
|
2257
|
-
* assertions use.
|
|
2258
|
-
*/
|
|
2259
|
-
type TextFilter = Matcher | RegExp | string;
|
|
2260
|
-
//#endregion
|
|
2261
|
-
//#region src/integrations/anthropic/anthropic.d.ts
|
|
2262
|
-
type AnthropicMessagesFilter = {
|
|
2263
|
-
model?: TextFilter;
|
|
2264
|
-
system?: TextFilter;
|
|
2265
|
-
user?: TextFilter;
|
|
2266
|
-
tools?: string[];
|
|
2267
|
-
};
|
|
2268
|
-
/**
|
|
2269
|
-
* Anthropic API intercept helpers.
|
|
2270
|
-
*/
|
|
2271
|
-
declare const anthropic: {
|
|
2272
|
-
/**
|
|
2273
|
-
* Request: match Messages API calls, optionally routed through a
|
|
2274
|
-
* custom gateway URL. When used with a JSON fixture file, the data is
|
|
2275
|
-
* returned as-is (no wrapping) because Anthropic fixtures are typically
|
|
2276
|
-
* already in the Messages API response shape.
|
|
2277
|
-
*
|
|
2278
|
-
* @example
|
|
2279
|
-
* anthropic.messages()
|
|
2280
|
-
* anthropic.messages({ system: /classify/ })
|
|
2281
|
-
* anthropic.messages({ user: buildPrompt() }) // string = EXACT equality
|
|
2282
|
-
* anthropic.messages({ user: /classify/ }, GATEWAY)
|
|
2283
|
-
*/
|
|
2284
|
-
messages(filter?: AnthropicMessagesFilter, url?: string): ContractRequest;
|
|
2285
|
-
/** Response: wrap data in Anthropic messages format. */
|
|
2286
|
-
reply(data: unknown): ContractResponse;
|
|
2287
|
-
/** Response: return an Anthropic error. */
|
|
2288
|
-
error(status: number, message?: string): ContractResponse;
|
|
2289
|
-
/** Response: simulate a timeout. */
|
|
2290
|
-
timeout(): ContractResponse;
|
|
2291
|
-
};
|
|
2292
|
-
//#endregion
|
|
2293
|
-
//#region src/specification/contracts/http.d.ts
|
|
2294
|
-
/**
|
|
2295
|
-
* Request filters for the generic HTTP provider. Every field is a subset
|
|
2296
|
-
* constraint — a request matches when all provided fields match.
|
|
2297
|
-
*/
|
|
2298
|
-
type HttpContractFilter = {
|
|
2299
|
-
/**
|
|
2300
|
-
* Body constraint. An object is a deep SUBSET match (toMatchObject-style)
|
|
2301
|
-
* whose leaf values may be `match.*` matchers; a string is a containment
|
|
2302
|
-
* test and a RegExp a `test()` over the raw text body.
|
|
2303
|
-
*/
|
|
2304
|
-
body?: object | RegExp | string;
|
|
2305
|
-
/** Header subset. Names are case-insensitive; string = exact value, RegExp = `test()`. */
|
|
2306
|
-
headers?: Record<string, RegExp | string>;
|
|
2307
|
-
/** Query-param subset. string = exact value, RegExp = `test()`. */
|
|
2308
|
-
query?: Record<string, RegExp | string>;
|
|
2309
|
-
};
|
|
2310
|
-
/** Init options shared by the response builders. */
|
|
2311
|
-
type HttpResponseInit = {
|
|
2312
|
-
/** Delay in ms before responding (for timeout testing). */
|
|
2313
|
-
delay?: number;
|
|
2314
|
-
/** Response headers, merged over the builder's own. */
|
|
2315
|
-
headers?: Record<string, string>;
|
|
2316
|
-
/** HTTP status code. */
|
|
2317
|
-
status?: number;
|
|
2318
|
-
};
|
|
2319
|
-
/**
|
|
2320
|
-
* Generic HTTP contract helpers for any URL. The url is absolute (string or
|
|
2321
|
-
* RegExp), or a PATH FORM starting with `/` — `http.get('/articles/{{uuid}}')`
|
|
2322
|
-
* matches that path on ANY origin, which is what an app calling its own
|
|
2323
|
-
* backend needs. An optional {@link HttpContractFilter} narrows matching by
|
|
2324
|
-
* body, headers, or query — a request that hits the URL/method but fails the
|
|
2325
|
-
* filter counts as unmatched (strict contracts, CONVENTIONS D7).
|
|
2326
|
-
*
|
|
2327
|
-
* @example
|
|
2328
|
-
* defineContract({ request: http.get('/articles/{{uuid}}'), response: http.json(article) })
|
|
2329
|
-
* defineContract({ request: http.post(URL, { body: { user: 'alice' } }), response: http.empty() })
|
|
2330
|
-
*/
|
|
2331
|
-
declare const http: {
|
|
2332
|
-
any(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
2333
|
-
delete(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
2334
|
-
get(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
2335
|
-
patch(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
2336
|
-
post(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
2337
|
-
put(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
|
|
2338
|
-
/** Response: a body-less reply (204 by default). */
|
|
2339
|
-
empty(status?: number): ContractResponse;
|
|
2340
|
-
/** Response: an error status. Without a body, `{ error: 'HTTP <status>' }`. */
|
|
2341
|
-
error(status: number, body?: unknown): ContractResponse;
|
|
2342
|
-
/** Response: a JSON body (200 by default). */
|
|
2343
|
-
json(body: unknown, init?: HttpResponseInit): ContractResponse;
|
|
2344
|
-
/** Response: a text body, served as `text/plain` (200 by default). */
|
|
2345
|
-
text(body: string, init?: HttpResponseInit): ContractResponse;
|
|
2346
|
-
};
|
|
2347
|
-
//#endregion
|
|
2348
|
-
//#region src/integrations/openai/openai.d.ts
|
|
2349
|
-
type OpenAIChatFilter = {
|
|
2350
|
-
model?: TextFilter;
|
|
2351
|
-
system?: TextFilter;
|
|
2352
|
-
user?: TextFilter;
|
|
2353
|
-
tools?: string[];
|
|
2354
|
-
temperature?: number;
|
|
2355
|
-
};
|
|
2356
|
-
type OpenAIResponsesFilter = {
|
|
2357
|
-
model?: TextFilter;
|
|
2358
|
-
system?: TextFilter;
|
|
2359
|
-
user?: TextFilter;
|
|
2360
|
-
tools?: string[];
|
|
2361
|
-
};
|
|
2362
|
-
/**
|
|
2363
|
-
* OpenAI API intercept helpers.
|
|
2364
|
-
*/
|
|
2365
|
-
declare const openai: {
|
|
2366
|
-
/**
|
|
2367
|
-
* Request: match Chat Completions API calls. STRING filters mean EXACT
|
|
2368
|
-
* equality (pass the app's own prompt builder); loosen deliberately with a
|
|
2369
|
-
* RegExp or `match.includes(...)`.
|
|
2370
|
-
*
|
|
2371
|
-
* @example
|
|
2372
|
-
* openai.chat() // any chat call
|
|
2373
|
-
* openai.chat({ model: 'gpt-4o' }) // exact model
|
|
2374
|
-
* openai.chat({ system: buildPrompt() }) // exact system prompt
|
|
2375
|
-
* openai.chat({ system: /classify/ }) // pattern
|
|
2376
|
-
*/
|
|
2377
|
-
chat(filter?: OpenAIChatFilter): ContractRequest;
|
|
2378
|
-
/**
|
|
2379
|
-
* Request: match Responses API calls (AI SDK v5+) with auto-wrapping.
|
|
2380
|
-
* String filters mean EXACT equality (see {@link openai.chat}).
|
|
2381
|
-
* When used with a JSON file, the data is automatically wrapped in the
|
|
2382
|
-
* Responses API envelope.
|
|
2383
|
-
*
|
|
2384
|
-
* @param filter - Optional body filters.
|
|
2385
|
-
* @param url - Custom gateway URL (default: api.openai.com).
|
|
2386
|
-
*
|
|
2387
|
-
* @example
|
|
2388
|
-
* openai.responses({ user: /Report Ingestion/ }, GATEWAY)
|
|
2389
|
-
*/
|
|
2390
|
-
responses(filter?: OpenAIResponsesFilter, url?: string): ContractRequest;
|
|
2391
|
-
/**
|
|
2392
|
-
* Response: wrap data in Chat Completions format.
|
|
2393
|
-
*
|
|
2394
|
-
* @example
|
|
2395
|
-
* openai.reply({ categories: ['TECH'] })
|
|
2396
|
-
*/
|
|
2397
|
-
reply(data: unknown): ContractResponse;
|
|
2398
|
-
/** Response: return an OpenAI error. */
|
|
2399
|
-
error(status: number, message?: string): ContractResponse;
|
|
2400
|
-
/** Response: return malformed content. */
|
|
2401
|
-
malformed(content: string): ContractResponse;
|
|
2402
|
-
/** Response: simulate a timeout. */
|
|
2403
|
-
timeout(): ContractResponse;
|
|
2404
|
-
};
|
|
2405
|
-
//#endregion
|
|
2406
|
-
//#region src/vitest/mock-of.d.ts
|
|
2407
|
-
/** Factory signature that creates a deep mock proxy for any interface. */
|
|
2408
|
-
type MockPort = <T>() => DeepMockProxy<T>;
|
|
2409
|
-
/**
|
|
2410
|
-
* Create a deep mock proxy for a given type.
|
|
2411
|
-
* Wraps `vitest-mock-extended`'s `mockDeep` for convenient port mocking.
|
|
2412
|
-
*/
|
|
2413
|
-
declare const mockOf: MockPort;
|
|
2414
|
-
//#endregion
|
|
2415
|
-
//#region src/vitest/mock-of-date.d.ts
|
|
2416
|
-
/** Interface for freezing and resetting the global Date in tests. */
|
|
2417
|
-
type MockDatePort = {
|
|
2418
|
-
/** Restore the real Date object. */
|
|
2419
|
-
reset: () => void;
|
|
2420
|
-
/** Freeze `Date.now()` and `new Date()` to the given value. */
|
|
2421
|
-
set: (date: Date | number | string) => void;
|
|
2422
|
-
};
|
|
2423
|
-
/**
|
|
2424
|
-
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
2425
|
-
* Wraps the `mockdate` package.
|
|
2426
|
-
*/
|
|
2427
|
-
declare const mockOfDate: MockDatePort;
|
|
2428
|
-
//#endregion
|
|
2429
|
-
//#region src/index.d.ts
|
|
2430
|
-
declare module 'vitest' {
|
|
2431
|
-
interface Assertion<T = any> {
|
|
2432
|
-
/**
|
|
2433
|
-
* Assert the subject is empty — zero rows for a table (async), an
|
|
2434
|
-
* empty stream for a text accessor (console, errors, stdout).
|
|
2435
|
-
*/
|
|
2436
|
-
toBeEmpty: T extends TableAccessor ? () => Promise<void> : T extends {
|
|
2437
|
-
readonly comparableText: string;
|
|
2438
|
-
} ? () => Promise<void> : never;
|
|
2439
|
-
/** Assert the container is running. Async — docker-backed subject. */
|
|
2440
|
-
toBeRunning: T extends ContainerAccessor ? () => Promise<void> : never;
|
|
2441
|
-
/**
|
|
2442
|
-
* On `@jterrazz/test` accessors: assert the subject matches a fixture
|
|
2443
|
-
* file under `_expected/<name>` (flat — a slash creates a subfolder;
|
|
2444
|
-
* `.http` format for `result.response`). Async for filesystem/directory
|
|
2445
|
-
* subjects (tree compare on disk). Other subjects keep vitest-native
|
|
2446
|
-
* `toMatch` semantics (string substring / regexp).
|
|
2447
|
-
*
|
|
2448
|
-
* Pass `{ frozen: true }` to opt a single fixture OUT of update-mode
|
|
2449
|
-
* rewriting: a frozen fixture is never overwritten under `TEST_UPDATE=1`
|
|
2450
|
-
* (or vitest `-u`), and a frozen mismatch/missing fixture still throws.
|
|
2451
|
-
* Use it for a deliberately-wrong fixture whose diff/error rendering is
|
|
2452
|
-
* the behaviour under test.
|
|
2453
|
-
*/
|
|
2454
|
-
toMatch: T extends DirectoryAccessor | FilesystemAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends JsonAccessor | ResponseAccessor | TextAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => void : (expected: RegExp | string, options?: MatchFixtureOptions) => void;
|
|
2455
|
-
/**
|
|
2456
|
-
* Assert the table contains exactly the given rows for the given
|
|
2457
|
-
* columns. Cells accept `match.*` dynamic-value matchers. Async —
|
|
2458
|
-
* queries the database.
|
|
2459
|
-
*/
|
|
2460
|
-
toMatchRows: T extends TableAccessor ? (expected: {
|
|
2461
|
-
columns: string[];
|
|
2462
|
-
rows: readonly (readonly unknown[])[];
|
|
2463
|
-
}) => Promise<void> : never;
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
//#endregion
|
|
2467
|
-
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CaptureScope, type CliEnv, type CliHandle, type CliInput, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, 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 ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, HttpResult, 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 MockPort, Orchestrator, PageResult, type PostgresOptions, type RedisOptions, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, 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, TableAccessor, TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, complementary, content, contentinfo, defineContract, defineContracts, field, findContainersByLabel, form, heading, http, inspectContainer, link, main, match, mockOf, mockOfDate, navigation, openai, postgres, redis, region, removeContainers, search, specification, sqlite, testId, text, within };
|
|
2777
|
+
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, 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 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 MockPort, Orchestrator, PageResult, type PostgresOptions, type RedisOptions, type RenderResult, type RenderSubject, type RenderedText, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, 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, TableAccessor, TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, complementary, component, content, contentinfo, defineContract, defineContracts, dialog, disabled, enabled, field, findContainersByLabel, focused, form, heading, http, inspectContainer, link, listitem, main, match, mockOf, mockOfDate, navigation, openai, option, postgres, redis, region, removeContainers, row, search, selected, specification, sqlite, status, table, testId, text, valued, within };
|