@jterrazz/test 15.3.0 → 16.1.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 +120 -427
- package/dist/appium.adapter.js +71 -15
- package/dist/artifacts.js +25 -1735
- package/dist/browser/index.js +304 -233
- package/dist/checker.d.ts +1 -1
- package/dist/checker.js +1103 -97
- package/dist/fixtures.js +110 -0
- package/dist/index.d.ts +956 -971
- package/dist/index.js +2159 -2281
- package/dist/literate.js +1747 -0
- package/dist/match.js +625 -84
- package/dist/oxlint.cjs +3585 -1480
- package/dist/oxlint.d.cts +60 -48
- package/dist/oxlint.d.ts +60 -48
- package/dist/oxlint.js +3585 -1480
- package/dist/playwright.adapter.js +51 -8
- package/dist/{intercept.js → server.js} +2 -2
- package/dist/stream.js +6 -6
- package/dist/{ambiguity.js → substring-warning.js} +106 -7
- package/dist/vitest.d.ts +67 -48
- package/dist/vitest.js +229 -93
- package/docs/01-architecture.md +95 -0
- package/docs/02-developing.md +351 -0
- package/docs/03-testing.md +495 -0
- package/docs/04-operating.md +101 -0
- package/docs/05-module-tests.md +152 -0
- package/docs/06-integration.md +180 -0
- package/docs/07-component.md +246 -0
- package/docs/08-website.md +404 -0
- package/docs/09-mobile.md +270 -0
- package/docs/10-api.md +264 -0
- package/docs/11-jobs.md +157 -0
- package/docs/12-cli.md +677 -0
- package/docs/13-elements.md +242 -0
- package/docs/14-assertions.md +314 -0
- package/docs/15-tokens.md +253 -0
- package/docs/16-contracts.md +438 -0
- package/docs/17-services.md +188 -0
- package/docs/18-conventions.md +305 -0
- package/docs/19-linting.md +510 -0
- package/docs/README.md +36 -0
- package/docs/decisions/001-v9-conventions-on-three-channels.md +50 -0
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +49 -0
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +84 -0
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +81 -0
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +86 -0
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +245 -0
- package/docs/decisions/007-compose-mode-leaves-the-package.md +53 -0
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +75 -0
- package/docs/decisions/_template.md +22 -0
- package/docs/reference/classes/Matcher.md +120 -0
- package/docs/reference/classes/ProcessHandle.md +368 -0
- package/docs/reference/functions/defineContract.md +39 -0
- package/docs/reference/functions/defineContracts.md +36 -0
- package/docs/reference/functions/disabled.md +28 -0
- package/docs/reference/functions/enabled.md +20 -0
- package/docs/reference/functions/focused.md +26 -0
- package/docs/reference/functions/mockOf.md +79 -0
- package/docs/reference/functions/postgres.md +31 -0
- package/docs/reference/functions/process.md +33 -0
- package/docs/reference/functions/redis.md +28 -0
- package/docs/reference/functions/required.md +41 -0
- package/docs/reference/functions/selected.md +26 -0
- package/docs/reference/functions/sqlite.md +33 -0
- package/docs/reference/functions/testId.md +20 -0
- package/docs/reference/functions/text.md +40 -0
- package/docs/reference/functions/valued.md +28 -0
- package/docs/reference/functions/waitUntil.md +46 -0
- package/docs/reference/functions/within.md +29 -0
- package/docs/reference/index.md +196 -0
- package/docs/reference/interfaces/BaseResult.md +87 -0
- package/docs/reference/interfaces/CallResult.md +160 -0
- package/docs/reference/interfaces/CaptureScope.md +82 -0
- package/docs/reference/interfaces/CliResult.md +253 -0
- package/docs/reference/interfaces/ContainerAccessor.md +166 -0
- package/docs/reference/interfaces/DirectoryAccessor.md +33 -0
- package/docs/reference/interfaces/FetchResult.md +180 -0
- package/docs/reference/interfaces/FilesystemAccessor.md +46 -0
- package/docs/reference/interfaces/HttpResult.md +126 -0
- package/docs/reference/interfaces/JsonAccessor.md +46 -0
- package/docs/reference/interfaces/PageResult.md +356 -0
- package/docs/reference/interfaces/RenderResult.md +72 -0
- package/docs/reference/interfaces/RenderedText.md +110 -0
- package/docs/reference/interfaces/ResponseAccessor.md +47 -0
- package/docs/reference/interfaces/ScreenResult.md +128 -0
- package/docs/reference/interfaces/TableAccessor.md +26 -0
- package/docs/reference/interfaces/TextAccessor.md +96 -0
- package/docs/reference/type-aliases/ApiHandle.md +68 -0
- package/docs/reference/type-aliases/ApiSpecification.md +213 -0
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +68 -0
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +30 -0
- package/docs/reference/type-aliases/BrowserLinkElement.md +50 -0
- package/docs/reference/type-aliases/BrowserMetaElement.md +40 -0
- package/docs/reference/type-aliases/BrowserOpenOptions.md +87 -0
- package/docs/reference/type-aliases/BrowserPage.md +132 -0
- package/docs/reference/type-aliases/BrowserPort.md +51 -0
- package/docs/reference/type-aliases/CliEnv.md +11 -0
- package/docs/reference/type-aliases/CliHandle.md +80 -0
- package/docs/reference/type-aliases/CliInput.md +36 -0
- package/docs/reference/type-aliases/CliOutput.md +46 -0
- package/docs/reference/type-aliases/CliPort.md +58 -0
- package/docs/reference/type-aliases/CliSpecification.md +144 -0
- package/docs/reference/type-aliases/CliSpecificationOptions.md +134 -0
- package/docs/reference/type-aliases/ComponentChain.md +156 -0
- package/docs/reference/type-aliases/ComponentScenario.md +20 -0
- package/docs/reference/type-aliases/ComponentUi.md +47 -0
- package/docs/reference/type-aliases/ComponentVisitor.md +10 -0
- package/docs/reference/type-aliases/ContainerPort.md +113 -0
- package/docs/reference/type-aliases/Contract.md +68 -0
- package/docs/reference/type-aliases/ContractInput.md +13 -0
- package/docs/reference/type-aliases/ContractRequest.md +95 -0
- package/docs/reference/type-aliases/ContractResponder.md +23 -0
- package/docs/reference/type-aliases/ContractResponse.md +78 -0
- package/docs/reference/type-aliases/ContractResponseValue.md +13 -0
- package/docs/reference/type-aliases/Contracts.md +48 -0
- package/docs/reference/type-aliases/DatabaseKeys.md +16 -0
- package/docs/reference/type-aliases/DatabasePort.md +72 -0
- package/docs/reference/type-aliases/DeviceOpenOptions.md +46 -0
- package/docs/reference/type-aliases/DevicePort.md +51 -0
- package/docs/reference/type-aliases/DeviceScreen.md +32 -0
- package/docs/reference/type-aliases/DeviceTimeouts.md +39 -0
- package/docs/reference/type-aliases/DockerSpecConfig.md +43 -0
- package/docs/reference/type-aliases/DomMount.md +21 -0
- package/docs/reference/type-aliases/ElementKind.md +27 -0
- package/docs/reference/type-aliases/ElementMatch.md +74 -0
- package/docs/reference/type-aliases/ElementOptions.md +29 -0
- package/docs/reference/type-aliases/ElementRef.md +128 -0
- package/docs/reference/type-aliases/ExecOptions.md +37 -0
- package/docs/reference/type-aliases/FileAccessor.md +54 -0
- package/docs/reference/type-aliases/HonoApp.md +31 -0
- package/docs/reference/type-aliases/HttpContractFilter.md +49 -0
- package/docs/reference/type-aliases/HttpResponseInit.md +46 -0
- package/docs/reference/type-aliases/HttpStreamInit.md +58 -0
- package/docs/reference/type-aliases/IntegrationHandle.md +46 -0
- package/docs/reference/type-aliases/IntegrationSpecification.md +111 -0
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +42 -0
- package/docs/reference/type-aliases/Intercept.md +19 -0
- package/docs/reference/type-aliases/InterceptOptions.md +28 -0
- package/docs/reference/type-aliases/InterceptScope.md +11 -0
- package/docs/reference/type-aliases/IsolationStrategy.md +75 -0
- package/docs/reference/type-aliases/JobHandle.md +34 -0
- package/docs/reference/type-aliases/JobsHandle.md +57 -0
- package/docs/reference/type-aliases/JobsResult.md +16 -0
- package/docs/reference/type-aliases/JobsSpecification.md +94 -0
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +56 -0
- package/docs/reference/type-aliases/LandmarkKind.md +20 -0
- package/docs/reference/type-aliases/LiterateRunFlags.md +27 -0
- package/docs/reference/type-aliases/LiterateServeRegistration.md +70 -0
- package/docs/reference/type-aliases/MatchFixtureOptions.md +27 -0
- package/docs/reference/type-aliases/MatchableRequest.md +61 -0
- package/docs/reference/type-aliases/MatcherKind.md +34 -0
- package/docs/reference/type-aliases/MobileBackendOptions.md +28 -0
- package/docs/reference/type-aliases/MobileElementKind.md +14 -0
- package/docs/reference/type-aliases/MobileElementMatch.md +60 -0
- package/docs/reference/type-aliases/MobileElementRef.md +20 -0
- package/docs/reference/type-aliases/MobileHandle.md +65 -0
- package/docs/reference/type-aliases/MobileScenario.md +20 -0
- package/docs/reference/type-aliases/MobileSpecification.md +51 -0
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +126 -0
- package/docs/reference/type-aliases/MobileVisitor.md +81 -0
- package/docs/reference/type-aliases/MockOfOptions.md +26 -0
- package/docs/reference/type-aliases/MockPort.md +10 -0
- package/docs/reference/type-aliases/PinnedClock.md +10 -0
- package/docs/reference/type-aliases/PostgresOptions.md +32 -0
- package/docs/reference/type-aliases/ProcessOptions.md +112 -0
- package/docs/reference/type-aliases/RedisOptions.md +20 -0
- package/docs/reference/type-aliases/RenderSubject.md +12 -0
- package/docs/reference/type-aliases/ScreenNode.md +67 -0
- package/docs/reference/type-aliases/ServerPort.md +36 -0
- package/docs/reference/type-aliases/ServerResponse.md +46 -0
- package/docs/reference/type-aliases/ServerSpec.md +21 -0
- package/docs/reference/type-aliases/ServiceHandle.md +216 -0
- package/docs/reference/type-aliases/ServiceRecord.md +12 -0
- package/docs/reference/type-aliases/SpecDocument.md +84 -0
- package/docs/reference/type-aliases/SpecEnvToken.md +21 -0
- package/docs/reference/type-aliases/SpecFileAssertion.md +27 -0
- package/docs/reference/type-aliases/SpecFixture.md +30 -0
- package/docs/reference/type-aliases/SpecKind.md +8 -0
- package/docs/reference/type-aliases/SpecRun.md +108 -0
- package/docs/reference/type-aliases/SpecServeEntry.md +40 -0
- package/docs/reference/type-aliases/SpecStream.md +32 -0
- package/docs/reference/type-aliases/SpecificationConfig.md +290 -0
- package/docs/reference/type-aliases/SqliteOptions.md +36 -0
- package/docs/reference/type-aliases/SseEvent.md +58 -0
- package/docs/reference/type-aliases/StreamBody.md +61 -0
- package/docs/reference/type-aliases/TextFilter.md +22 -0
- package/docs/reference/type-aliases/VisitScenario.md +20 -0
- package/docs/reference/type-aliases/Visitor.md +215 -0
- package/docs/reference/type-aliases/WaitUntilOptions.md +46 -0
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +37 -0
- package/docs/reference/type-aliases/WebsiteHandle.md +51 -0
- package/docs/reference/type-aliases/WebsiteSpecification.md +118 -0
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +40 -0
- package/docs/reference/variables/anthropic.md +19 -0
- package/docs/reference/variables/banner.md +10 -0
- package/docs/reference/variables/button.md +10 -0
- package/docs/reference/variables/clock.md +16 -0
- package/docs/reference/variables/complementary.md +10 -0
- package/docs/reference/variables/component.md +10 -0
- package/docs/reference/variables/content.md +10 -0
- package/docs/reference/variables/contentinfo.md +10 -0
- package/docs/reference/variables/dialog.md +10 -0
- package/docs/reference/variables/field.md +10 -0
- package/docs/reference/variables/form.md +10 -0
- package/docs/reference/variables/heading.md +10 -0
- package/docs/reference/variables/http.md +40 -0
- package/docs/reference/variables/intercept.md +11 -0
- package/docs/reference/variables/link.md +10 -0
- package/docs/reference/variables/listitem.md +10 -0
- package/docs/reference/variables/main.md +10 -0
- package/docs/reference/variables/match.md +8 -0
- package/docs/reference/variables/navigation.md +10 -0
- package/docs/reference/variables/openai.md +21 -0
- package/docs/reference/variables/option.md +17 -0
- package/docs/reference/variables/region.md +10 -0
- package/docs/reference/variables/row.md +10 -0
- package/docs/reference/variables/search.md +10 -0
- package/docs/reference/variables/specification.md +8 -0
- package/docs/reference/variables/status.md +10 -0
- package/docs/reference/variables/table.md +10 -0
- package/package.json +32 -11
- package/dist/spec-document.js +0 -651
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Scalar } from "yaml";
|
|
|
2
2
|
import { DeepMockProxy, MockProxy } from "vitest-mock-extended";
|
|
3
3
|
import { ReactNode as ComponentUi } from "react";
|
|
4
4
|
import "vitest/browser";
|
|
5
|
-
//#region src/
|
|
5
|
+
//#region src/model/contracts/types.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* The observed outgoing request, reduced to what contract matchers inspect.
|
|
8
8
|
* Built once per request by the engine (MSW on api/jobs, the stub backend on
|
|
@@ -104,7 +104,7 @@ type ContractResponder = (request: MatchableRequest) => ContractResponse;
|
|
|
104
104
|
*/
|
|
105
105
|
type ContractResponseValue = ContractResponder | ContractResponse;
|
|
106
106
|
//#endregion
|
|
107
|
-
//#region src/
|
|
107
|
+
//#region src/model/contracts/contract.d.ts
|
|
108
108
|
/**
|
|
109
109
|
* A declared external interaction: what to match (`request`) and what to reply
|
|
110
110
|
* (`response`), together in one named artifact. Contracts live in TypeScript
|
|
@@ -189,12 +189,25 @@ declare function defineContract(contract: Contract): Contract;
|
|
|
189
189
|
*/
|
|
190
190
|
declare function defineContracts(...items: ContractInput[]): Contracts;
|
|
191
191
|
//#endregion
|
|
192
|
-
//#region src/
|
|
192
|
+
//#region src/model/contracts/intercept.d.ts
|
|
193
193
|
/**
|
|
194
194
|
* One block's worth of declared network. Disposed at the end of the scope that
|
|
195
195
|
* declared it: the handlers are dropped, and an unmatched request is thrown.
|
|
196
196
|
*/
|
|
197
197
|
type InterceptScope = AsyncDisposable;
|
|
198
|
+
/** What a scope may state about the network it declares. */
|
|
199
|
+
type InterceptOptions = {
|
|
200
|
+
/**
|
|
201
|
+
* The base a RELATIVE request resolves against, for the life of the scope.
|
|
202
|
+
*
|
|
203
|
+
* A module that calls `fetch('/api/posts')` is browser code: under node the
|
|
204
|
+
* URL cannot be parsed at all, so the request fails before any contract can
|
|
205
|
+
* answer it and the test's only way out was to replace `fetch` itself. With
|
|
206
|
+
* an origin stated, the relative call resolves here and the declared
|
|
207
|
+
* contracts — path form or absolute — match it as they would any other.
|
|
208
|
+
*/
|
|
209
|
+
origin?: string;
|
|
210
|
+
};
|
|
198
211
|
/**
|
|
199
212
|
* The module-scope network double. Resolves once the engine is listening —
|
|
200
213
|
* which is why it is a promise, and why the canonical form awaits it:
|
|
@@ -207,9 +220,9 @@ type InterceptScope = AsyncDisposable;
|
|
|
207
220
|
* first handler is in place, so the shape refuses the racy spelling: a promise
|
|
208
221
|
* is not an `AsyncDisposable`, and the compiler says so.
|
|
209
222
|
*/
|
|
210
|
-
type Intercept = ((contracts: ContractInput) => Promise<InterceptScope>) & ((request: ContractRequest, response: ContractResponder | ContractResponse) => Promise<InterceptScope>);
|
|
223
|
+
type Intercept = ((contracts: ContractInput, options?: InterceptOptions) => Promise<InterceptScope>) & ((request: ContractRequest, response: ContractResponder | ContractResponse, options?: InterceptOptions) => Promise<InterceptScope>);
|
|
211
224
|
//#endregion
|
|
212
|
-
//#region src/
|
|
225
|
+
//#region src/model/matching/match.d.ts
|
|
213
226
|
/**
|
|
214
227
|
* Code-side dynamic-value matchers (`match.*`) and the capture scope shared
|
|
215
228
|
* by every assertion of a single spec execution.
|
|
@@ -291,7 +304,52 @@ declare class CaptureScope {
|
|
|
291
304
|
set(name: string, value: unknown): void;
|
|
292
305
|
}
|
|
293
306
|
//#endregion
|
|
294
|
-
//#region src/
|
|
307
|
+
//#region src/model/result/directory.d.ts
|
|
308
|
+
/**
|
|
309
|
+
* Read-only accessor for a directory produced by a spec action.
|
|
310
|
+
*
|
|
311
|
+
* Assertions go through `expect()` (async — they walk the disk):
|
|
312
|
+
* `await expect(result.directory('out')).toMatch('scaffold/out')`
|
|
313
|
+
* compares the tree against the fixture directory `_expected/<name>/`.
|
|
314
|
+
*/
|
|
315
|
+
declare class DirectoryAccessor {
|
|
316
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
317
|
+
readonly captures: CaptureScope;
|
|
318
|
+
/** @internal Absolute path of the directory under assertion. */
|
|
319
|
+
readonly root: string;
|
|
320
|
+
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
321
|
+
readonly testDir: string;
|
|
322
|
+
constructor(absPath: string, testDir: string, captures?: CaptureScope);
|
|
323
|
+
/** List all files (recursively) under the directory, sorted. */
|
|
324
|
+
files(options?: {
|
|
325
|
+
ignore?: string[];
|
|
326
|
+
}): Promise<string[]>;
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/model/result/filesystem.d.ts
|
|
330
|
+
/**
|
|
331
|
+
* Read-only accessor for the whole temporary working directory used by a
|
|
332
|
+
* command spec.
|
|
333
|
+
*
|
|
334
|
+
* Assertions go through `expect()` (async — they walk the disk):
|
|
335
|
+
* `await expect(result.filesystem).toMatch('scaffolded')` compares the
|
|
336
|
+
* tree against the fixture directory `_expected/<name>/`.
|
|
337
|
+
*/
|
|
338
|
+
declare class FilesystemAccessor {
|
|
339
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
340
|
+
readonly captures: CaptureScope;
|
|
341
|
+
/** The absolute path of the temporary working directory. */
|
|
342
|
+
readonly cwd: string;
|
|
343
|
+
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
344
|
+
readonly testDir: string;
|
|
345
|
+
constructor(cwd: string, testDir: string, captures?: CaptureScope);
|
|
346
|
+
/** List all files (recursively) under the working directory, sorted. */
|
|
347
|
+
files(options?: {
|
|
348
|
+
ignore?: string[];
|
|
349
|
+
}): Promise<string[]>;
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/model/result/json.d.ts
|
|
295
353
|
/**
|
|
296
354
|
* Read-only accessor for a JSON payload parsed from a text stream (stdout).
|
|
297
355
|
*
|
|
@@ -327,214 +385,220 @@ declare class JsonAccessor<Value = unknown> {
|
|
|
327
385
|
get value(): Value;
|
|
328
386
|
}
|
|
329
387
|
//#endregion
|
|
330
|
-
//#region src/
|
|
331
|
-
/**
|
|
332
|
-
* The landmark roles — the standard set of page regions, and the only
|
|
333
|
-
* containers a scope can name. Closed on purpose: ARIA defines exactly these,
|
|
334
|
-
* so `within()` never becomes a second selector language.
|
|
335
|
-
*/
|
|
336
|
-
type LandmarkKind = 'banner' | 'complementary' | 'contentinfo' | 'form' | 'main' | 'navigation' | 'region' | 'search';
|
|
388
|
+
//#region src/model/result/match-options.d.ts
|
|
337
389
|
/**
|
|
338
|
-
* The
|
|
390
|
+
* The per-call option both `toMatch` implementations honour.
|
|
339
391
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
392
|
+
* A golden is read from disk under node and through a server command from
|
|
393
|
+
* inside a page, and BOTH builds must answer `{ frozen: true }` the same way —
|
|
394
|
+
* so the option is declared here, where the node matchers and the page's
|
|
395
|
+
* matchers can each reach it, rather than in the runner's own folder, which a
|
|
396
|
+
* seam may not import (rule I1).
|
|
344
397
|
*/
|
|
345
|
-
type ElementKind = 'button' | 'dialog' | 'field' | 'heading' | 'link' | 'listitem' | 'option' | 'row' | 'status' | 'table' | 'testId' | 'text';
|
|
346
398
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
399
|
+
* Per-call options for the fixture-file `toMatch` subjects. `frozen` opts a
|
|
400
|
+
* single fixture OUT of update-mode rewriting: a frozen fixture is NEVER
|
|
401
|
+
* written under `TEST_UPDATE=1` (or vitest `-u`) — in update mode a frozen
|
|
402
|
+
* mismatch still throws its diff, and a frozen missing fixture still throws its
|
|
403
|
+
* "does not exist" error. This is what makes a DELIBERATELY-WRONG fixture (the
|
|
404
|
+
* subject of a negative test that asserts the mismatch/error rendering)
|
|
405
|
+
* survivable across update runs instead of being silently overwritten with the
|
|
406
|
+
* actual output.
|
|
354
407
|
*/
|
|
355
|
-
type
|
|
356
|
-
|
|
357
|
-
* Match the accessible name as a whole string rather than a substring.
|
|
358
|
-
* Default (`false`) mirrors playwright: `link('Articles')` also matches
|
|
359
|
-
* "Read Articles".
|
|
360
|
-
*/
|
|
361
|
-
exact?: boolean;
|
|
362
|
-
/**
|
|
363
|
-
* Assert about the element's FOCUS rather than its presence — built by
|
|
364
|
-
* `focused(element)`. The accessibility tree carries no focus state, so
|
|
365
|
-
* where the keyboard is cannot be goldened; it is asserted by a verb.
|
|
366
|
-
*/
|
|
367
|
-
focused?: boolean;
|
|
368
|
-
/**
|
|
369
|
-
* Narrows to "and it is (not) accepting input" — built by `disabled(x)` /
|
|
370
|
-
* `enabled(x)`. Like `focused`, a STATE the vocabulary can already name the
|
|
371
|
-
* element of; unlike it, one the ARIA tree carries too (`[disabled]`), so
|
|
372
|
-
* this modifier is what names the ONE control a test is about, and it
|
|
373
|
-
* reaches the direction a behavioural substitute cannot — clicking a
|
|
374
|
-
* disabled control is a timeout, never an answer.
|
|
375
|
-
*/
|
|
376
|
-
disabled?: boolean;
|
|
377
|
-
kind: ElementKind | LandmarkKind;
|
|
378
|
-
/** Landmarks may be anonymous (`main()`, `banner()`); everything else is named. */
|
|
379
|
-
name?: string;
|
|
380
|
-
/**
|
|
381
|
-
* Narrows to "and it is (not) the chosen one" — built by `selected(x)`.
|
|
382
|
-
* An option of a COLLAPSED select is in the tree and in the document, and
|
|
383
|
-
* it has no box on the screen, so this facet of it is read from the node
|
|
384
|
-
* (`:checked`) rather than from what a visitor could point at.
|
|
385
|
-
*/
|
|
386
|
-
selected?: boolean;
|
|
387
|
-
/**
|
|
388
|
-
* The value the field must hold — built by `valued(field('Title'), '…')`.
|
|
389
|
-
* A field's value is a PROPERTY of the node: a controlled input's `value`
|
|
390
|
-
* attribute never moves, so nothing but the live value answers for it.
|
|
391
|
-
*/
|
|
392
|
-
value?: string;
|
|
393
|
-
/**
|
|
394
|
-
* Restrict the search to the elements of another descriptor — built by
|
|
395
|
-
* `within(scope, target)`. Chains: a scope may itself carry a scope.
|
|
396
|
-
*/
|
|
397
|
-
scope?: ElementRef;
|
|
408
|
+
type MatchFixtureOptions = {
|
|
409
|
+
frozen?: boolean;
|
|
398
410
|
};
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/model/ports/server.port.d.ts
|
|
399
413
|
/**
|
|
400
|
-
*
|
|
401
|
-
* the evidence the ambiguity error enumerates so the author can disambiguate
|
|
402
|
-
* without opening a browser.
|
|
414
|
+
* HTTP response returned by a server port, with parsed JSON body.
|
|
403
415
|
*/
|
|
404
|
-
type
|
|
405
|
-
/**
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
/** Nearest landmark ancestor (`nav`, `footer`, `main`…), when there is one. */
|
|
412
|
-
context?: string | undefined;
|
|
413
|
-
/** The attribute that disambiguates most — `href` for links, `name` for fields. */
|
|
414
|
-
detail?: string | undefined;
|
|
415
|
-
/** Tag name, lower-cased. */
|
|
416
|
-
tag: string;
|
|
417
|
-
/** Text content, whitespace-collapsed and truncated. */
|
|
418
|
-
text: string;
|
|
419
|
-
};
|
|
420
|
-
/** A `<link>` element captured from the rendered document's head. */
|
|
421
|
-
type BrowserLinkElement = {
|
|
422
|
-
href: string;
|
|
423
|
-
hreflang?: string | undefined;
|
|
424
|
-
rel: string;
|
|
425
|
-
type?: string | undefined;
|
|
426
|
-
};
|
|
427
|
-
/** A `<meta>` element captured from the rendered document's head. */
|
|
428
|
-
type BrowserMetaElement = {
|
|
429
|
-
content: string;
|
|
430
|
-
name?: string | undefined;
|
|
431
|
-
property?: string | undefined;
|
|
432
|
-
};
|
|
433
|
-
/** A console message emitted while the page loaded or the scenario ran. */
|
|
434
|
-
type BrowserConsoleMessage = {
|
|
435
|
-
text: string;
|
|
436
|
-
type: string;
|
|
416
|
+
type ServerResponse = {
|
|
417
|
+
/** HTTP status code. */
|
|
418
|
+
status: number;
|
|
419
|
+
/** Parsed JSON response body, or null if parsing failed. */
|
|
420
|
+
body: unknown;
|
|
421
|
+
/** Response headers as a flat key-value map. */
|
|
422
|
+
headers: Record<string, string>;
|
|
437
423
|
};
|
|
438
424
|
/**
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* synchronization primitive: it retries until the element is visible and
|
|
442
|
-
* fails at the timeout. There is no sleep and no conditional helper.
|
|
425
|
+
* Abstract server interface for specification runners.
|
|
426
|
+
* Integration mode uses an in-process Hono app; E2E mode uses real HTTP via fetch.
|
|
443
427
|
*/
|
|
444
|
-
type
|
|
445
|
-
/**
|
|
446
|
-
|
|
447
|
-
/** Click the element. */
|
|
448
|
-
click: (element: ElementRef) => Promise<void>;
|
|
449
|
-
/** Fill a form field with a value. */
|
|
450
|
-
fill: (element: ElementRef, value: string) => Promise<void>;
|
|
451
|
-
/**
|
|
452
|
-
* Wait until the element is GONE — hidden, or never in the document. The
|
|
453
|
-
* absence primitive: `see()` cannot answer "the dialog closed", because an
|
|
454
|
-
* element that never appears and one that disappeared read the same to it.
|
|
455
|
-
*/
|
|
456
|
-
gone: (element: ElementRef) => Promise<void>;
|
|
457
|
-
/** Navigate to a path of the site under test. */
|
|
458
|
-
goto: (path: string) => Promise<void>;
|
|
459
|
-
/** Hover the element. */
|
|
460
|
-
hover: (element: ElementRef) => Promise<void>;
|
|
461
|
-
/** Press a key (e.g. `Enter`). */
|
|
462
|
-
press: (key: string) => Promise<void>;
|
|
463
|
-
/** Wait until the element is visible — the only synchronization primitive. */
|
|
464
|
-
see: (element: ElementRef) => Promise<void>;
|
|
465
|
-
/** Select an option in a select field. */
|
|
466
|
-
select: (element: ElementRef, option: string) => Promise<void>;
|
|
467
|
-
};
|
|
468
|
-
/** The behavior of a visit — the When of the spec; assertions stay in the Then. */
|
|
469
|
-
type VisitScenario = (visitor: Visitor) => Promise<void>;
|
|
470
|
-
/** Per-visit options forwarded to the browser context. */
|
|
471
|
-
type BrowserOpenOptions = {
|
|
472
|
-
/**
|
|
473
|
-
* Extra origins the `external: 'block'` policy lets through — the
|
|
474
|
-
* declared stub backend the page legitimately fetches from.
|
|
475
|
-
*/
|
|
476
|
-
allowedOrigins?: string[] | undefined;
|
|
477
|
-
/**
|
|
478
|
-
* Base URL of the site under test — the origin `goto()` resolves against
|
|
479
|
-
* and the boundary of the `external` policy.
|
|
480
|
-
*/
|
|
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;
|
|
487
|
-
/**
|
|
488
|
-
* Cross-origin request policy. `'block'` aborts every request leaving
|
|
489
|
-
* the site under test (analytics, CDNs) — the browser-side analog of
|
|
490
|
-
* strict intercepts. `'allow'` lets them through (deployed-site mode).
|
|
491
|
-
*/
|
|
492
|
-
external: 'allow' | 'block';
|
|
493
|
-
/** Extra HTTP headers sent with every request of the visit (incl. User-Agent overrides). */
|
|
494
|
-
headers?: Record<string, string> | undefined;
|
|
495
|
-
/** The interaction scenario to run after load; the capture reflects the final state. */
|
|
496
|
-
scenario?: undefined | VisitScenario;
|
|
428
|
+
type ServerPort = {
|
|
429
|
+
/** Send an HTTP request and return the parsed response. */
|
|
430
|
+
request: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<ServerResponse>;
|
|
497
431
|
};
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/model/result/response.d.ts
|
|
498
434
|
/**
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
435
|
+
* Read-only accessor for an HTTP response.
|
|
436
|
+
*
|
|
437
|
+
* Assertions go through `expect()`:
|
|
438
|
+
* `expect(result.response).toMatch('created.http')` compares status,
|
|
439
|
+
* a subset of headers, and the JSON body against `_expected/<name>` —
|
|
440
|
+
* with `{{placeholder}}` support in all three.
|
|
502
441
|
*/
|
|
503
|
-
|
|
504
|
-
/**
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
|
|
510
|
-
/**
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
442
|
+
declare class ResponseAccessor {
|
|
443
|
+
/** The parsed JSON response body (null when parsing failed). */
|
|
444
|
+
readonly body: unknown;
|
|
445
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
446
|
+
readonly captures: CaptureScope;
|
|
447
|
+
/** Response headers as a flat, lower-cased key-value map. */
|
|
448
|
+
readonly headers: Record<string, string>;
|
|
449
|
+
/** The HTTP response status code. */
|
|
450
|
+
readonly status: number;
|
|
451
|
+
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
452
|
+
readonly testDir: string;
|
|
453
|
+
constructor(response: ServerResponse, testDir: string, captures?: CaptureScope);
|
|
454
|
+
}
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/model/ports/database.port.d.ts
|
|
457
|
+
/**
|
|
458
|
+
* Abstract database interface for specification runners.
|
|
459
|
+
* Implement this to plug in your database stack (e.g. Postgres, SQLite).
|
|
460
|
+
*/
|
|
461
|
+
type DatabasePort = {
|
|
462
|
+
/** Execute raw SQL (for seeding test data). */
|
|
463
|
+
seed: (sql: string) => Promise<void>;
|
|
464
|
+
/** Query a table and return rows as arrays of values. */
|
|
465
|
+
query: (table: string, columns: string[]) => Promise<unknown[][]>;
|
|
466
|
+
/** Reset database to clean state between tests. */
|
|
467
|
+
reset: () => Promise<void>;
|
|
524
468
|
};
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region src/model/result/table.d.ts
|
|
525
471
|
/**
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
472
|
+
* Read-only accessor for a database table after a specification run.
|
|
473
|
+
*
|
|
474
|
+
* Assertions go through `expect()` (async — they query the database):
|
|
475
|
+
*
|
|
476
|
+
* await expect(result.table('users', { database: 'db' })).toMatchRows({
|
|
477
|
+
* columns: ['name'],
|
|
478
|
+
* rows: [['Alice']],
|
|
479
|
+
* });
|
|
480
|
+
* await expect(result.table('users', { database: 'db' })).toBeEmpty();
|
|
529
481
|
*/
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
|
|
482
|
+
declare class TableAccessor {
|
|
483
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
484
|
+
readonly captures: CaptureScope;
|
|
485
|
+
/** @internal */
|
|
486
|
+
private readonly db;
|
|
487
|
+
/** The table name this accessor reads. */
|
|
488
|
+
readonly name: string;
|
|
489
|
+
constructor(name: string, db: DatabasePort, captures?: CaptureScope);
|
|
490
|
+
/** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
|
|
491
|
+
query(columns: string[]): Promise<unknown[][]>;
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/model/result/text.d.ts
|
|
495
|
+
/**
|
|
496
|
+
* Read-only accessor for a captured text handle — THE universal one: stdout,
|
|
497
|
+
* stderr, container logs, and file text all surface as a `TextAccessor`.
|
|
498
|
+
*
|
|
499
|
+
* Backed by a primitive string, exposed via {@link text}, but also provides
|
|
500
|
+
* `toString()` / `valueOf()` for string coercion, so common patterns like
|
|
501
|
+
* `String(result.stdout)` and template-literal interpolation still work.
|
|
502
|
+
*
|
|
503
|
+
* ANSI escape sequences are stripped by default before every comparison
|
|
504
|
+
* (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
|
|
505
|
+
* an escape hatch for application noise not covered by the `{{token}}`
|
|
506
|
+
* grammar.
|
|
507
|
+
*
|
|
508
|
+
* Text operations are **closed** over the type: `.grep(pattern)` returns a
|
|
509
|
+
* `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
|
|
510
|
+
* context and the `transform`, so results are chainable
|
|
511
|
+
* (`result.stdout.grep(a).grep(b)`) and snapshot-able
|
|
512
|
+
* (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
|
|
513
|
+
*
|
|
514
|
+
* Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
|
|
515
|
+
* and `expect(result.stdout).toMatch('name.txt')` (resolved against
|
|
516
|
+
* `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
|
|
517
|
+
*/
|
|
518
|
+
declare class TextAccessor {
|
|
519
|
+
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
520
|
+
readonly captures: CaptureScope;
|
|
521
|
+
/** @internal Stream label used in failure messages ("stdout" / "stderr"). */
|
|
522
|
+
readonly streamName: string;
|
|
523
|
+
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
524
|
+
readonly testDir: string;
|
|
525
|
+
/** The raw captured text (never transformed, ANSI preserved). */
|
|
526
|
+
readonly text: string;
|
|
527
|
+
/** @internal Normaliser applied before comparisons, never to fixtures. */
|
|
528
|
+
readonly transform?: ((text: string) => string) | undefined;
|
|
529
|
+
constructor(value: string, streamName: string, testDir: string, options?: {
|
|
530
|
+
captures?: CaptureScope | undefined;
|
|
531
|
+
transform?: ((text: string) => string) | undefined;
|
|
532
|
+
});
|
|
533
|
+
/** @internal The text as compared by matchers — ANSI stripped, transform applied. */
|
|
534
|
+
get comparableText(): string;
|
|
535
|
+
/**
|
|
536
|
+
* Keep only the blank-line-separated blocks of the text that contain
|
|
537
|
+
* `pattern` (how linter/compiler output is structured), returned as a new
|
|
538
|
+
* `TextAccessor` — the same subject type, so the result is chainable and
|
|
539
|
+
* snapshot-able. The scalpel for probing large tool outputs; snapshot the
|
|
540
|
+
* whole surface by default, reach for `.grep()` for targeted checks.
|
|
541
|
+
*/
|
|
542
|
+
grep(pattern: string): TextAccessor;
|
|
543
|
+
toString(): string;
|
|
544
|
+
valueOf(): string;
|
|
545
|
+
}
|
|
546
|
+
//#endregion
|
|
547
|
+
//#region src/model/result/result.d.ts
|
|
548
|
+
/** Read-only handle to a single file produced by a spec action. */
|
|
549
|
+
type FileAccessor = {
|
|
550
|
+
/** The UTF-8 text content. Throws if the file does not exist. */
|
|
551
|
+
readonly content: string;
|
|
552
|
+
readonly exists: boolean;
|
|
553
|
+
/** The file text as a {@link TextAccessor}, keeping only blocks matching `pattern`. */
|
|
554
|
+
grep: (pattern: string) => TextAccessor;
|
|
555
|
+
};
|
|
556
|
+
type BaseResultOptions = {
|
|
557
|
+
config: SpecificationConfig;
|
|
558
|
+
testDir: string;
|
|
559
|
+
workDir?: string | undefined;
|
|
535
560
|
};
|
|
561
|
+
/**
|
|
562
|
+
* Base result - common accessors available after any action type.
|
|
563
|
+
* Extended by HttpResult, CliResult, and used directly by job results.
|
|
564
|
+
*
|
|
565
|
+
* Every result carries a fresh {@link CaptureScope}: `match.ref()` captures
|
|
566
|
+
* and `{{type#ref}}` placeholders are scoped to one spec execution.
|
|
567
|
+
*/
|
|
568
|
+
declare class BaseResult {
|
|
569
|
+
/** @internal Ref-capture scope shared by every assertion on this result. */
|
|
570
|
+
readonly captures: CaptureScope;
|
|
571
|
+
protected config: SpecificationConfig;
|
|
572
|
+
protected testDir: string;
|
|
573
|
+
protected workDir?: string | undefined;
|
|
574
|
+
constructor(options: BaseResultOptions);
|
|
575
|
+
/** Access a directory (relative to the working directory) for snapshot assertions. */
|
|
576
|
+
directory(path?: string): DirectoryAccessor;
|
|
577
|
+
/** Access a single file (relative to the working directory) for content assertions. */
|
|
578
|
+
file(path: string): FileAccessor;
|
|
579
|
+
/** Access a database table for row-level assertions via expect() matchers. */
|
|
580
|
+
table(tableName: string, options?: {
|
|
581
|
+
database?: string;
|
|
582
|
+
}): TableAccessor;
|
|
583
|
+
private resolveDatabase;
|
|
584
|
+
}
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region src/facets/api/api.result.d.ts
|
|
587
|
+
/** Result from an HTTP action (.request(), .get(), .post(), .put(), .delete()). */
|
|
588
|
+
declare class HttpResult extends BaseResult {
|
|
589
|
+
private readonly responseData;
|
|
590
|
+
constructor(options: {
|
|
591
|
+
config: SpecificationConfig;
|
|
592
|
+
response: ServerResponse;
|
|
593
|
+
testDir: string;
|
|
594
|
+
});
|
|
595
|
+
/** Access the HTTP response (status, headers, body) for assertions. */
|
|
596
|
+
get response(): ResponseAccessor;
|
|
597
|
+
/** The HTTP response status code. */
|
|
598
|
+
get status(): number;
|
|
599
|
+
}
|
|
536
600
|
//#endregion
|
|
537
|
-
//#region src/
|
|
601
|
+
//#region src/model/ports/cli.port.d.ts
|
|
538
602
|
/**
|
|
539
603
|
* Raw output from a command execution, including exit code and captured output streams.
|
|
540
604
|
*/
|
|
@@ -585,516 +649,457 @@ type CliPort = {
|
|
|
585
649
|
watch: (args: string, cwd: string, options: ExecOptions, env?: CliEnv) => Promise<CliOutput>;
|
|
586
650
|
};
|
|
587
651
|
//#endregion
|
|
588
|
-
//#region src/
|
|
652
|
+
//#region src/facets/cli/literate.d.ts
|
|
589
653
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
654
|
+
* Running a `<case>.spec.yaml` document: one scenario, one working directory,
|
|
655
|
+
* one set of servers, every run executed and asserted.
|
|
656
|
+
*
|
|
657
|
+
* The engine composes pieces that already exist rather than adding a second of
|
|
658
|
+
* anything — {@link copyPlan} for `fixture:`, the runner's own command adapter
|
|
659
|
+
* for each run, {@link ServeAdapter} for `serve:`, the `{{token}}` engine for
|
|
660
|
+
* every comparison, and {@link mergeTextPreservingPlaceholders} for the
|
|
661
|
+
* rewrite. What is new here is only the ORCHESTRATION the chain cannot express:
|
|
662
|
+
* several commands sharing one working directory, each judged the moment it
|
|
663
|
+
* ends — so what a run states about that directory is what IT left there, and
|
|
664
|
+
* the first run to disagree is the one the failure names.
|
|
592
665
|
*/
|
|
593
|
-
type DatabasePort = {
|
|
594
|
-
/** Execute raw SQL (for seeding test data). */
|
|
595
|
-
seed: (sql: string) => Promise<void>;
|
|
596
|
-
/** Query a table and return rows as arrays of values. */
|
|
597
|
-
query: (table: string, columns: string[]) => Promise<unknown[][]>;
|
|
598
|
-
/** Reset database to clean state between tests. */
|
|
599
|
-
reset: () => Promise<void>;
|
|
600
|
-
};
|
|
601
|
-
//#endregion
|
|
602
|
-
//#region src/specification/ports/device.port.d.ts
|
|
603
666
|
/**
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
* There is ONE element vocabulary across facets: `button('Bookmark')` works
|
|
607
|
-
* in a visit scenario and a mobile scenario alike. Landmarks have no iOS
|
|
608
|
-
* analog — passing one to a mobile verb refuses at runtime.
|
|
667
|
+
* A server a document may start by name (`serve: mcp`), registered once per app
|
|
668
|
+
* in the `serve` option of `specification.cli()`.
|
|
609
669
|
*/
|
|
610
|
-
type
|
|
670
|
+
type LiterateServeRegistration = {
|
|
671
|
+
/** Shell command that starts the server, run from the project root. */
|
|
672
|
+
command: string;
|
|
673
|
+
/** The variable the resolved URL is bound to in every run's child env. */
|
|
674
|
+
env: string;
|
|
675
|
+
/**
|
|
676
|
+
* Matched against the server's output; the FIRST capture group is the port
|
|
677
|
+
* it chose — named (`(?<port>\d+)`) or not, it is group 1 either way.
|
|
678
|
+
*/
|
|
679
|
+
ready: RegExp;
|
|
680
|
+
/** Builds the URL bound to {@link env} from the announced port. */
|
|
681
|
+
url: (port: number) => string;
|
|
682
|
+
};
|
|
683
|
+
/** Per-call options for {@link runSpecDocument} / `cli.run()`. */
|
|
684
|
+
type LiterateRunFlags = {
|
|
685
|
+
/**
|
|
686
|
+
* Opt this document OUT of the update-mode rewrite. A frozen document is
|
|
687
|
+
* NEVER written under `TEST_UPDATE=1`: its mismatch still throws its diff.
|
|
688
|
+
* That is what makes a DELIBERATELY-WRONG `.spec.yaml` — one whose failure
|
|
689
|
+
* rendering is the subject of a negative test — survive an update run
|
|
690
|
+
* instead of being silently corrected into a passing file. The document's
|
|
691
|
+
* mirror of `toMatch(name, { frozen: true })`.
|
|
692
|
+
*/
|
|
693
|
+
frozen?: boolean | undefined;
|
|
694
|
+
};
|
|
695
|
+
//#endregion
|
|
696
|
+
//#region src/facets/integration/integration.result.d.ts
|
|
697
|
+
/** What `.call()` produced: the value it returned, or the error it threw. */
|
|
698
|
+
type CallOutcome = {
|
|
699
|
+
error: unknown;
|
|
700
|
+
threw: boolean;
|
|
701
|
+
value: unknown;
|
|
702
|
+
};
|
|
703
|
+
type CallResultOptions = BaseResultOptions & {
|
|
704
|
+
outcome: CallOutcome;
|
|
705
|
+
};
|
|
611
706
|
/**
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
* device integration. Structurally the same ref type as the browser's, so the
|
|
616
|
-
* vocabulary stays single; kinds outside {@link MobileElementKind} (the ARIA
|
|
617
|
-
* landmarks) are refused at runtime with a message naming the boundary.
|
|
707
|
+
* What `result.value` is, decided by what the call returned: a string reads as
|
|
708
|
+
* a stream, anything else as JSON. `.call<T>()` already knows T, so the test
|
|
709
|
+
* reads one field (`result.value.value.ok`) without narrowing a union first.
|
|
618
710
|
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
711
|
+
* Three answers, because a type has three ways of relating to `string`:
|
|
712
|
+
*
|
|
713
|
+
* - EXACTLY a string — a `TextAccessor`, and nothing to narrow;
|
|
714
|
+
* - it MAY be a string (`string | undefined`, `string | number`, `any`) — the
|
|
715
|
+
* UNION, which the test narrows. The type used to read the static type only
|
|
716
|
+
* while the accessor was built from the RUNTIME value, so a subject typed
|
|
717
|
+
* `string | undefined` was handed back as a `JsonAccessor`: `.value.value`
|
|
718
|
+
* compiled, the runtime had built a `TextAccessor`, and the read silently
|
|
719
|
+
* answered `undefined`;
|
|
720
|
+
* - never a string — a `JsonAccessor<Returned>`.
|
|
721
|
+
*
|
|
722
|
+
* `Returned` there is what the value CLAIMS, not what survived the crossing:
|
|
723
|
+
* the accessor is built from `JSON.stringify`, so a `Map`, a `Date` or a class
|
|
724
|
+
* instance arrives as the plain JSON it serialises to (`{}`, an ISO string, an
|
|
725
|
+
* object of its own fields). A subject whose answer is one of those is
|
|
726
|
+
* projected to plain data inside the call, where the test can see it happen.
|
|
623
727
|
*/
|
|
624
|
-
type
|
|
728
|
+
type CallValue<Returned> = [Returned] extends [string] ? TextAccessor : [Extract<Returned, string>] extends [never] ? JsonAccessor<Returned> : JsonAccessor<Exclude<Returned, string>> | TextAccessor;
|
|
625
729
|
/**
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
730
|
+
* The result of an in-process call — the integration facet's whole surface.
|
|
731
|
+
*
|
|
732
|
+
* Two readings, never both: `value` is what the call RETURNED, `error` is
|
|
733
|
+
* what it THREW. A module that refuses is specified the same way a module
|
|
734
|
+
* that answers is, and neither needs a `try`/`catch` in the test — a shape
|
|
735
|
+
* that costs a refusal spec three times the size of the happy one, and that
|
|
736
|
+
* quietly passes when nothing throws at all.
|
|
737
|
+
*
|
|
738
|
+
* Both are the package's ordinary accessors, so the golden mechanism reaches
|
|
739
|
+
* them, and `Returned` is what `.call<T>()` returned — `result.value.value` is
|
|
740
|
+
* that type, not `unknown`, so a one-field reading needs no golden file: `toMatch('<name>.json'|'<name>.txt')`, the `{{token}}` grammar,
|
|
741
|
+
* `{ frozen }` and `TEST_UPDATE=1` all work here exactly as on an HTTP
|
|
742
|
+
* response or a command's stdout.
|
|
629
743
|
*/
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
/** The accessible label, whitespace-collapsed and truncated. */
|
|
634
|
-
label?: string;
|
|
635
|
-
/** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
|
|
636
|
-
type: string;
|
|
637
|
-
/** The element value (text-field content, adjustable value), when present. */
|
|
638
|
-
value?: string;
|
|
639
|
-
};
|
|
640
|
-
/**
|
|
641
|
-
* The visitor — the interaction vocabulary handed to a mobile scenario.
|
|
642
|
-
* Every verb auto-waits by polling until a visible match exists; acting
|
|
643
|
-
* verbs then enforce exactly-one (W3), while `see()` — the single
|
|
644
|
-
* synchronization primitive — is satisfied by any visible match. There is
|
|
645
|
-
* no sleep and no conditional helper.
|
|
646
|
-
*/
|
|
647
|
-
type MobileVisitor = {
|
|
648
|
-
/** Fill a text field with a value. */
|
|
649
|
-
fill: (element: MobileElementRef, value: string) => Promise<void>;
|
|
650
|
-
/** Wait until the element is visible — the only synchronization primitive. */
|
|
651
|
-
see: (element: MobileElementRef) => Promise<void>;
|
|
652
|
-
/** Tap the element. */
|
|
653
|
-
tap: (element: MobileElementRef) => Promise<void>;
|
|
654
|
-
};
|
|
655
|
-
/** The behavior of an open — the When of the spec; assertions stay in the Then. */
|
|
656
|
-
type MobileScenario = (visitor: MobileVisitor) => Promise<void>;
|
|
657
|
-
/**
|
|
658
|
-
* One node of the projected accessibility tree — the XCUITest page source
|
|
659
|
-
* with its noise collapsed: unlabeled, identifier-less, valueless wrapper
|
|
660
|
-
* nodes are dropped and their children hoisted, so the projection stays
|
|
661
|
-
* stable and golden-friendly. Type names lose the `XCUIElementType` prefix.
|
|
662
|
-
*/
|
|
663
|
-
type ScreenNode = {
|
|
664
|
-
children?: ScreenNode[];
|
|
665
|
-
/** The accessibility identifier, only when it differs from the label. */
|
|
666
|
-
identifier?: string;
|
|
667
|
-
label?: string;
|
|
668
|
-
/** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
|
|
669
|
-
type: string;
|
|
670
|
-
value?: string;
|
|
671
|
-
};
|
|
672
|
-
/**
|
|
673
|
-
* The screen captured by a device open — the FINAL state when a scenario
|
|
674
|
-
* ran. The tree is the projected page source; `texts` are the visible
|
|
675
|
-
* labels/values in document order, consecutive duplicates collapsed.
|
|
676
|
-
*/
|
|
677
|
-
type DeviceScreen = {
|
|
678
|
-
texts: string[];
|
|
679
|
-
tree: ScreenNode;
|
|
680
|
-
};
|
|
681
|
-
/** Per-open options forwarded to the device session. */
|
|
682
|
-
type DeviceOpenOptions = {
|
|
683
|
-
/** The app under test — terminated and relaunched for a fresh, deterministic state. */
|
|
684
|
-
bundleId: string;
|
|
685
|
-
/** Deep link applied after the relaunch (`news://events`); absent opens the app plainly. */
|
|
686
|
-
deepLink?: string | undefined;
|
|
687
|
-
/** The interaction scenario to run after launch; the capture reflects the final state. */
|
|
688
|
-
scenario?: MobileScenario | undefined;
|
|
689
|
-
};
|
|
690
|
-
/**
|
|
691
|
-
* How long a device session waits, in milliseconds. Every field is optional
|
|
692
|
-
* and falls back to the framework's own default, so a runner states only the
|
|
693
|
-
* wait it needs to move — the defaults suit a release build, and a project
|
|
694
|
-
* that drives a DEV build pays for its bundler's cold boot.
|
|
695
|
-
*/
|
|
696
|
-
type DeviceTimeouts = {
|
|
744
|
+
declare class CallResult<Returned = unknown> extends BaseResult {
|
|
745
|
+
private readonly outcome;
|
|
746
|
+
constructor(options: CallResultOptions);
|
|
697
747
|
/**
|
|
698
|
-
*
|
|
699
|
-
*
|
|
748
|
+
* What the call THREW, as text — the message of an `Error`, the value
|
|
749
|
+
* itself when something else was thrown, and empty when it returned.
|
|
750
|
+
*
|
|
751
|
+
* Empty rather than absent on purpose: `await expect(result.error).toBeEmpty()`
|
|
752
|
+
* is how a spec says "and it did not refuse" — awaited, because the
|
|
753
|
+
* matcher answers a promise on every subject it takes, and a dropped one
|
|
754
|
+
* passes the test while the failure surfaces as an unhandled rejection
|
|
755
|
+
* (D2 refuses the bare form).
|
|
700
756
|
*/
|
|
701
|
-
|
|
757
|
+
get error(): TextAccessor;
|
|
702
758
|
/**
|
|
703
|
-
*
|
|
704
|
-
*
|
|
759
|
+
* What the call RETURNED. A string reads as a stream (`<name>.txt`),
|
|
760
|
+
* anything else as JSON (`<name>.json`) — the same two subjects every
|
|
761
|
+
* other facet's results are built from, chosen on the value in hand and
|
|
762
|
+
* typed by {@link CallValue}, which answers for the three ways a type can
|
|
763
|
+
* relate to `string`.
|
|
764
|
+
*
|
|
765
|
+
* A call that threw has no value to choose an accessor on, so `value` is
|
|
766
|
+
* the JSON reading of `null` whatever the declared type promises: what a
|
|
767
|
+
* refusal produced is {@link CallResult.error}, and that is where a spec
|
|
768
|
+
* reads it.
|
|
705
769
|
*/
|
|
706
|
-
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* Abstract device interface for the mobile specification runner.
|
|
710
|
-
* One implementation lives in `integrations/appium/` — a single driver
|
|
711
|
-
* session per runner, created on the first `open()` and reused; each open
|
|
712
|
-
* terminates and relaunches the app for a deterministic fresh state.
|
|
713
|
-
*/
|
|
714
|
-
type DevicePort = {
|
|
715
|
-
/** End the driver session (idempotent). */
|
|
716
|
-
close: () => Promise<void>;
|
|
717
|
-
/** Relaunch the app, apply the deep link, run the scenario, capture the final screen. */
|
|
718
|
-
open: (options: DeviceOpenOptions) => Promise<DeviceScreen>;
|
|
719
|
-
};
|
|
770
|
+
get value(): CallValue<Returned>;
|
|
771
|
+
}
|
|
720
772
|
//#endregion
|
|
721
|
-
//#region src/
|
|
773
|
+
//#region src/model/ports/browser.port.d.ts
|
|
722
774
|
/**
|
|
723
|
-
*
|
|
775
|
+
* The landmark roles — the standard set of page regions, and the only
|
|
776
|
+
* containers a scope can name. Closed on purpose: ARIA defines exactly these,
|
|
777
|
+
* so `within()` never becomes a second selector language.
|
|
724
778
|
*/
|
|
725
|
-
type
|
|
726
|
-
/** HTTP status code. */
|
|
727
|
-
status: number;
|
|
728
|
-
/** Parsed JSON response body, or null if parsing failed. */
|
|
729
|
-
body: unknown;
|
|
730
|
-
/** Response headers as a flat key-value map. */
|
|
731
|
-
headers: Record<string, string>;
|
|
732
|
-
};
|
|
779
|
+
type LandmarkKind = 'banner' | 'complementary' | 'contentinfo' | 'form' | 'main' | 'navigation' | 'region' | 'search';
|
|
733
780
|
/**
|
|
734
|
-
*
|
|
735
|
-
*
|
|
781
|
+
* The interactive/textual element kinds — what a visitor actually acts on.
|
|
782
|
+
*
|
|
783
|
+
* `dialog`, `status`, `table`, `row`, `listitem` and `option` are ARIA roles
|
|
784
|
+
* like the landmarks, but they are not CONTAINERS of a page: they are things a
|
|
785
|
+
* visitor reads and acts on, and they are the roles the estate's UI specs were
|
|
786
|
+
* already reaching for through `getByRole` or, worse, through `.first()`.
|
|
736
787
|
*/
|
|
737
|
-
type
|
|
738
|
-
/** Send an HTTP request and return the parsed response. */
|
|
739
|
-
request: (method: string, path: string, body?: unknown, headers?: Record<string, string>) => Promise<ServerResponse>;
|
|
740
|
-
};
|
|
741
|
-
//#endregion
|
|
742
|
-
//#region src/specification/ports/isolation.port.d.ts
|
|
788
|
+
type ElementKind = 'button' | 'dialog' | 'field' | 'heading' | 'link' | 'listitem' | 'option' | 'row' | 'status' | 'table' | 'testId' | 'text';
|
|
743
789
|
/**
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
* `spec.run()`, and `release()` when the worker shuts down.
|
|
790
|
+
* A user-facing element descriptor — pure data, built by the element
|
|
791
|
+
* vocabulary (`button()`, `link()`, `field()`, …) and translated into
|
|
792
|
+
* concrete locators by the browser integration. CSS/XPath selectors are
|
|
793
|
+
* deliberately not expressible: user-facing elements are the only surface.
|
|
749
794
|
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
795
|
+
* A descriptor must designate exactly ONE element at action time; see
|
|
796
|
+
* {@link ElementMatch} and CONVENTIONS W3.
|
|
752
797
|
*/
|
|
753
|
-
type
|
|
798
|
+
type ElementRef = {
|
|
754
799
|
/**
|
|
755
|
-
*
|
|
756
|
-
* Called once when the worker starts — e.g. clone a template database,
|
|
757
|
-
* set a Redis key prefix.
|
|
800
|
+
* Match the accessible name as a whole string rather than a substring.
|
|
758
801
|
*
|
|
759
|
-
*
|
|
802
|
+
* ABSENT means "the author said nothing", which the adapters read as the
|
|
803
|
+
* default — exact — while keeping the information that no choice was made,
|
|
804
|
+
* so a name that matches only as a substring can be reported once with the
|
|
805
|
+
* transitional warning.
|
|
760
806
|
*/
|
|
761
|
-
|
|
807
|
+
exact?: boolean;
|
|
762
808
|
/**
|
|
763
|
-
*
|
|
764
|
-
*
|
|
809
|
+
* Assert about the element's FOCUS rather than its presence — built by
|
|
810
|
+
* `focused(element)`. The accessibility tree carries no focus state, so
|
|
811
|
+
* where the keyboard is cannot be goldened; it is asserted by a verb.
|
|
765
812
|
*/
|
|
766
|
-
|
|
813
|
+
focused?: boolean;
|
|
767
814
|
/**
|
|
768
|
-
*
|
|
769
|
-
*
|
|
815
|
+
* Narrows to "and it is (not) accepting input" — built by `disabled(x)` /
|
|
816
|
+
* `enabled(x)`. Like `focused`, a STATE the vocabulary can already name the
|
|
817
|
+
* element of; unlike it, one the ARIA tree carries too (`[disabled]`), so
|
|
818
|
+
* this modifier is what names the ONE control a test is about, and it
|
|
819
|
+
* reaches the direction a behavioural substitute cannot — clicking a
|
|
820
|
+
* disabled control is a timeout, never an answer.
|
|
770
821
|
*/
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* A service handle — returned by factory functions like postgres(), redis().
|
|
777
|
-
* Mutable: connectionString is populated after the orchestrator starts containers.
|
|
778
|
-
*/
|
|
779
|
-
type ServiceHandle = {
|
|
780
|
-
/** Service type identifier. */
|
|
781
|
-
readonly type: string;
|
|
822
|
+
disabled?: boolean;
|
|
823
|
+
kind: ElementKind | LandmarkKind;
|
|
824
|
+
/** Landmarks may be anonymous (`main()`, `banner()`); everything else is named. */
|
|
825
|
+
name?: string;
|
|
782
826
|
/**
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
* to the compose service `analytics-db` without any option. Set
|
|
788
|
-
* `composeService` explicitly only for names the key cannot derive.
|
|
827
|
+
* Narrows to "and it is (not) the chosen one" — built by `selected(x)`.
|
|
828
|
+
* An option of a COLLAPSED select is in the tree and in the document, and
|
|
829
|
+
* it has no box on the screen, so this facet of it is read from the node
|
|
830
|
+
* (`:checked`) rather than from what a visitor could point at.
|
|
789
831
|
*/
|
|
790
|
-
|
|
791
|
-
/** Default container port for this service type. */
|
|
792
|
-
readonly defaultPort: number;
|
|
793
|
-
/** Default Docker image for this service type. */
|
|
794
|
-
readonly defaultImage: string;
|
|
795
|
-
/** Environment variables to pass to the container. */
|
|
796
|
-
readonly environment: Record<string, string>;
|
|
797
|
-
/** Connection string — populated after start. */
|
|
798
|
-
connectionString: string;
|
|
799
|
-
/** Whether this service has been started. */
|
|
800
|
-
started: boolean;
|
|
801
|
-
/** Build the connection string from host and port. */
|
|
802
|
-
buildConnectionString: (host: string, port: number) => string;
|
|
803
|
-
/** Create a DatabasePort adapter (if this is a database). Returns null otherwise. */
|
|
804
|
-
createDatabaseAdapter: () => DatabasePort | null;
|
|
805
|
-
/** Verify the service is ready and accepting connections. Throws with context if not. */
|
|
806
|
-
healthcheck: () => Promise<void>;
|
|
832
|
+
selected?: boolean;
|
|
807
833
|
/**
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
* `composeDir` is where the compose file lives — where a service reads its
|
|
812
|
-
* `init.sql` from. `root` is the project root the specification resolved
|
|
813
|
-
* (A9), handed down so a service that CACHES something writes it under the
|
|
814
|
-
* project's own `.artifacts/`, never in a machine-global directory two
|
|
815
|
-
* checkouts would share.
|
|
834
|
+
* The value the field must hold — built by `valued(field('Title'), '…')`.
|
|
835
|
+
* A field's value is a PROPERTY of the node: a controlled input's `value`
|
|
836
|
+
* attribute never moves, so nothing but the live value answers for it.
|
|
816
837
|
*/
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
838
|
+
value?: string;
|
|
839
|
+
/**
|
|
840
|
+
* Restrict the search to the elements of another descriptor — built by
|
|
841
|
+
* `within(scope, target)`. Chains: a scope may itself carry a scope.
|
|
842
|
+
*/
|
|
843
|
+
scope?: ElementRef;
|
|
822
844
|
};
|
|
823
|
-
//#endregion
|
|
824
|
-
//#region src/specification/facets/_common/result/response.d.ts
|
|
825
|
-
/**
|
|
826
|
-
* Read-only accessor for an HTTP response.
|
|
827
|
-
*
|
|
828
|
-
* Assertions go through `expect()`:
|
|
829
|
-
* `expect(result.response).toMatch('created.http')` compares status,
|
|
830
|
-
* a subset of headers, and the JSON body against `_expected/<name>` —
|
|
831
|
-
* with `{{placeholder}}` support in all three.
|
|
832
|
-
*/
|
|
833
|
-
declare class ResponseAccessor {
|
|
834
|
-
/** The parsed JSON response body (null when parsing failed). */
|
|
835
|
-
readonly body: unknown;
|
|
836
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
837
|
-
readonly captures: CaptureScope;
|
|
838
|
-
/** Response headers as a flat, lower-cased key-value map. */
|
|
839
|
-
readonly headers: Record<string, string>;
|
|
840
|
-
/** The HTTP response status code. */
|
|
841
|
-
readonly status: number;
|
|
842
|
-
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
843
|
-
readonly testDir: string;
|
|
844
|
-
constructor(response: ServerResponse, testDir: string, captures?: CaptureScope);
|
|
845
|
-
}
|
|
846
|
-
//#endregion
|
|
847
|
-
//#region src/specification/facets/api/result.d.ts
|
|
848
|
-
/** Result from an HTTP action (.request(), .get(), .post(), .put(), .delete()). */
|
|
849
|
-
declare class HttpResult extends BaseResult {
|
|
850
|
-
private readonly responseData;
|
|
851
|
-
constructor(options: {
|
|
852
|
-
config: SpecificationConfig;
|
|
853
|
-
response: ServerResponse;
|
|
854
|
-
testDir: string;
|
|
855
|
-
});
|
|
856
|
-
/** Access the HTTP response (status, headers, body) for assertions. */
|
|
857
|
-
get response(): ResponseAccessor;
|
|
858
|
-
/** The HTTP response status code. */
|
|
859
|
-
get status(): number;
|
|
860
|
-
}
|
|
861
|
-
//#endregion
|
|
862
|
-
//#region src/specification/facets/_common/result/filesystem.d.ts
|
|
863
845
|
/**
|
|
864
|
-
*
|
|
865
|
-
*
|
|
866
|
-
*
|
|
867
|
-
* Assertions go through `expect()` (async — they walk the disk):
|
|
868
|
-
* `await expect(result.filesystem).toMatch('scaffolded')` compares the
|
|
869
|
-
* tree against the fixture directory `_expected/<name>/`.
|
|
870
|
-
*/
|
|
871
|
-
declare class FilesystemAccessor {
|
|
872
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
873
|
-
readonly captures: CaptureScope;
|
|
874
|
-
/** The absolute path of the temporary working directory. */
|
|
875
|
-
readonly cwd: string;
|
|
876
|
-
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
877
|
-
readonly testDir: string;
|
|
878
|
-
constructor(cwd: string, testDir: string, captures?: CaptureScope);
|
|
879
|
-
/** List all files (recursively) under the working directory, sorted. */
|
|
880
|
-
files(options?: {
|
|
881
|
-
ignore?: string[];
|
|
882
|
-
}): Promise<string[]>;
|
|
883
|
-
}
|
|
884
|
-
//#endregion
|
|
885
|
-
//#region src/specification/facets/_common/result/text.d.ts
|
|
886
|
-
/**
|
|
887
|
-
* Read-only accessor for a captured text handle — THE universal one: stdout,
|
|
888
|
-
* stderr, container logs, and file text all surface as a `TextAccessor`.
|
|
889
|
-
*
|
|
890
|
-
* Backed by a primitive string, exposed via {@link text}, but also provides
|
|
891
|
-
* `toString()` / `valueOf()` for string coercion, so common patterns like
|
|
892
|
-
* `String(result.stdout)` and template-literal interpolation still work.
|
|
893
|
-
*
|
|
894
|
-
* ANSI escape sequences are stripped by default before every comparison
|
|
895
|
-
* (CONVENTIONS D6) — `.text` stays raw. The runner `transform` option remains
|
|
896
|
-
* an escape hatch for application noise not covered by the `{{token}}`
|
|
897
|
-
* grammar.
|
|
898
|
-
*
|
|
899
|
-
* Text operations are **closed** over the type: `.grep(pattern)` returns a
|
|
900
|
-
* `TextAccessor` (not a bare string), preserving the `_expected/`-resolution
|
|
901
|
-
* context and the `transform`, so results are chainable
|
|
902
|
-
* (`result.stdout.grep(a).grep(b)`) and snapshot-able
|
|
903
|
-
* (`expect(result.stdout.grep('users.ts')).toMatch('block.txt')`).
|
|
904
|
-
*
|
|
905
|
-
* Assertions go through `expect()` matchers: `expect(result.stdout).toContain(...)`
|
|
906
|
-
* and `expect(result.stdout).toMatch('name.txt')` (resolved against
|
|
907
|
-
* `_expected/<name>`, flat — with `{{token}}` support, CONVENTIONS D4).
|
|
846
|
+
* One candidate captured when a descriptor matched more than one element —
|
|
847
|
+
* the evidence the ambiguity error enumerates so the author can disambiguate
|
|
848
|
+
* without opening a browser.
|
|
908
849
|
*/
|
|
909
|
-
|
|
910
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
911
|
-
readonly captures: CaptureScope;
|
|
912
|
-
/** @internal Stream label used in failure messages ("stdout" / "stderr"). */
|
|
913
|
-
readonly streamName: string;
|
|
914
|
-
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
915
|
-
readonly testDir: string;
|
|
916
|
-
/** The raw captured text (never transformed, ANSI preserved). */
|
|
917
|
-
readonly text: string;
|
|
918
|
-
/** @internal Normaliser applied before comparisons, never to fixtures. */
|
|
919
|
-
readonly transform?: ((text: string) => string) | undefined;
|
|
920
|
-
constructor(value: string, streamName: string, testDir: string, options?: {
|
|
921
|
-
captures?: CaptureScope | undefined;
|
|
922
|
-
transform?: ((text: string) => string) | undefined;
|
|
923
|
-
});
|
|
924
|
-
/** @internal The text as compared by matchers — ANSI stripped, transform applied. */
|
|
925
|
-
get comparableText(): string;
|
|
850
|
+
type ElementMatch = {
|
|
926
851
|
/**
|
|
927
|
-
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
* snapshot-able. The scalpel for probing large tool outputs; snapshot the
|
|
931
|
-
* whole surface by default, reach for `.grep()` for targeted checks.
|
|
852
|
+
* The accessible name, when it differs from the text — an `aria-label` is
|
|
853
|
+
* what a role descriptor MATCHED on, and printing the text alone sends the
|
|
854
|
+
* author to disambiguate a name they cannot see.
|
|
932
855
|
*/
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
856
|
+
accessibleName?: string | undefined;
|
|
857
|
+
/** Nearest landmark ancestor (`nav`, `footer`, `main`…), when there is one. */
|
|
858
|
+
context?: string | undefined;
|
|
859
|
+
/** The attribute that disambiguates most — `href` for links, `name` for fields. */
|
|
860
|
+
detail?: string | undefined;
|
|
861
|
+
/** Tag name, lower-cased. */
|
|
862
|
+
tag: string;
|
|
863
|
+
/** Text content, whitespace-collapsed and truncated. */
|
|
864
|
+
text: string;
|
|
865
|
+
};
|
|
866
|
+
/** A `<link>` element captured from the rendered document's head. */
|
|
867
|
+
type BrowserLinkElement = {
|
|
868
|
+
href: string;
|
|
869
|
+
hreflang?: string | undefined;
|
|
870
|
+
rel: string;
|
|
871
|
+
type?: string | undefined;
|
|
872
|
+
};
|
|
873
|
+
/** A `<meta>` element captured from the rendered document's head. */
|
|
874
|
+
type BrowserMetaElement = {
|
|
875
|
+
content: string;
|
|
876
|
+
name?: string | undefined;
|
|
877
|
+
property?: string | undefined;
|
|
878
|
+
};
|
|
879
|
+
/** A console message emitted while the page loaded or the scenario ran. */
|
|
880
|
+
type BrowserConsoleMessage = {
|
|
881
|
+
text: string;
|
|
882
|
+
type: string;
|
|
883
|
+
};
|
|
939
884
|
/**
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
*
|
|
944
|
-
* cleanup. The Docker shell-outs are **lazy**: a test that never calls
|
|
945
|
-
* `.container(...)` never queries the Docker daemon, so command-only tests
|
|
946
|
-
* pay zero Docker cost even when the runner is Docker-aware.
|
|
947
|
-
*
|
|
948
|
-
* Dispose always runs one final label-filtered `docker ps` to catch
|
|
949
|
-
* containers that were spawned during `.exec` but never asserted on —
|
|
950
|
-
* tests still get leak-free cleanup even if they forget to reach into
|
|
951
|
-
* a container.
|
|
885
|
+
* The visitor — the interaction vocabulary handed to a visit scenario.
|
|
886
|
+
* Every action auto-waits (playwright actionability); `see()` is the single
|
|
887
|
+
* synchronization primitive: it retries until the element is visible and
|
|
888
|
+
* fails at the timeout. There is no sleep and no conditional helper.
|
|
952
889
|
*/
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
commandOutput: CliOutput;
|
|
961
|
-
config: SpecificationConfig;
|
|
962
|
-
dockerConfig?: DockerSpecConfig | undefined;
|
|
963
|
-
testDir: string;
|
|
964
|
-
testRunId?: string | undefined;
|
|
965
|
-
transform?: ((text: string) => string) | undefined;
|
|
966
|
-
workDir: string;
|
|
967
|
-
});
|
|
968
|
-
/** The process exit code. */
|
|
969
|
-
get exitCode(): number;
|
|
970
|
-
/** Accessor for the captured standard output with file-based assertions. */
|
|
971
|
-
get stdout(): TextAccessor;
|
|
972
|
-
/** Accessor for the captured standard error with file-based assertions. */
|
|
973
|
-
get stderr(): TextAccessor;
|
|
974
|
-
/** Accessor for parsing stdout as JSON and asserting against JSON fixtures. */
|
|
975
|
-
get json(): JsonAccessor;
|
|
976
|
-
/** Accessor for the temporary working directory the command ran in. */
|
|
977
|
-
get filesystem(): FilesystemAccessor;
|
|
890
|
+
type Visitor = {
|
|
891
|
+
/** Check a checkbox or radio. */
|
|
892
|
+
check: (element: ElementRef) => Promise<void>;
|
|
893
|
+
/** Click the element. */
|
|
894
|
+
click: (element: ElementRef) => Promise<void>;
|
|
895
|
+
/** Fill a form field with a value. */
|
|
896
|
+
fill: (element: ElementRef, value: string) => Promise<void>;
|
|
978
897
|
/**
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
-
*
|
|
982
|
-
* First access triggers a one-shot `docker ps` + `docker inspect`
|
|
983
|
-
* query and caches the result for the rest of the result's lifetime.
|
|
984
|
-
* Tests that don't call this never touch Docker.
|
|
985
|
-
*
|
|
986
|
-
* Returns an accessor with `.exists === false` when no container
|
|
987
|
-
* carries that name — callers can still assert absence without a try.
|
|
898
|
+
* Wait until the element is GONE — hidden, or never in the document. The
|
|
899
|
+
* absence primitive: `see()` cannot answer "the dialog closed", because an
|
|
900
|
+
* element that never appears and one that disappeared read the same to it.
|
|
988
901
|
*/
|
|
989
|
-
|
|
990
|
-
/**
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
902
|
+
gone: (element: ElementRef) => Promise<void>;
|
|
903
|
+
/** Navigate to a path of the site under test. */
|
|
904
|
+
goto: (path: string) => Promise<void>;
|
|
905
|
+
/** Hover the element. */
|
|
906
|
+
hover: (element: ElementRef) => Promise<void>;
|
|
907
|
+
/** Press a key (e.g. `Enter`). */
|
|
908
|
+
press: (key: string) => Promise<void>;
|
|
909
|
+
/** Wait until the element is visible — the only synchronization primitive. */
|
|
910
|
+
see: (element: ElementRef) => Promise<void>;
|
|
911
|
+
/** Select an option in a select field. */
|
|
912
|
+
select: (element: ElementRef, option: string) => Promise<void>;
|
|
913
|
+
};
|
|
914
|
+
/** The behavior of a visit — the When of the spec; assertions stay in the Then. */
|
|
915
|
+
type VisitScenario = (visitor: Visitor) => Promise<void>;
|
|
916
|
+
/** Per-visit options forwarded to the browser context. */
|
|
917
|
+
type BrowserOpenOptions = {
|
|
918
|
+
/**
|
|
919
|
+
* Extra origins the `external: 'block'` policy lets through — the
|
|
920
|
+
* declared stub backend the page legitimately fetches from.
|
|
921
|
+
*/
|
|
922
|
+
allowedOrigins?: string[] | undefined;
|
|
923
|
+
/**
|
|
924
|
+
* Base URL of the site under test — the origin `goto()` resolves against
|
|
925
|
+
* and the boundary of the `external` policy.
|
|
926
|
+
*/
|
|
927
|
+
baseUrl: string;
|
|
928
|
+
/**
|
|
929
|
+
* Pin the page's own `Date` at this instant before the first byte is
|
|
930
|
+
* parsed — the calendar the site's scripts read. `.clock()` on the chain.
|
|
931
|
+
*/
|
|
932
|
+
clock?: string | undefined;
|
|
933
|
+
/**
|
|
934
|
+
* Cross-origin request policy. `'block'` aborts every request leaving
|
|
935
|
+
* the site under test (analytics, CDNs) — the browser-side analog of
|
|
936
|
+
* strict intercepts. `'allow'` lets them through (deployed-site mode).
|
|
937
|
+
*/
|
|
938
|
+
external: 'allow' | 'block';
|
|
939
|
+
/** Extra HTTP headers sent with every request of the visit (incl. User-Agent overrides). */
|
|
940
|
+
headers?: Record<string, string> | undefined;
|
|
941
|
+
/** The interaction scenario to run after load; the capture reflects the final state. */
|
|
942
|
+
scenario?: undefined | VisitScenario;
|
|
943
|
+
};
|
|
944
|
+
/**
|
|
945
|
+
* The rendered page captured by a browser visit — the FINAL state when a
|
|
946
|
+
* scenario ran. Extraction happens in-page (the browser IS the HTML
|
|
947
|
+
* parser); interpretation of the raw elements belongs to the model.
|
|
948
|
+
*/
|
|
949
|
+
type BrowserPage = {
|
|
950
|
+
/** Console messages emitted while loading and interacting, in order. */
|
|
951
|
+
consoleMessages: BrowserConsoleMessage[];
|
|
952
|
+
/** Serialized DOM after rendering (`document.documentElement.outerHTML`). */
|
|
953
|
+
html: string;
|
|
954
|
+
/** Raw text content of every `application/ld+json` script, in DOM order. */
|
|
955
|
+
jsonLdBlocks: string[];
|
|
956
|
+
/** `<link>` elements of the head, in DOM order. */
|
|
957
|
+
links: BrowserLinkElement[];
|
|
958
|
+
/** `<meta>` elements of the head, in DOM order. */
|
|
959
|
+
metas: BrowserMetaElement[];
|
|
960
|
+
/** HTTP status of the main document response (0 when unavailable). */
|
|
961
|
+
status: number;
|
|
962
|
+
/** Rendered `document.body.innerText`. */
|
|
963
|
+
text: string;
|
|
964
|
+
/** The ARIA snapshot of the rendered `<body>` — the outline a golden pins. */
|
|
965
|
+
tree: string;
|
|
966
|
+
/** `document.title` after rendering. */
|
|
967
|
+
title: string;
|
|
968
|
+
/** Final URL after redirects and scenario navigation. */
|
|
969
|
+
url: string;
|
|
970
|
+
};
|
|
971
|
+
/**
|
|
972
|
+
* Abstract browser interface for the website specification runner.
|
|
973
|
+
* One implementation lives in `integrations/playwright/` — a single shared
|
|
974
|
+
* browser instance per runner; each `open()` gets a fresh, isolated context.
|
|
975
|
+
*/
|
|
976
|
+
type BrowserPort = {
|
|
977
|
+
/** Close the shared browser instance (idempotent). */
|
|
978
|
+
close: () => Promise<void>;
|
|
979
|
+
/** Load `url` in a fresh context, run the scenario, capture the final page. */
|
|
980
|
+
open: (url: string, options: BrowserOpenOptions) => Promise<BrowserPage>;
|
|
981
|
+
};
|
|
996
982
|
//#endregion
|
|
997
|
-
//#region src/
|
|
983
|
+
//#region src/model/ports/device.port.d.ts
|
|
998
984
|
/**
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
985
|
+
* The element kinds a mobile screen can designate — the structural subset of
|
|
986
|
+
* {@link ElementRef} kinds that map onto the XCUITest accessibility tree.
|
|
987
|
+
* There is ONE element vocabulary across facets: `button('Bookmark')` works
|
|
988
|
+
* in a visit scenario and a mobile scenario alike. Landmarks have no iOS
|
|
989
|
+
* analog — passing one to a mobile verb refuses at runtime.
|
|
990
|
+
*/
|
|
991
|
+
type MobileElementKind = 'button' | 'field' | 'testId' | 'text';
|
|
992
|
+
/**
|
|
993
|
+
* A user-facing element descriptor for mobile scenarios — pure data, built by
|
|
994
|
+
* the shared element vocabulary (`button()`, `field()`, `content()`,
|
|
995
|
+
* `testId()`, `within()`) and translated into iOS predicate strings by the
|
|
996
|
+
* device integration. Structurally the same ref type as the browser's, so the
|
|
997
|
+
* vocabulary stays single; kinds outside {@link MobileElementKind} (the ARIA
|
|
998
|
+
* landmarks) are refused at runtime with a message naming the boundary.
|
|
1001
999
|
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
* rewrite. What is new here is only the ORCHESTRATION the chain cannot express:
|
|
1007
|
-
* several commands sharing one working directory, each judged the moment it
|
|
1008
|
-
* ends — so what a run states about that directory is what IT left there, and
|
|
1009
|
-
* the first run to disagree is the one the failure names.
|
|
1000
|
+
* A descriptor must designate exactly ONE element when a verb ACTS on it
|
|
1001
|
+
* (`tap`, `fill`) — see {@link MobileElementMatch} and CONVENTIONS W3.
|
|
1002
|
+
* `see()` acts on nothing, so any visible match satisfies it: XCUITest trees
|
|
1003
|
+
* legitimately duplicate a label across container and child.
|
|
1010
1004
|
*/
|
|
1005
|
+
type MobileElementRef = ElementRef;
|
|
1011
1006
|
/**
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1007
|
+
* One candidate captured when a descriptor matched more than one element —
|
|
1008
|
+
* the evidence the ambiguity error enumerates so the author can disambiguate
|
|
1009
|
+
* without opening the simulator.
|
|
1014
1010
|
*/
|
|
1015
|
-
type
|
|
1016
|
-
/**
|
|
1017
|
-
|
|
1018
|
-
/** The
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
ready: RegExp;
|
|
1025
|
-
/** Builds the URL bound to {@link env} from the announced port. */
|
|
1026
|
-
url: (port: number) => string;
|
|
1027
|
-
};
|
|
1028
|
-
/** Per-call options for {@link runSpecDocument} / `cli.run()`. */
|
|
1029
|
-
type LiterateRunFlags = {
|
|
1030
|
-
/**
|
|
1031
|
-
* Opt this document OUT of the update-mode rewrite. A frozen document is
|
|
1032
|
-
* NEVER written under `TEST_UPDATE=1`: its mismatch still throws its diff.
|
|
1033
|
-
* That is what makes a DELIBERATELY-WRONG `.spec.yaml` — one whose failure
|
|
1034
|
-
* rendering is the subject of a negative test — survive an update run
|
|
1035
|
-
* instead of being silently corrected into a passing file. The document's
|
|
1036
|
-
* mirror of `toMatch(name, { frozen: true })`.
|
|
1037
|
-
*/
|
|
1038
|
-
frozen?: boolean | undefined;
|
|
1011
|
+
type MobileElementMatch = {
|
|
1012
|
+
/** The accessibility identifier (`testId()` target), when one is set. */
|
|
1013
|
+
identifier?: string;
|
|
1014
|
+
/** The accessible label, whitespace-collapsed and truncated. */
|
|
1015
|
+
label?: string;
|
|
1016
|
+
/** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
|
|
1017
|
+
type: string;
|
|
1018
|
+
/** The element value (text-field content, adjustable value), when present. */
|
|
1019
|
+
value?: string;
|
|
1039
1020
|
};
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1021
|
+
/**
|
|
1022
|
+
* The visitor — the interaction vocabulary handed to a mobile scenario.
|
|
1023
|
+
* Every verb auto-waits by polling until a visible match exists; acting
|
|
1024
|
+
* verbs then enforce exactly-one (W3), while `see()` — the single
|
|
1025
|
+
* synchronization primitive — is satisfied by any visible match. There is
|
|
1026
|
+
* no sleep and no conditional helper.
|
|
1027
|
+
*/
|
|
1028
|
+
type MobileVisitor = {
|
|
1029
|
+
/** Fill a text field with a value. */
|
|
1030
|
+
fill: (element: MobileElementRef, value: string) => Promise<void>;
|
|
1031
|
+
/** Wait until the element is visible — the only synchronization primitive. */
|
|
1032
|
+
see: (element: MobileElementRef) => Promise<void>;
|
|
1033
|
+
/** Tap the element. */
|
|
1034
|
+
tap: (element: MobileElementRef) => Promise<void>;
|
|
1047
1035
|
};
|
|
1048
|
-
|
|
1049
|
-
|
|
1036
|
+
/** The behavior of an open — the When of the spec; assertions stay in the Then. */
|
|
1037
|
+
type MobileScenario = (visitor: MobileVisitor) => Promise<void>;
|
|
1038
|
+
/**
|
|
1039
|
+
* One node of the projected accessibility tree — the XCUITest page source
|
|
1040
|
+
* with its noise collapsed: unlabeled, identifier-less, valueless wrapper
|
|
1041
|
+
* nodes are dropped and their children hoisted, so the projection stays
|
|
1042
|
+
* stable and golden-friendly. Type names lose the `XCUIElementType` prefix.
|
|
1043
|
+
*/
|
|
1044
|
+
type ScreenNode = {
|
|
1045
|
+
children?: ScreenNode[];
|
|
1046
|
+
/** The accessibility identifier, only when it differs from the label. */
|
|
1047
|
+
identifier?: string;
|
|
1048
|
+
label?: string;
|
|
1049
|
+
/** Element type without the `XCUIElementType` prefix — `Button`, `StaticText`. */
|
|
1050
|
+
type: string;
|
|
1051
|
+
value?: string;
|
|
1050
1052
|
};
|
|
1051
1053
|
/**
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1054
|
+
* The screen captured by a device open — the FINAL state when a scenario
|
|
1055
|
+
* ran. The tree is the projected page source; `texts` are the visible
|
|
1056
|
+
* labels/values in document order, consecutive duplicates collapsed.
|
|
1055
1057
|
*/
|
|
1056
|
-
type
|
|
1058
|
+
type DeviceScreen = {
|
|
1059
|
+
texts: string[];
|
|
1060
|
+
tree: ScreenNode;
|
|
1061
|
+
};
|
|
1062
|
+
/** Per-open options forwarded to the device session. */
|
|
1063
|
+
type DeviceOpenOptions = {
|
|
1064
|
+
/** The app under test — terminated and relaunched for a fresh, deterministic state. */
|
|
1065
|
+
bundleId: string;
|
|
1066
|
+
/** Deep link applied after the relaunch (`news://events`); absent opens the app plainly. */
|
|
1067
|
+
deepLink?: string | undefined;
|
|
1068
|
+
/** The interaction scenario to run after launch; the capture reflects the final state. */
|
|
1069
|
+
scenario?: MobileScenario | undefined;
|
|
1070
|
+
};
|
|
1057
1071
|
/**
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
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.
|
|
1072
|
+
* How long a device session waits, in milliseconds. Every field is optional
|
|
1073
|
+
* and falls back to the framework's own default, so a runner states only the
|
|
1074
|
+
* wait it needs to move — the defaults suit a release build, and a project
|
|
1075
|
+
* that drives a DEV build pays for its bundler's cold boot.
|
|
1071
1076
|
*/
|
|
1072
|
-
|
|
1073
|
-
private readonly outcome;
|
|
1074
|
-
constructor(options: CallResultOptions);
|
|
1077
|
+
type DeviceTimeouts = {
|
|
1075
1078
|
/**
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
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).
|
|
1079
|
+
* How long every verb polls for a visible match before refusing.
|
|
1080
|
+
* Default: `30_000` — enough for a cold app boot on a release bundle.
|
|
1084
1081
|
*/
|
|
1085
|
-
|
|
1082
|
+
action?: number | undefined;
|
|
1086
1083
|
/**
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
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}.
|
|
1084
|
+
* How long the FIRST session waits for WebDriverAgent to build and
|
|
1085
|
+
* launch on the simulator. Default: `240_000`.
|
|
1093
1086
|
*/
|
|
1094
|
-
|
|
1095
|
-
}
|
|
1087
|
+
launch?: number | undefined;
|
|
1088
|
+
};
|
|
1089
|
+
/**
|
|
1090
|
+
* Abstract device interface for the mobile specification runner.
|
|
1091
|
+
* One implementation lives in `integrations/appium/` — a single driver
|
|
1092
|
+
* session per runner, created on the first `open()` and reused; each open
|
|
1093
|
+
* terminates and relaunches the app for a deterministic fresh state.
|
|
1094
|
+
*/
|
|
1095
|
+
type DevicePort = {
|
|
1096
|
+
/** End the driver session (idempotent). */
|
|
1097
|
+
close: () => Promise<void>;
|
|
1098
|
+
/** Relaunch the app, apply the deep link, run the scenario, capture the final screen. */
|
|
1099
|
+
open: (options: DeviceOpenOptions) => Promise<DeviceScreen>;
|
|
1100
|
+
};
|
|
1096
1101
|
//#endregion
|
|
1097
|
-
//#region src/
|
|
1102
|
+
//#region src/facets/mobile/mobile.result.d.ts
|
|
1098
1103
|
/** Result from a `.open()` action — the screen as the device saw it, final state. */
|
|
1099
1104
|
declare class ScreenResult extends BaseResult {
|
|
1100
1105
|
private readonly capturedScreen;
|
|
@@ -1115,7 +1120,7 @@ declare class ScreenResult extends BaseResult {
|
|
|
1115
1120
|
get screen(): JsonAccessor;
|
|
1116
1121
|
}
|
|
1117
1122
|
//#endregion
|
|
1118
|
-
//#region src/
|
|
1123
|
+
//#region src/facets/website/website.result.d.ts
|
|
1119
1124
|
/** A raw HTTP exchange captured by `.fetch()` — redirects are NOT followed. */
|
|
1120
1125
|
type FetchExchange = {
|
|
1121
1126
|
body: string;
|
|
@@ -1174,32 +1179,113 @@ declare class PageResult extends BaseResult {
|
|
|
1174
1179
|
/** The rendered DOM serialization as a text accessor. */
|
|
1175
1180
|
get html(): TextAccessor;
|
|
1176
1181
|
/**
|
|
1177
|
-
* Every `application/ld+json` block of the page, parsed, as one JSON
|
|
1178
|
-
* array — `expect(result.jsonLd).toMatch('article.jsonld.json')`.
|
|
1182
|
+
* Every `application/ld+json` block of the page, parsed, as one JSON
|
|
1183
|
+
* array — `expect(result.jsonLd).toMatch('article.jsonld.json')`.
|
|
1184
|
+
*/
|
|
1185
|
+
get jsonLd(): JsonAccessor;
|
|
1186
|
+
/** `<link>` elements of the head, in DOM order. */
|
|
1187
|
+
get links(): BrowserLinkElement[];
|
|
1188
|
+
/** The content of a named meta — `meta('description')`, `meta('og:image')`. */
|
|
1189
|
+
meta(name: string): string | undefined;
|
|
1190
|
+
/** HTTP status of the main document response. */
|
|
1191
|
+
get status(): number;
|
|
1192
|
+
/** Rendered body text as a text accessor. */
|
|
1193
|
+
get content(): TextAccessor;
|
|
1194
|
+
/**
|
|
1195
|
+
* The ARIA snapshot of the rendered `<body>` — the page's outline, as the
|
|
1196
|
+
* accessibility tree draws it, and the one golden a rendered surface pins.
|
|
1197
|
+
* Deterministic where a screenshot is not, and the SAME dialect a component
|
|
1198
|
+
* spec asserts, so a page and the components inside it read alike.
|
|
1199
|
+
*/
|
|
1200
|
+
get tree(): TextAccessor;
|
|
1201
|
+
/** The document title as a text accessor. */
|
|
1202
|
+
get title(): TextAccessor;
|
|
1203
|
+
/** Final URL after redirects. */
|
|
1204
|
+
get url(): string;
|
|
1205
|
+
}
|
|
1206
|
+
//#endregion
|
|
1207
|
+
//#region src/model/ports/isolation.port.d.ts
|
|
1208
|
+
/**
|
|
1209
|
+
* Strategy for isolating service state across parallel test workers.
|
|
1210
|
+
*
|
|
1211
|
+
* Each service handle provides an isolation strategy. The framework
|
|
1212
|
+
* calls `acquire()` once per vitest worker, `reset()` before each
|
|
1213
|
+
* `spec.run()`, and `release()` when the worker shuts down.
|
|
1214
|
+
*
|
|
1215
|
+
* Implement this interface to support new service types (e.g. MongoDB,
|
|
1216
|
+
* Elasticsearch, S3).
|
|
1217
|
+
*/
|
|
1218
|
+
type IsolationStrategy = {
|
|
1219
|
+
/**
|
|
1220
|
+
* Create an isolated namespace for this worker.
|
|
1221
|
+
* Called once when the worker starts — e.g. clone a template database,
|
|
1222
|
+
* set a Redis key prefix.
|
|
1223
|
+
*
|
|
1224
|
+
* @param workerId - Unique identifier for this vitest worker.
|
|
1225
|
+
*/
|
|
1226
|
+
acquire: (workerId: string) => Promise<void>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Fast reset within the namespace between `spec.run()` calls.
|
|
1229
|
+
* E.g. truncate tables (without dropping the database).
|
|
1230
|
+
*/
|
|
1231
|
+
reset: () => Promise<void>;
|
|
1232
|
+
/**
|
|
1233
|
+
* Tear down the isolated namespace.
|
|
1234
|
+
* Called once when the worker shuts down — e.g. drop the cloned database.
|
|
1235
|
+
*/
|
|
1236
|
+
release: () => Promise<void>;
|
|
1237
|
+
};
|
|
1238
|
+
//#endregion
|
|
1239
|
+
//#region src/model/ports/service.port.d.ts
|
|
1240
|
+
/**
|
|
1241
|
+
* A service handle — returned by factory functions like postgres(), redis().
|
|
1242
|
+
* Mutable: connectionString is populated after the orchestrator starts containers.
|
|
1243
|
+
*/
|
|
1244
|
+
type ServiceHandle = {
|
|
1245
|
+
/** Service type identifier. */
|
|
1246
|
+
readonly type: string;
|
|
1247
|
+
/**
|
|
1248
|
+
* The name this handle is known by. Left `null` until the orchestrator
|
|
1249
|
+
* assigns it at start time: it is the kebab-case form of the RECORD KEY,
|
|
1250
|
+
* so `{ analyticsDb: postgres() }` reports as `analytics-db` and reads its
|
|
1251
|
+
* init script from `docker/analytics-db/`. The key is the only name a
|
|
1252
|
+
* specification writes; a handle carries no second one of its own.
|
|
1179
1253
|
*/
|
|
1180
|
-
|
|
1181
|
-
/**
|
|
1182
|
-
|
|
1183
|
-
/**
|
|
1184
|
-
|
|
1185
|
-
/**
|
|
1186
|
-
|
|
1187
|
-
/**
|
|
1188
|
-
|
|
1254
|
+
serviceName: null | string;
|
|
1255
|
+
/** Default container port for this service type. */
|
|
1256
|
+
readonly defaultPort: number;
|
|
1257
|
+
/** Default Docker image for this service type. */
|
|
1258
|
+
readonly defaultImage: string;
|
|
1259
|
+
/** Environment variables to pass to the container. */
|
|
1260
|
+
readonly environment: Record<string, string>;
|
|
1261
|
+
/** Connection string — populated after start. */
|
|
1262
|
+
connectionString: string;
|
|
1263
|
+
/** Whether this service has been started. */
|
|
1264
|
+
started: boolean;
|
|
1265
|
+
/** Build the connection string from host and port. */
|
|
1266
|
+
buildConnectionString: (host: string, port: number) => string;
|
|
1267
|
+
/** Create a DatabasePort adapter (if this is a database). Returns null otherwise. */
|
|
1268
|
+
createDatabaseAdapter: () => DatabasePort | null;
|
|
1269
|
+
/** Verify the service is ready and accepting connections. Throws with context if not. */
|
|
1270
|
+
healthcheck: () => Promise<void>;
|
|
1189
1271
|
/**
|
|
1190
|
-
*
|
|
1191
|
-
*
|
|
1192
|
-
*
|
|
1193
|
-
*
|
|
1272
|
+
* Run initialization scripts (e.g., init.sql). Throws with SQL error
|
|
1273
|
+
* context if it fails.
|
|
1274
|
+
*
|
|
1275
|
+
* `dockerDir` is `<root>/docker` — where a service reads its `init.sql`
|
|
1276
|
+
* from, under its own name. `root` is the project root the specification resolved
|
|
1277
|
+
* (A9), handed down so a service that CACHES something writes it under the
|
|
1278
|
+
* project's own `.artifacts/`, never in a machine-global directory two
|
|
1279
|
+
* checkouts would share.
|
|
1194
1280
|
*/
|
|
1195
|
-
|
|
1196
|
-
/**
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
|
|
1200
|
-
}
|
|
1281
|
+
initialize: (dockerDir: string, root: string) => Promise<void>;
|
|
1282
|
+
/** Reset state between tests (truncate tables, flush cache, etc.) */
|
|
1283
|
+
reset: () => Promise<void>;
|
|
1284
|
+
/** Get the isolation strategy for parallel test execution. */
|
|
1285
|
+
isolation: () => IsolationStrategy;
|
|
1286
|
+
};
|
|
1201
1287
|
//#endregion
|
|
1202
|
-
//#region src/
|
|
1288
|
+
//#region src/model/chain/stub-backend.d.ts
|
|
1203
1289
|
/** Options for the stub backend server. */
|
|
1204
1290
|
type StubBackendOptions = {
|
|
1205
1291
|
/**
|
|
@@ -1241,7 +1327,7 @@ declare class StubBackend {
|
|
|
1241
1327
|
private record;
|
|
1242
1328
|
}
|
|
1243
1329
|
//#endregion
|
|
1244
|
-
//#region src/
|
|
1330
|
+
//#region src/model/chain/builder.d.ts
|
|
1245
1331
|
/** A named job that can be triggered via jobs.trigger(). */
|
|
1246
1332
|
type JobHandle = {
|
|
1247
1333
|
name: string;
|
|
@@ -1321,17 +1407,6 @@ type SpecificationConfig = {
|
|
|
1321
1407
|
* (`env: frozen`). Declared once per app in `specification.cli()`.
|
|
1322
1408
|
*/
|
|
1323
1409
|
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;
|
|
1330
|
-
/**
|
|
1331
|
-
* When set, `.intercept()` is unavailable on this runner and throws this
|
|
1332
|
-
* reason immediately (compose mode — MSW is in-process, CONVENTIONS I3).
|
|
1333
|
-
*/
|
|
1334
|
-
interceptDisabledReason?: string | undefined;
|
|
1335
1410
|
jobs?: JobHandle[] | undefined;
|
|
1336
1411
|
/** The project root — the working directory a document's `serve:` command runs from. */
|
|
1337
1412
|
root?: string | undefined;
|
|
@@ -1391,8 +1466,18 @@ type ApiSpecification<DatabaseKey extends string = string> = {
|
|
|
1391
1466
|
};
|
|
1392
1467
|
/**
|
|
1393
1468
|
* The `jobs` facet — job chain entry handed out by `specification.jobs()`.
|
|
1394
|
-
* Jobs run in-process by definition (CONVENTIONS
|
|
1469
|
+
* Jobs run in-process by definition (CONVENTIONS A8).
|
|
1470
|
+
*/
|
|
1471
|
+
/**
|
|
1472
|
+
* What a triggered job HANDS BACK.
|
|
1473
|
+
*
|
|
1474
|
+
* A job writes to a database, a cache, a queue — never to a working directory
|
|
1475
|
+
* the spec then walks, because a job has none: it runs in this process, where
|
|
1476
|
+
* the only filesystem is the repository's. So the two accessors that read one
|
|
1477
|
+
* (`file()`, `directory()`) are not on this result. A pipeline that really
|
|
1478
|
+
* produces files is a binary, and `specification.cli()` is its facet.
|
|
1395
1479
|
*/
|
|
1480
|
+
type JobsResult = Omit<BaseResult, 'directory' | 'file'>;
|
|
1396
1481
|
type JobsSpecification<DatabaseKey extends string = string> = {
|
|
1397
1482
|
/** Pin the job's `Date` at `iso` for this chain — the calendar the job reads. */
|
|
1398
1483
|
clock: (iso: string) => JobsSpecification<DatabaseKey>;
|
|
@@ -1403,7 +1488,7 @@ type JobsSpecification<DatabaseKey extends string = string> = {
|
|
|
1403
1488
|
database?: DatabaseKey;
|
|
1404
1489
|
}) => JobsSpecification<DatabaseKey>;
|
|
1405
1490
|
/** Execute the named job registered via the `jobs` option and resolve with the result. */
|
|
1406
|
-
trigger: (name: string) => Promise<
|
|
1491
|
+
trigger: (name: string) => Promise<JobsResult>;
|
|
1407
1492
|
};
|
|
1408
1493
|
/**
|
|
1409
1494
|
* The `integration` facet — the in-process chain handed out by
|
|
@@ -1515,92 +1600,66 @@ type MobileSpecification = {
|
|
|
1515
1600
|
open: (deepLink?: string, scenario?: MobileScenario) => Promise<ScreenResult>;
|
|
1516
1601
|
};
|
|
1517
1602
|
//#endregion
|
|
1518
|
-
//#region src/
|
|
1519
|
-
/**
|
|
1520
|
-
* Read-only accessor for a directory produced by a spec action.
|
|
1521
|
-
*
|
|
1522
|
-
* Assertions go through `expect()` (async — they walk the disk):
|
|
1523
|
-
* `await expect(result.directory('out')).toMatch('scaffold/out')`
|
|
1524
|
-
* compares the tree against the fixture directory `_expected/<name>/`.
|
|
1525
|
-
*/
|
|
1526
|
-
declare class DirectoryAccessor {
|
|
1527
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
1528
|
-
readonly captures: CaptureScope;
|
|
1529
|
-
/** @internal Absolute path of the directory under assertion. */
|
|
1530
|
-
readonly root: string;
|
|
1531
|
-
/** @internal Test-file directory — fixture resolution root for matchers. */
|
|
1532
|
-
readonly testDir: string;
|
|
1533
|
-
constructor(absPath: string, testDir: string, captures?: CaptureScope);
|
|
1534
|
-
/** List all files (recursively) under the directory, sorted. */
|
|
1535
|
-
files(options?: {
|
|
1536
|
-
ignore?: string[];
|
|
1537
|
-
}): Promise<string[]>;
|
|
1538
|
-
}
|
|
1539
|
-
//#endregion
|
|
1540
|
-
//#region src/specification/facets/_common/result/table.d.ts
|
|
1603
|
+
//#region src/facets/cli/cli.result.d.ts
|
|
1541
1604
|
/**
|
|
1542
|
-
*
|
|
1543
|
-
*
|
|
1544
|
-
* Assertions go through `expect()` (async — they query the database):
|
|
1605
|
+
* Result from a command action (`.exec()`).
|
|
1545
1606
|
*
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
1551
|
-
*/
|
|
1552
|
-
declare class TableAccessor {
|
|
1553
|
-
/** @internal Ref-capture scope shared by the current spec execution. */
|
|
1554
|
-
readonly captures: CaptureScope;
|
|
1555
|
-
/** @internal */
|
|
1556
|
-
private readonly db;
|
|
1557
|
-
/** The table name this accessor reads. */
|
|
1558
|
-
readonly name: string;
|
|
1559
|
-
constructor(name: string, db: DatabasePort, captures?: CaptureScope);
|
|
1560
|
-
/** @internal Query the table — used by the toMatchRows / toBeEmpty matchers. */
|
|
1561
|
-
query(columns: string[]): Promise<unknown[][]>;
|
|
1562
|
-
}
|
|
1563
|
-
//#endregion
|
|
1564
|
-
//#region src/specification/facets/_common/result/result.d.ts
|
|
1565
|
-
/** Read-only handle to a single file produced by a spec action. */
|
|
1566
|
-
type FileAccessor = {
|
|
1567
|
-
/** The UTF-8 text content. Throws if the file does not exist. */
|
|
1568
|
-
readonly content: string;
|
|
1569
|
-
readonly exists: boolean;
|
|
1570
|
-
/** The file text as a {@link TextAccessor}, keeping only blocks matching `pattern`. */
|
|
1571
|
-
grep: (pattern: string) => TextAccessor;
|
|
1572
|
-
};
|
|
1573
|
-
type BaseResultOptions = {
|
|
1574
|
-
config: SpecificationConfig;
|
|
1575
|
-
testDir: string;
|
|
1576
|
-
workDir?: string | undefined;
|
|
1577
|
-
};
|
|
1578
|
-
/**
|
|
1579
|
-
* Base result - common accessors available after any action type.
|
|
1580
|
-
* Extended by HttpResult, CliResult, and used directly by job results.
|
|
1607
|
+
* When the runner was configured with a `docker` option, the result also
|
|
1608
|
+
* exposes container accessors and participates in `Symbol.asyncDispose`
|
|
1609
|
+
* cleanup. The Docker shell-outs are **lazy**: a test that never calls
|
|
1610
|
+
* `.container(...)` never queries the Docker daemon, so command-only tests
|
|
1611
|
+
* pay zero Docker cost even when the runner is Docker-aware.
|
|
1581
1612
|
*
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1613
|
+
* Dispose always runs one final label-filtered `docker ps` to catch
|
|
1614
|
+
* containers that were spawned during `.exec` but never asserted on —
|
|
1615
|
+
* tests still get leak-free cleanup even if they forget to reach into
|
|
1616
|
+
* a container.
|
|
1584
1617
|
*/
|
|
1585
|
-
declare class BaseResult {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
constructor(options:
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
})
|
|
1600
|
-
|
|
1618
|
+
declare class CliResult extends BaseResult {
|
|
1619
|
+
private readonly commandOutput;
|
|
1620
|
+
private containersCache;
|
|
1621
|
+
private readonly dockerConfig?;
|
|
1622
|
+
private readonly testRunId?;
|
|
1623
|
+
private readonly transform?;
|
|
1624
|
+
constructor(options: {
|
|
1625
|
+
commandOutput: CliOutput;
|
|
1626
|
+
config: SpecificationConfig;
|
|
1627
|
+
dockerConfig?: DockerSpecConfig | undefined;
|
|
1628
|
+
testDir: string;
|
|
1629
|
+
testRunId?: string | undefined;
|
|
1630
|
+
transform?: ((text: string) => string) | undefined;
|
|
1631
|
+
workDir: string;
|
|
1632
|
+
});
|
|
1633
|
+
/** The process exit code. */
|
|
1634
|
+
get exitCode(): number;
|
|
1635
|
+
/** Accessor for the captured standard output with file-based assertions. */
|
|
1636
|
+
get stdout(): TextAccessor;
|
|
1637
|
+
/** Accessor for the captured standard error with file-based assertions. */
|
|
1638
|
+
get stderr(): TextAccessor;
|
|
1639
|
+
/** Accessor for parsing stdout as JSON and asserting against JSON fixtures. */
|
|
1640
|
+
get json(): JsonAccessor;
|
|
1641
|
+
/** Accessor for the temporary working directory the command ran in. */
|
|
1642
|
+
get filesystem(): FilesystemAccessor;
|
|
1643
|
+
/**
|
|
1644
|
+
* Look up a container the command spawned during this run by the value of
|
|
1645
|
+
* its name-label (as declared in the `docker.nameLabel` of the
|
|
1646
|
+
* `specification.cli()` options).
|
|
1647
|
+
* First access triggers a one-shot `docker ps` + `docker inspect`
|
|
1648
|
+
* query and caches the result for the rest of the result's lifetime.
|
|
1649
|
+
* Tests that don't call this never touch Docker.
|
|
1650
|
+
*
|
|
1651
|
+
* Returns an accessor with `.exists === false` when no container
|
|
1652
|
+
* carries that name — callers can still assert absence without a try.
|
|
1653
|
+
*/
|
|
1654
|
+
container(name: string): ContainerAccessor;
|
|
1655
|
+
/** All captured container IDs. Triggers the same lazy query as `container()`. */
|
|
1656
|
+
get containerIds(): string[];
|
|
1657
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
1658
|
+
private ensureDockerAware;
|
|
1659
|
+
private loadContainers;
|
|
1601
1660
|
}
|
|
1602
1661
|
//#endregion
|
|
1603
|
-
//#region src/
|
|
1662
|
+
//#region src/seams/docker/container-accessor.d.ts
|
|
1604
1663
|
/**
|
|
1605
1664
|
* Assertion accessor for a single Docker container captured by the docker()
|
|
1606
1665
|
* spec mode. Mirrors the shape of {@link CliResult} so tests use the same
|
|
@@ -1653,31 +1712,7 @@ declare class ContainerAccessor {
|
|
|
1653
1712
|
private containerExec;
|
|
1654
1713
|
}
|
|
1655
1714
|
//#endregion
|
|
1656
|
-
//#region src/
|
|
1657
|
-
/**
|
|
1658
|
-
* The per-call option both `toMatch` implementations honour.
|
|
1659
|
-
*
|
|
1660
|
-
* A golden is read from disk under node and through a server command from
|
|
1661
|
-
* inside a page, and BOTH builds must answer `{ frozen: true }` the same way —
|
|
1662
|
-
* so the option is declared here, where the node matchers and the page's
|
|
1663
|
-
* matchers can each reach it, rather than in the runner's own folder, which a
|
|
1664
|
-
* seam may not import (rule I1).
|
|
1665
|
-
*/
|
|
1666
|
-
/**
|
|
1667
|
-
* Per-call options for the fixture-file `toMatch` subjects. `frozen` opts a
|
|
1668
|
-
* single fixture OUT of update-mode rewriting: a frozen fixture is NEVER
|
|
1669
|
-
* written under `TEST_UPDATE=1` (or vitest `-u`) — in update mode a frozen
|
|
1670
|
-
* mismatch still throws its diff, and a frozen missing fixture still throws its
|
|
1671
|
-
* "does not exist" error. This is what makes a DELIBERATELY-WRONG fixture (the
|
|
1672
|
-
* subject of a negative test that asserts the mismatch/error rendering)
|
|
1673
|
-
* survivable across update runs instead of being silently overwritten with the
|
|
1674
|
-
* actual output.
|
|
1675
|
-
*/
|
|
1676
|
-
type MatchFixtureOptions = {
|
|
1677
|
-
frozen?: boolean;
|
|
1678
|
-
};
|
|
1679
|
-
//#endregion
|
|
1680
|
-
//#region src/integrations/vitest-browser/ui.d.ts
|
|
1715
|
+
//#region src/seams/vitest-browser/ui.d.ts
|
|
1681
1716
|
/**
|
|
1682
1717
|
* A vanilla DOM subject: a function handed the container it must fill. Its
|
|
1683
1718
|
* optional return is the teardown, the way a DOM library states one.
|
|
@@ -1701,7 +1736,7 @@ declare module 'vitest/browser' {
|
|
|
1701
1736
|
}
|
|
1702
1737
|
}
|
|
1703
1738
|
//#endregion
|
|
1704
|
-
//#region src/
|
|
1739
|
+
//#region src/seams/vitest-browser/vitest-browser.adapter.d.ts
|
|
1705
1740
|
/** What a mounted subject offers the chain, React or vanilla DOM alike. */
|
|
1706
1741
|
type MountedSurface = {
|
|
1707
1742
|
/** The element the subject was mounted into. */
|
|
@@ -1725,7 +1760,7 @@ declare function componentVerbs(surface: MountedSurface): {
|
|
|
1725
1760
|
unmount: () => Promise<void>;
|
|
1726
1761
|
};
|
|
1727
1762
|
//#endregion
|
|
1728
|
-
//#region src/
|
|
1763
|
+
//#region src/facets/component/rendered-text.d.ts
|
|
1729
1764
|
/**
|
|
1730
1765
|
* A stream captured INSIDE the page.
|
|
1731
1766
|
*
|
|
@@ -1740,7 +1775,7 @@ declare class RenderedText extends TextAccessor {
|
|
|
1740
1775
|
readonly capturedInPage: true;
|
|
1741
1776
|
}
|
|
1742
1777
|
//#endregion
|
|
1743
|
-
//#region src/
|
|
1778
|
+
//#region src/facets/component/component.result.d.ts
|
|
1744
1779
|
/**
|
|
1745
1780
|
* What a render resolves to — the component as the page saw it, after the
|
|
1746
1781
|
* scenario ran.
|
|
@@ -1770,7 +1805,7 @@ declare class RenderResult {
|
|
|
1770
1805
|
});
|
|
1771
1806
|
}
|
|
1772
1807
|
//#endregion
|
|
1773
|
-
//#region src/
|
|
1808
|
+
//#region src/facets/component/component.types.d.ts
|
|
1774
1809
|
/**
|
|
1775
1810
|
* The component facet's shape, stated where BOTH runtimes can read it.
|
|
1776
1811
|
*
|
|
@@ -1824,26 +1859,29 @@ type ComponentChain = {
|
|
|
1824
1859
|
wrap: (wrapper: (ui: ComponentUi) => ComponentUi) => ComponentChain;
|
|
1825
1860
|
};
|
|
1826
1861
|
//#endregion
|
|
1827
|
-
//#region src/
|
|
1862
|
+
//#region src/model/elements/elements.d.ts
|
|
1828
1863
|
/**
|
|
1829
1864
|
* The element vocabulary — user-facing descriptors for visit scenarios.
|
|
1830
1865
|
* Reads like English (`click(link('Articles'))`), translates to
|
|
1831
1866
|
* accessibility-first locators in the browser integration. CSS/XPath is
|
|
1832
1867
|
* deliberately not expressible; `testId()` is the single escape hatch.
|
|
1833
1868
|
*
|
|
1834
|
-
* A descriptor must designate exactly ONE element (CONVENTIONS W3).
|
|
1835
|
-
*
|
|
1836
|
-
*
|
|
1837
|
-
*
|
|
1838
|
-
* would say "the Articles link *in the nav*";
|
|
1839
|
-
* 2. `{ exact: true }` — match the accessible name whole rather than as a
|
|
1840
|
-
* substring, when two names genuinely overlap.
|
|
1869
|
+
* A descriptor must designate exactly ONE element (CONVENTIONS W3). A name
|
|
1870
|
+
* matches the accessible name WHOLE, and `within(scope, target)` is how an
|
|
1871
|
+
* overlap is narrowed — the way a person would say "the Articles link *in the
|
|
1872
|
+
* nav*".
|
|
1841
1873
|
*/
|
|
1842
1874
|
/** Options accepted by every named descriptor. */
|
|
1843
1875
|
type ElementOptions = {
|
|
1844
1876
|
/**
|
|
1845
|
-
* Match the accessible name as a whole string.
|
|
1846
|
-
*
|
|
1877
|
+
* Match the accessible name as a whole string. `true` by default since
|
|
1878
|
+
* 16.0.
|
|
1879
|
+
*
|
|
1880
|
+
* A substring match is what a person does NOT mean: `link('Articles')`
|
|
1881
|
+
* designated "Read Articles" as readily as "Articles", so a test could pass
|
|
1882
|
+
* for years against the element beside the one it named. `{ exact: false }`
|
|
1883
|
+
* is the opt-out, for a name that genuinely carries a variable part the
|
|
1884
|
+
* test does not control.
|
|
1847
1885
|
*/
|
|
1848
1886
|
exact?: boolean;
|
|
1849
1887
|
};
|
|
@@ -1958,7 +1996,7 @@ declare const selected: (element: ElementRef) => ElementRef;
|
|
|
1958
1996
|
*/
|
|
1959
1997
|
declare const valued: (element: ElementRef, value: string) => ElementRef;
|
|
1960
1998
|
//#endregion
|
|
1961
|
-
//#region src/
|
|
1999
|
+
//#region src/model/contracts/filters.d.ts
|
|
1962
2000
|
/**
|
|
1963
2001
|
* A text filter on a provider request builder (`openai.chat({ user })`,
|
|
1964
2002
|
* `anthropic.messages({ system })`, …).
|
|
@@ -1976,7 +2014,7 @@ declare const valued: (element: ElementRef, value: string) => ElementRef;
|
|
|
1976
2014
|
*/
|
|
1977
2015
|
type TextFilter = Matcher | RegExp | string;
|
|
1978
2016
|
//#endregion
|
|
1979
|
-
//#region src/
|
|
2017
|
+
//#region src/seams/anthropic/anthropic.d.ts
|
|
1980
2018
|
type AnthropicMessagesFilter = {
|
|
1981
2019
|
model?: TextFilter;
|
|
1982
2020
|
system?: TextFilter;
|
|
@@ -2008,7 +2046,7 @@ declare const anthropic: {
|
|
|
2008
2046
|
timeout(): ContractResponse;
|
|
2009
2047
|
};
|
|
2010
2048
|
//#endregion
|
|
2011
|
-
//#region src/
|
|
2049
|
+
//#region src/model/contracts/http.d.ts
|
|
2012
2050
|
/**
|
|
2013
2051
|
* Request filters for the generic HTTP provider. Every field is a subset
|
|
2014
2052
|
* constraint — a request matches when all provided fields match.
|
|
@@ -2111,7 +2149,7 @@ declare const http: {
|
|
|
2111
2149
|
text(body: string, init?: HttpResponseInit): ContractResponse;
|
|
2112
2150
|
};
|
|
2113
2151
|
//#endregion
|
|
2114
|
-
//#region src/
|
|
2152
|
+
//#region src/seams/openai/openai.d.ts
|
|
2115
2153
|
type OpenAIChatFilter = {
|
|
2116
2154
|
model?: TextFilter;
|
|
2117
2155
|
system?: TextFilter;
|
|
@@ -2169,7 +2207,7 @@ declare const openai: {
|
|
|
2169
2207
|
timeout(): ContractResponse;
|
|
2170
2208
|
};
|
|
2171
2209
|
//#endregion
|
|
2172
|
-
//#region src/
|
|
2210
|
+
//#region src/model/doubles/mock-of.d.ts
|
|
2173
2211
|
/** How much of the port the double answers for. */
|
|
2174
2212
|
type MockOfOptions = {
|
|
2175
2213
|
/**
|
|
@@ -2183,7 +2221,7 @@ type MockOfOptions = {
|
|
|
2183
2221
|
};
|
|
2184
2222
|
/**
|
|
2185
2223
|
* A typed double for an injected port — the fourth rung of the doubles ladder
|
|
2186
|
-
* (docs/
|
|
2224
|
+
* (docs/18-conventions.md), and the only one a module test may build itself.
|
|
2187
2225
|
*
|
|
2188
2226
|
* The type argument is the port: `T` is constrained to `object` and has no
|
|
2189
2227
|
* default, so a double is always asked for something. A call with no argument
|
|
@@ -2200,21 +2238,7 @@ declare function mockOf<T extends object>(options?: MockOfOptions): DeepMockProx
|
|
|
2200
2238
|
/** The factory's own shape — kept as a name so a consumer can annotate with it. */
|
|
2201
2239
|
type MockPort = typeof mockOf;
|
|
2202
2240
|
//#endregion
|
|
2203
|
-
//#region src/
|
|
2204
|
-
/** Interface for freezing and resetting the global Date in tests. */
|
|
2205
|
-
type MockDatePort = {
|
|
2206
|
-
/** Restore the real Date object. */
|
|
2207
|
-
reset: () => void;
|
|
2208
|
-
/** Freeze `Date.now()` and `new Date()` to the given value. */
|
|
2209
|
-
set: (date: Date | number | string) => void;
|
|
2210
|
-
};
|
|
2211
|
-
/**
|
|
2212
|
-
* Freeze or reset the global Date for deterministic time-dependent tests.
|
|
2213
|
-
* Wraps the `mockdate` package.
|
|
2214
|
-
*/
|
|
2215
|
-
declare const mockOfDate: MockDatePort;
|
|
2216
|
-
//#endregion
|
|
2217
|
-
//#region src/vitest/clock.d.ts
|
|
2241
|
+
//#region src/model/clock/clock.d.ts
|
|
2218
2242
|
/**
|
|
2219
2243
|
* The one time primitive.
|
|
2220
2244
|
*
|
|
@@ -2269,7 +2293,7 @@ declare const clock: {
|
|
|
2269
2293
|
run(iso?: string): PinnedClock;
|
|
2270
2294
|
};
|
|
2271
2295
|
//#endregion
|
|
2272
|
-
//#region src/
|
|
2296
|
+
//#region src/model/assertions/required.d.ts
|
|
2273
2297
|
/**
|
|
2274
2298
|
* The value, or a failure that says what was missing and why it mattered.
|
|
2275
2299
|
*
|
|
@@ -2286,7 +2310,7 @@ declare const clock: {
|
|
|
2286
2310
|
*/
|
|
2287
2311
|
declare function required<T>(value: null | T | undefined, why: string): T;
|
|
2288
2312
|
//#endregion
|
|
2289
|
-
//#region src/
|
|
2313
|
+
//#region src/model/assertions/wait-until.d.ts
|
|
2290
2314
|
/** How long to wait, and how often to look. */
|
|
2291
2315
|
type WaitUntilOptions = {
|
|
2292
2316
|
/** Milliseconds between two readings. Default 50. */
|
|
@@ -2325,7 +2349,7 @@ declare function waitUntil(predicate: () => boolean | Promise<boolean>, options?
|
|
|
2325
2349
|
//#endregion
|
|
2326
2350
|
//#region src/surface.d.ts
|
|
2327
2351
|
declare module 'vitest' {
|
|
2328
|
-
interface Assertion<T =
|
|
2352
|
+
interface Assertion<R extends Promise<void> | void = void, T = unknown> {
|
|
2329
2353
|
/**
|
|
2330
2354
|
* Assert the subject is empty — zero rows for a table (async), an
|
|
2331
2355
|
* empty stream for a text accessor (console, errors, stdout).
|
|
@@ -2349,9 +2373,9 @@ declare module 'vitest' {
|
|
|
2349
2373
|
* Use it for a deliberately-wrong fixture whose diff/error rendering is
|
|
2350
2374
|
* the behaviour under test.
|
|
2351
2375
|
*/
|
|
2352
|
-
toMatch: T extends DirectoryAccessor | FilesystemAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends {
|
|
2376
|
+
toMatch: T extends DirectoryAccessor | FilesystemAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> & R : T extends {
|
|
2353
2377
|
readonly capturedInPage: true;
|
|
2354
|
-
} ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends JsonAccessor | ResponseAccessor | TextAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) =>
|
|
2378
|
+
} ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> & R : T extends JsonAccessor | ResponseAccessor | TextAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => R : (expected: RegExp | string, options?: MatchFixtureOptions) => R;
|
|
2355
2379
|
/**
|
|
2356
2380
|
* Assert the table contains exactly the given rows for the given
|
|
2357
2381
|
* columns. Cells accept `match.*` dynamic-value matchers. Async —
|
|
@@ -2364,17 +2388,39 @@ declare module 'vitest' {
|
|
|
2364
2388
|
}
|
|
2365
2389
|
}
|
|
2366
2390
|
//#endregion
|
|
2367
|
-
//#region src/
|
|
2391
|
+
//#region src/model/chain/reporter.d.ts
|
|
2392
|
+
/**
|
|
2393
|
+
* What the subject IS, for the line the startup report closes on.
|
|
2394
|
+
*
|
|
2395
|
+
* Four shapes, because there are four: an app built and served inside this
|
|
2396
|
+
* process (api), a child process the runner started and polls (a site's dev
|
|
2397
|
+
* server), a deployment already running somewhere (`url`), and a subject that
|
|
2398
|
+
* is not an app at all — a binary, a pipeline, a module, a screen — which gets
|
|
2399
|
+
* no line rather than a wrong one.
|
|
2400
|
+
*/
|
|
2401
|
+
type AppInfo = {
|
|
2402
|
+
/** The command, for a child process. */
|
|
2403
|
+
command?: string | undefined;
|
|
2404
|
+
type: 'http' | 'in-process' | 'none' | 'process';
|
|
2405
|
+
url?: string | undefined;
|
|
2406
|
+
};
|
|
2407
|
+
//#endregion
|
|
2408
|
+
//#region src/model/chain/orchestrator.d.ts
|
|
2368
2409
|
type OrchestratorOptions = {
|
|
2369
2410
|
/**
|
|
2370
|
-
*
|
|
2371
|
-
*
|
|
2372
|
-
*
|
|
2373
|
-
*
|
|
2374
|
-
|
|
2411
|
+
* What the subject IS — the line the startup report closes on. Defaults to
|
|
2412
|
+
* the api shape, which is the only facet that serves an app in this
|
|
2413
|
+
* process; a facet whose subject is a binary, a pipeline or a screen says
|
|
2414
|
+
* `{ type: 'none' }` and gets no line rather than a wrong one.
|
|
2415
|
+
*/
|
|
2416
|
+
subject?: AppInfo | undefined;
|
|
2417
|
+
/**
|
|
2418
|
+
* Named infrastructure record. Keys are the database vocabulary of the spec
|
|
2419
|
+
* (`.seed()` / `.table()` `database` option) and the name each handle is
|
|
2420
|
+
* reported and initialised under: the kebab-case form of the key is the
|
|
2421
|
+
* directory its init script lives in (`analyticsDb` → `docker/analytics-db/`).
|
|
2375
2422
|
*/
|
|
2376
2423
|
services: Record<string, ServiceHandle>;
|
|
2377
|
-
mode: 'e2e' | 'integration';
|
|
2378
2424
|
/**
|
|
2379
2425
|
* The project root, already resolved. REQUIRED: the constructors derive it
|
|
2380
2426
|
* by walking up from the calling specification file (CONVENTIONS A9), and
|
|
@@ -2384,68 +2430,40 @@ type OrchestratorOptions = {
|
|
|
2384
2430
|
* launched, which in a workspace is rarely the same directory.
|
|
2385
2431
|
*/
|
|
2386
2432
|
root: string;
|
|
2387
|
-
/** Compose project name — used for per-worker stack isolation. */
|
|
2388
|
-
projectName?: string;
|
|
2389
2433
|
};
|
|
2390
2434
|
/**
|
|
2391
|
-
*
|
|
2392
|
-
*
|
|
2393
|
-
*
|
|
2435
|
+
* The infrastructure a specification declares, started and stopped as a unit.
|
|
2436
|
+
*
|
|
2437
|
+
* Every declared handle becomes a real thing: a container through
|
|
2438
|
+
* testcontainers, or an embedded service (SQLite) that needs no container. The
|
|
2439
|
+
* record KEY is the only name in play — it is the spec's database vocabulary,
|
|
2440
|
+
* the name the startup report prints, and, kebab-cased, the directory the
|
|
2441
|
+
* service reads its init script from.
|
|
2442
|
+
*
|
|
2443
|
+
* It is internal wiring: the constructors drive it, and no consumer names it.
|
|
2394
2444
|
*/
|
|
2395
2445
|
declare class Orchestrator {
|
|
2396
2446
|
private readonly services;
|
|
2397
|
-
private readonly mode;
|
|
2398
2447
|
private readonly root;
|
|
2399
|
-
private readonly
|
|
2448
|
+
private readonly subject;
|
|
2400
2449
|
private running;
|
|
2401
|
-
private composeStack;
|
|
2402
|
-
private composeHandles;
|
|
2403
2450
|
private started;
|
|
2404
2451
|
constructor(options: OrchestratorOptions);
|
|
2405
2452
|
/**
|
|
2406
|
-
*
|
|
2407
|
-
* key resolves to the service named exactly like it, else the kebab-case
|
|
2408
|
-
* conversion of the key; an explicit `composeService` wins. Throws on an
|
|
2409
|
-
* ambiguous binding (both names present). Runs once compose config is known
|
|
2410
|
-
* so the two-step resolution can consult the real service list.
|
|
2411
|
-
*/
|
|
2412
|
-
private resolveBindings;
|
|
2413
|
-
/**
|
|
2414
|
-
* Start declared services via testcontainers (integration mode).
|
|
2453
|
+
* Start every declared service.
|
|
2415
2454
|
* Phase 1: start all containers in parallel (the slow part).
|
|
2416
2455
|
* Phase 2: wire connections, healthcheck, and init sequentially (fast).
|
|
2417
2456
|
*/
|
|
2418
2457
|
start(): Promise<void>;
|
|
2419
|
-
/**
|
|
2420
|
-
* Stop testcontainers (integration mode).
|
|
2421
|
-
*/
|
|
2458
|
+
/** Stop every container this stack started. */
|
|
2422
2459
|
stop(): Promise<void>;
|
|
2423
|
-
/**
|
|
2424
|
-
* Start full docker compose stack (e2e mode).
|
|
2425
|
-
* Auto-detects infra services and creates handles for them.
|
|
2426
|
-
*/
|
|
2427
|
-
startCompose(): Promise<void>;
|
|
2428
|
-
/**
|
|
2429
|
-
* Stop docker compose stack (e2e mode).
|
|
2430
|
-
*/
|
|
2431
|
-
stopCompose(): Promise<void>;
|
|
2432
|
-
/**
|
|
2433
|
-
* Get the default database — the first declared handle that is one.
|
|
2434
|
-
*/
|
|
2460
|
+
/** The default database — the first declared handle that is one. */
|
|
2435
2461
|
getDatabase(): DatabasePort | null;
|
|
2436
|
-
/**
|
|
2437
|
-
* Get all database services keyed by their record key (declared services)
|
|
2438
|
-
* or compose service name (stack-detected services).
|
|
2439
|
-
*/
|
|
2462
|
+
/** Every database service, keyed by its record key. */
|
|
2440
2463
|
getDatabases(): Map<string, DatabasePort>;
|
|
2441
|
-
private allHandles;
|
|
2442
|
-
/**
|
|
2443
|
-
* Get app URL from compose (e2e mode).
|
|
2444
|
-
*/
|
|
2445
|
-
getAppUrl(): null | string;
|
|
2446
2464
|
}
|
|
2447
2465
|
//#endregion
|
|
2448
|
-
//#region src/
|
|
2466
|
+
//#region src/model/chain/services.d.ts
|
|
2449
2467
|
/**
|
|
2450
2468
|
* Infrastructure services declared as a named record. Keys become the typed
|
|
2451
2469
|
* vocabulary of the whole spec: the server factory receives the same record,
|
|
@@ -2455,41 +2473,29 @@ type ServiceRecord = Record<string, ServiceHandle>;
|
|
|
2455
2473
|
/** Keys of a services record whose handles are databases. */
|
|
2456
2474
|
type DatabaseKeys<Services extends ServiceRecord> = { [K in keyof Services]: Services[K] extends DatabasePort ? K & string : never; }[keyof Services];
|
|
2457
2475
|
//#endregion
|
|
2458
|
-
//#region src/
|
|
2476
|
+
//#region src/facets/api/api.specification.d.ts
|
|
2459
2477
|
/** Any object with a request method compatible with Hono's app.request(). */
|
|
2460
2478
|
type HonoApp = {
|
|
2461
2479
|
request: (path: string, init?: RequestInit) => Promise<Response> | Response;
|
|
2462
2480
|
};
|
|
2463
|
-
/** Execution mode — exists ONLY on `specification.api()` (CONVENTIONS A5). */
|
|
2464
|
-
type SpecificationMode = 'compose' | 'node';
|
|
2465
2481
|
/** Options for {@link startApi | specification.api}. */
|
|
2466
2482
|
type ApiSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2467
2483
|
/**
|
|
2468
|
-
*
|
|
2469
|
-
*
|
|
2470
|
-
*
|
|
2471
|
-
* project via `env: { TEST_MODE: 'compose' }` (CONVENTIONS A5).
|
|
2472
|
-
*/
|
|
2473
|
-
mode?: SpecificationMode;
|
|
2474
|
-
/**
|
|
2475
|
-
* Project root override for compose detection and init scripts. When
|
|
2476
|
-
* absent, the root is auto-discovered by walking up from the calling
|
|
2477
|
-
* specification file to the first directory containing
|
|
2478
|
-
* `docker/compose.test.yaml`, else the first containing `package.json`
|
|
2479
|
-
* (CONVENTIONS A9).
|
|
2484
|
+
* Project root override for init scripts and artefact paths. When absent,
|
|
2485
|
+
* the root is auto-discovered by walking up from the calling specification
|
|
2486
|
+
* file to the first directory containing `package.json` (CONVENTIONS A9).
|
|
2480
2487
|
*/
|
|
2481
2488
|
root?: string;
|
|
2482
2489
|
/**
|
|
2483
2490
|
* The app factory — receives the started services record (fully typed)
|
|
2484
|
-
* and returns the Hono app.
|
|
2485
|
-
* mode (the app runs as a compose service there).
|
|
2491
|
+
* and returns the Hono app.
|
|
2486
2492
|
*/
|
|
2487
|
-
server
|
|
2493
|
+
server: (services: Services) => HonoApp;
|
|
2488
2494
|
/**
|
|
2489
2495
|
* Named infrastructure record. Keys become the `database` vocabulary of
|
|
2490
|
-
* `.seed()` / `.table()
|
|
2491
|
-
*
|
|
2492
|
-
*
|
|
2496
|
+
* `.seed()` / `.table()`, and, kebab-cased, the folder each service reads
|
|
2497
|
+
* its init script from (`{ analyticsDb: postgres() }` →
|
|
2498
|
+
* `docker/analytics-db/init.sql`).
|
|
2493
2499
|
*/
|
|
2494
2500
|
services?: Services;
|
|
2495
2501
|
};
|
|
@@ -2508,12 +2514,10 @@ type ApiHandle<DatabaseKey extends string = string> = {
|
|
|
2508
2514
|
* usable with `await expect(...).toBeRunning()` and read accessors.
|
|
2509
2515
|
*/
|
|
2510
2516
|
docker: (containerId: string) => ContainerAccessor;
|
|
2511
|
-
/** The orchestrator managing the test infrastructure lifecycle. */
|
|
2512
|
-
orchestrator: Orchestrator;
|
|
2513
2517
|
};
|
|
2514
2518
|
declare function startApi<Services extends ServiceRecord>(options: ApiSpecificationOptions<Services>): Promise<ApiHandle<DatabaseKeys<Services>>>;
|
|
2515
2519
|
//#endregion
|
|
2516
|
-
//#region src/
|
|
2520
|
+
//#region src/facets/cli/cli.specification.d.ts
|
|
2517
2521
|
/** Options for {@link startCli | specification.cli}. */
|
|
2518
2522
|
type CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2519
2523
|
/**
|
|
@@ -2549,9 +2553,9 @@ type CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
|
2549
2553
|
serve?: Record<string, LiterateServeRegistration>;
|
|
2550
2554
|
/**
|
|
2551
2555
|
* Project-root override (CONVENTIONS A9) — the single meaning of `root`:
|
|
2552
|
-
* it anchors
|
|
2553
|
-
*
|
|
2554
|
-
* or `$FIXTURES/` paths on its own.
|
|
2556
|
+
* it anchors the local-bin resolution of the tested binary, in place of
|
|
2557
|
+
* the walk to the nearest `package.json`. It is NOT a fixtures root;
|
|
2558
|
+
* `.fixture()` resolves feature-local or `$FIXTURES/` paths on its own.
|
|
2555
2559
|
*/
|
|
2556
2560
|
root?: string;
|
|
2557
2561
|
/**
|
|
@@ -2589,7 +2593,7 @@ type CliHandle<DatabaseKey extends string = string> = {
|
|
|
2589
2593
|
};
|
|
2590
2594
|
declare function startCli<Services extends ServiceRecord>(bin: string, options?: CliSpecificationOptions<Services>): Promise<CliHandle<DatabaseKeys<Services>>>;
|
|
2591
2595
|
//#endregion
|
|
2592
|
-
//#region src/
|
|
2596
|
+
//#region src/facets/integration/integration.specification.d.ts
|
|
2593
2597
|
/** Options for {@link startIntegration | specification.integration}. */
|
|
2594
2598
|
type IntegrationSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2595
2599
|
/** Project-root override — see the `root` option of `specification.api()`. */
|
|
@@ -2619,19 +2623,20 @@ type IntegrationHandle<Services extends ServiceRecord = ServiceRecord, DatabaseK
|
|
|
2619
2623
|
*
|
|
2620
2624
|
* The fork is the SUBJECT. A module alone is a module test, beside its code,
|
|
2621
2625
|
* with no runner at all. A module that needs a real database, or whose oracle
|
|
2622
|
-
* is a golden file, is this —
|
|
2623
|
-
*
|
|
2624
|
-
*
|
|
2626
|
+
* is a golden file, is this one — which is what spares it the shape it would
|
|
2627
|
+
* otherwise take: plain vitest under a `specs/` folder, a hand-built sqlite
|
|
2628
|
+
* template, a `beforeAll` starting a container, and a `toMatchSnapshot` beside
|
|
2629
|
+
* a framework that owns a golden engine.
|
|
2625
2630
|
*/
|
|
2626
2631
|
declare function startIntegration<Services extends ServiceRecord>(options?: IntegrationSpecificationOptions<Services>): Promise<IntegrationHandle<Services, DatabaseKeys<Services>>>;
|
|
2627
2632
|
//#endregion
|
|
2628
|
-
//#region src/
|
|
2633
|
+
//#region src/facets/jobs/jobs.specification.d.ts
|
|
2629
2634
|
/** Options for {@link startJobs | specification.jobs}. */
|
|
2630
2635
|
type JobsSpecificationOptions<Services extends ServiceRecord = ServiceRecord> = {
|
|
2631
2636
|
/**
|
|
2632
2637
|
* Named jobs triggerable via `jobs.trigger(name)` — a factory receiving
|
|
2633
2638
|
* the started services record, or a static array. Jobs run in-process by
|
|
2634
|
-
* definition (CONVENTIONS
|
|
2639
|
+
* definition (CONVENTIONS A8).
|
|
2635
2640
|
*/
|
|
2636
2641
|
jobs: ((services: Services) => JobHandle[]) | JobHandle[];
|
|
2637
2642
|
/** Project root override — see the `root` option of `specification.api()`. */
|
|
@@ -2654,7 +2659,7 @@ type JobsHandle<DatabaseKey extends string = string> = {
|
|
|
2654
2659
|
};
|
|
2655
2660
|
declare function startJobs<Services extends ServiceRecord>(options: JobsSpecificationOptions<Services>): Promise<JobsHandle<DatabaseKeys<Services>>>;
|
|
2656
2661
|
//#endregion
|
|
2657
|
-
//#region src/
|
|
2662
|
+
//#region src/facets/mobile/mobile.specification.d.ts
|
|
2658
2663
|
/**
|
|
2659
2664
|
* The declared stub backend behind the app under test. The framework owns
|
|
2660
2665
|
* the simulator and appium but NOT the JS bundler (Metro belongs to the
|
|
@@ -2735,11 +2740,11 @@ type MobileHandle = {
|
|
|
2735
2740
|
};
|
|
2736
2741
|
declare function startMobile<Services extends ServiceRecord>(options: MobileSpecificationOptions<Services>): Promise<MobileHandle>;
|
|
2737
2742
|
//#endregion
|
|
2738
|
-
//#region src/
|
|
2743
|
+
//#region src/seams/process/serve.adapter.d.ts
|
|
2739
2744
|
/**
|
|
2740
2745
|
* The ONE shape an external process takes — the site under test, a backend a
|
|
2741
2746
|
* page talks to, a bundler a simulator loads from. Built by `process()`
|
|
2742
|
-
* (chapter
|
|
2747
|
+
* (chapter 17) and accepted wherever a facet starts something outside this
|
|
2743
2748
|
* process: `website({ server })`, `services: { api: process(…) }`, and the
|
|
2744
2749
|
* literate `serve:` registry.
|
|
2745
2750
|
*/
|
|
@@ -2779,26 +2784,16 @@ type ProcessOptions = {
|
|
|
2779
2784
|
/** Readiness budget in milliseconds. Default 30 000. */
|
|
2780
2785
|
timeout?: number;
|
|
2781
2786
|
};
|
|
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
2787
|
//#endregion
|
|
2792
|
-
//#region src/
|
|
2788
|
+
//#region src/model/chain/process.d.ts
|
|
2793
2789
|
/**
|
|
2794
2790
|
* An external process, declared like any other service.
|
|
2795
2791
|
*
|
|
2796
|
-
*
|
|
2797
|
-
*
|
|
2798
|
-
*
|
|
2799
|
-
*
|
|
2800
|
-
*
|
|
2801
|
-
* framework owns the lifetime.
|
|
2792
|
+
* One shape for every one of them: a site's dev server, an API behind the
|
|
2793
|
+
* site, the bundler a simulator loads from. Without it each is declared
|
|
2794
|
+
* somewhere else — a facet option, a document's `serve:` entry, a `beforeAll`
|
|
2795
|
+
* spawning a child and an `afterAll` that sometimes forgets to kill it — and
|
|
2796
|
+
* the lifetime belongs to whoever remembered. Here it is the framework's.
|
|
2802
2797
|
*
|
|
2803
2798
|
* It sits in a `services` record beside `postgres()`, `redis()` and
|
|
2804
2799
|
* `sqlite()`, so it starts after them and can be handed their connection
|
|
@@ -2810,7 +2805,7 @@ declare class ProcessHandle implements ServiceHandle {
|
|
|
2810
2805
|
readonly defaultPort = 0;
|
|
2811
2806
|
readonly environment: Record<string, string>;
|
|
2812
2807
|
readonly type = "process";
|
|
2813
|
-
|
|
2808
|
+
serviceName: null | string;
|
|
2814
2809
|
connectionString: string;
|
|
2815
2810
|
started: boolean;
|
|
2816
2811
|
private adapter;
|
|
@@ -2866,7 +2861,7 @@ declare class ProcessHandle implements ServiceHandle {
|
|
|
2866
2861
|
*/
|
|
2867
2862
|
declare function processService(options: ProcessOptions): ProcessHandle;
|
|
2868
2863
|
//#endregion
|
|
2869
|
-
//#region src/
|
|
2864
|
+
//#region src/facets/website/website.specification.d.ts
|
|
2870
2865
|
/** What `server` may be handed as, and what it resolves to. */
|
|
2871
2866
|
type ServerSpec<Services extends ServiceRecord> = ((services: Services) => ProcessHandle | ProcessOptions) | ProcessHandle | ProcessOptions;
|
|
2872
2867
|
/**
|
|
@@ -2909,6 +2904,14 @@ type WebsiteSpecificationOptions<Services extends ServiceRecord = ServiceRecord>
|
|
|
2909
2904
|
* API it was started beside.
|
|
2910
2905
|
*/
|
|
2911
2906
|
services?: Services;
|
|
2907
|
+
/**
|
|
2908
|
+
* Escape hatch: normaliser applied to every compared reading of a result
|
|
2909
|
+
* — the head projection and the json-ld among them — before the
|
|
2910
|
+
* comparison (CONVENTIONS D6). Does NOT mutate the raw `.text` accessor.
|
|
2911
|
+
* Prefer `{{token}}` placeholders in fixtures; this is for what a token
|
|
2912
|
+
* cannot name, a meta a FRAMEWORK owns and the page never asked for.
|
|
2913
|
+
*/
|
|
2914
|
+
transform?: ((text: string) => string) | undefined;
|
|
2912
2915
|
} & ({
|
|
2913
2916
|
/**
|
|
2914
2917
|
* Declared stub backend: started BEFORE the server command, its
|
|
@@ -2944,7 +2947,7 @@ type WebsiteHandle = {
|
|
|
2944
2947
|
};
|
|
2945
2948
|
declare function startWebsite<Services extends ServiceRecord>(options: WebsiteSpecificationOptions<Services>): Promise<WebsiteHandle>;
|
|
2946
2949
|
//#endregion
|
|
2947
|
-
//#region src/
|
|
2950
|
+
//#region src/facets/specification.d.ts
|
|
2948
2951
|
/**
|
|
2949
2952
|
* The six specification constructors (CONVENTIONS A2) — created in a
|
|
2950
2953
|
* `*.specification.ts` file under `specs/`, destructured with canonical
|
|
@@ -2980,11 +2983,11 @@ type Specification = {
|
|
|
2980
2983
|
};
|
|
2981
2984
|
declare const specification: Specification;
|
|
2982
2985
|
//#endregion
|
|
2983
|
-
//#region src/
|
|
2986
|
+
//#region src/facets/component/component.specification.d.ts
|
|
2984
2987
|
/** Present, typed, and refusing — every setup and the terminal action alike. */
|
|
2985
2988
|
declare const component: ComponentChain;
|
|
2986
2989
|
//#endregion
|
|
2987
|
-
//#region src/
|
|
2990
|
+
//#region src/model/literate/spec-document.d.ts
|
|
2988
2991
|
/**
|
|
2989
2992
|
* The facets a document may describe. `cli` is the only one implemented; the
|
|
2990
2993
|
* key exists so a second facet can arrive without a second grammar.
|
|
@@ -3064,15 +3067,7 @@ type SpecDocument = {
|
|
|
3064
3067
|
serve: SpecServeEntry[];
|
|
3065
3068
|
};
|
|
3066
3069
|
//#endregion
|
|
3067
|
-
//#region src/
|
|
3068
|
-
/** Return all container IDs (running or stopped) that carry `key=value`. */
|
|
3069
|
-
declare function findContainersByLabel(key: string, value: string): string[];
|
|
3070
|
-
/** Return the raw `docker inspect` payload (object, not array) for a container. */
|
|
3071
|
-
declare function inspectContainer(id: string): unknown;
|
|
3072
|
-
/** Force-remove the given container IDs in a single call. Errors are swallowed. */
|
|
3073
|
-
declare function removeContainers(ids: string[]): void;
|
|
3074
|
-
//#endregion
|
|
3075
|
-
//#region src/specification/facets/_common/result/text-subject.d.ts
|
|
3070
|
+
//#region src/model/result/text-subject.d.ts
|
|
3076
3071
|
/**
|
|
3077
3072
|
* Wrap an arbitrary string into a {@link TextAccessor} anchored on the calling
|
|
3078
3073
|
* test's directory — the same caller-detection the builders use.
|
|
@@ -3098,7 +3093,7 @@ declare function removeContainers(ids: string[]): void;
|
|
|
3098
3093
|
*/
|
|
3099
3094
|
declare function text(value: string): TextAccessor;
|
|
3100
3095
|
//#endregion
|
|
3101
|
-
//#region src/
|
|
3096
|
+
//#region src/model/ports/container.port.d.ts
|
|
3102
3097
|
/**
|
|
3103
3098
|
* Abstract container interface.
|
|
3104
3099
|
* Represents a running service (database, cache, etc.)
|
|
@@ -3118,13 +3113,8 @@ type ContainerPort = {
|
|
|
3118
3113
|
getLogs: () => Promise<string>;
|
|
3119
3114
|
};
|
|
3120
3115
|
//#endregion
|
|
3121
|
-
//#region src/
|
|
3116
|
+
//#region src/seams/postgres/postgres.d.ts
|
|
3122
3117
|
type PostgresOptions = {
|
|
3123
|
-
/**
|
|
3124
|
-
* Map to a service in docker/compose.test.yaml. Defaults to the handle's
|
|
3125
|
-
* key in the declared services record.
|
|
3126
|
-
*/
|
|
3127
|
-
composeService?: string;
|
|
3128
3118
|
/** Override image. */
|
|
3129
3119
|
image?: string;
|
|
3130
3120
|
/** Override environment variables. */
|
|
@@ -3132,7 +3122,7 @@ type PostgresOptions = {
|
|
|
3132
3122
|
};
|
|
3133
3123
|
declare class PostgresHandle implements DatabasePort, ServiceHandle {
|
|
3134
3124
|
readonly type = "postgres";
|
|
3135
|
-
|
|
3125
|
+
serviceName: null | string;
|
|
3136
3126
|
readonly defaultPort = 5432;
|
|
3137
3127
|
readonly defaultImage: string;
|
|
3138
3128
|
readonly environment: Record<string, string>;
|
|
@@ -3145,7 +3135,7 @@ declare class PostgresHandle implements DatabasePort, ServiceHandle {
|
|
|
3145
3135
|
buildConnectionString(host: string, port: number): string;
|
|
3146
3136
|
createDatabaseAdapter(): DatabasePort;
|
|
3147
3137
|
healthcheck(): Promise<void>;
|
|
3148
|
-
initialize(
|
|
3138
|
+
initialize(dockerDir: string): Promise<void>;
|
|
3149
3139
|
private getClient;
|
|
3150
3140
|
seed(sql: string): Promise<void>;
|
|
3151
3141
|
reset(): Promise<void>;
|
|
@@ -3156,28 +3146,23 @@ declare class PostgresHandle implements DatabasePort, ServiceHandle {
|
|
|
3156
3146
|
* Create a PostgreSQL service handle.
|
|
3157
3147
|
*
|
|
3158
3148
|
* @example
|
|
3159
|
-
* //
|
|
3160
|
-
* //
|
|
3161
|
-
* // {
|
|
3162
|
-
* //
|
|
3163
|
-
* const events = postgres(
|
|
3149
|
+
* // The record key is the name: it is the spec's `database` vocabulary, and
|
|
3150
|
+
* // Kebab-cased it is the folder the init script is read from.
|
|
3151
|
+
* // { db: postgres() } → docker/db/init.sql, then docker/postgres/init.sql
|
|
3152
|
+
* // { analyticsDb: postgres() } → docker/analytics-db/init.sql
|
|
3153
|
+
* const events = postgres();
|
|
3164
3154
|
* // After start: events.connectionString is populated
|
|
3165
3155
|
*/
|
|
3166
3156
|
declare function postgres(options?: PostgresOptions): PostgresHandle;
|
|
3167
3157
|
//#endregion
|
|
3168
|
-
//#region src/
|
|
3158
|
+
//#region src/seams/redis/redis.d.ts
|
|
3169
3159
|
type RedisOptions = {
|
|
3170
|
-
/**
|
|
3171
|
-
* Map to a service in docker/compose.test.yaml. Defaults to the handle's
|
|
3172
|
-
* key in the declared services record.
|
|
3173
|
-
*/
|
|
3174
|
-
composeService?: string;
|
|
3175
3160
|
/** Override image. */
|
|
3176
3161
|
image?: string;
|
|
3177
3162
|
};
|
|
3178
3163
|
declare class RedisHandle implements ServiceHandle {
|
|
3179
3164
|
readonly type = "redis";
|
|
3180
|
-
|
|
3165
|
+
serviceName: null | string;
|
|
3181
3166
|
readonly defaultPort = 6379;
|
|
3182
3167
|
readonly defaultImage: string;
|
|
3183
3168
|
readonly environment: Record<string, string>;
|
|
@@ -3196,13 +3181,13 @@ declare class RedisHandle implements ServiceHandle {
|
|
|
3196
3181
|
* Create a Redis service handle.
|
|
3197
3182
|
*
|
|
3198
3183
|
* @example
|
|
3199
|
-
* // The record key
|
|
3184
|
+
* // The record key is the name it reports under: { cache: redis() } → "cache".
|
|
3200
3185
|
* const cache = redis();
|
|
3201
3186
|
* // After start: cache.connectionString is populated
|
|
3202
3187
|
*/
|
|
3203
3188
|
declare function redis(options?: RedisOptions): RedisHandle;
|
|
3204
3189
|
//#endregion
|
|
3205
|
-
//#region src/
|
|
3190
|
+
//#region src/seams/sqlite/sqlite.d.ts
|
|
3206
3191
|
type SqliteOptions = {
|
|
3207
3192
|
/**
|
|
3208
3193
|
* Path to a SQL file used to initialize the database schema.
|
|
@@ -3219,7 +3204,7 @@ type SqliteOptions = {
|
|
|
3219
3204
|
};
|
|
3220
3205
|
declare class SqliteHandle implements DatabasePort, ServiceHandle {
|
|
3221
3206
|
readonly type = "sqlite";
|
|
3222
|
-
|
|
3207
|
+
serviceName: null | string;
|
|
3223
3208
|
readonly defaultPort = 0;
|
|
3224
3209
|
readonly defaultImage = "";
|
|
3225
3210
|
readonly environment: Record<string, string>;
|
|
@@ -3249,7 +3234,7 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
|
|
|
3249
3234
|
* rather than proceeding into a second concurrent `prisma db push` on the
|
|
3250
3235
|
* same file (which is what `database is locked` was).
|
|
3251
3236
|
*/
|
|
3252
|
-
initialize(
|
|
3237
|
+
initialize(_dockerDir?: string, root?: string): Promise<void>;
|
|
3253
3238
|
/**
|
|
3254
3239
|
* Build the template on a private path, then rename it into place.
|
|
3255
3240
|
*
|
|
@@ -3285,8 +3270,8 @@ declare function sqlite(options?: SqliteOptions): SqliteHandle;
|
|
|
3285
3270
|
//#region src/index.d.ts
|
|
3286
3271
|
/**
|
|
3287
3272
|
* The module-scope network double, on msw's node server — what a test with no
|
|
3288
|
-
* chain to hang contracts on reaches for (chapter
|
|
3273
|
+
* chain to hang contracts on reaches for (chapter 16).
|
|
3289
3274
|
*/
|
|
3290
3275
|
declare const intercept: Intercept;
|
|
3291
3276
|
//#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
|
|
3277
|
+
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, type BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CallResult, type CaptureScope, type CliEnv, type CliHandle, type CliInput, type CliOutput, type CliPort, type CliResult, type CliSpecification, type CliSpecificationOptions, type ComponentChain, type ComponentScenario, type ComponentUi, type ComponentVisitor, type 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, type DirectoryAccessor, type DockerSpecConfig, type DomMount, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, type FetchResult, type FileAccessor, type FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, type HttpResult, type HttpStreamInit, type IntegrationHandle, type IntegrationSpecification, type IntegrationSpecificationOptions, type Intercept, type InterceptOptions, type InterceptScope, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsResult, type JobsSpecification, type JobsSpecificationOptions, type 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 MockOfOptions, type MockPort, type PageResult, type PinnedClock, type PostgresOptions, ProcessHandle, type ProcessOptions, type RedisOptions, type RenderResult, type RenderSubject, type RenderedText, type ResponseAccessor, type ScreenNode, type ScreenResult, 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 SqliteOptions, type SseEvent, type StreamBody, type TableAccessor, type 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, focused, form, heading, http, intercept, link, listitem, main, match, mockOf, navigation, openai, option, postgres, processService as process, redis, region, required, row, search, selected, specification, sqlite, status, table, testId, text, valued, waitUntil, within };
|