@jterrazz/test 16.1.0 → 17.0.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 +45 -171
- package/dist/browser/index.js +2 -2867
- package/dist/browser/services.js +2 -0
- package/dist/browser/stubs.js +2 -0
- package/dist/checker.d.ts +6 -1
- package/dist/checker.js +4551 -2612
- package/dist/config.d.ts +40 -0
- package/dist/config.js +309 -0
- package/dist/in-place.global-setup.d.ts +11 -0
- package/dist/in-place.global-setup.js +21 -0
- package/dist/index.d.ts +1653 -3002
- package/dist/index.js +5226 -3845
- package/dist/mobile-session.global-setup.d.ts +14 -0
- package/dist/mobile-session.global-setup.js +31 -0
- package/dist/oxlint.cjs +6073 -5616
- package/dist/oxlint.d.cts +43 -7212
- package/dist/oxlint.d.ts +43 -7212
- package/dist/oxlint.js +6065 -5609
- package/dist/route-coverage.global-setup.d.ts +42 -0
- package/dist/route-coverage.global-setup.js +2 -0
- package/dist/services.d.ts +3 -0
- package/dist/services.global-setup.d.ts +43 -0
- package/dist/services.global-setup.js +2 -0
- package/dist/services.js +597 -0
- package/dist/shared-appium.adapter.js +428 -0
- package/dist/shared-artifacts.js +32 -0
- package/dist/shared-chapter.js +7 -0
- package/dist/shared-container.port.d.ts +31 -0
- package/dist/shared-derived-projects.js +58 -0
- package/dist/shared-device.port.d.ts +189 -0
- package/dist/shared-exchange.js +447 -0
- package/dist/shared-in-place.js +172 -0
- package/dist/shared-index.browser.js +3191 -0
- package/dist/shared-index.services.d.ts +346 -0
- package/dist/shared-index.stubs.d.ts +287 -0
- package/dist/shared-json-shape.js +17 -0
- package/dist/shared-landmarks.js +71 -0
- package/dist/shared-ledger.js +68 -0
- package/dist/shared-literate-plugin.js +183 -0
- package/dist/shared-manifest.js +1321 -0
- package/dist/shared-pause.js +11 -0
- package/dist/shared-peer.js +88 -0
- package/dist/shared-playwright.adapter.js +330 -0
- package/dist/shared-process.adapter.js +74 -0
- package/dist/shared-refusal.js +35 -0
- package/dist/shared-resolve.js +24 -0
- package/dist/shared-route-coverage.global-setup.js +187 -0
- package/dist/shared-scenario-failure.js +198 -0
- package/dist/shared-serve.js +365 -0
- package/dist/shared-service-images.js +10 -0
- package/dist/shared-services.global-setup.js +179 -0
- package/dist/shared-session-ledger.js +301 -0
- package/dist/shared-setup-entry.js +12 -0
- package/dist/shared-source-scan.js +133 -0
- package/dist/shared-spec-document.js +365 -0
- package/dist/shared-specifications.js +26 -0
- package/dist/shared-stream-body.js +130 -0
- package/dist/shared-stub-server.js +190 -0
- package/dist/shared-temp-root.global-setup.js +80 -0
- package/dist/shared-test-baseline.js +26 -0
- package/dist/shared-test-section.js +58 -0
- package/dist/shared-testcontainers.adapter.js +203 -0
- package/dist/shared-tokens.js +55 -0
- package/dist/shared-yaml-document.js +119 -0
- package/dist/stubs.d.ts +2 -0
- package/dist/stubs.js +657 -0
- package/dist/temp-root.global-setup.d.ts +7 -0
- package/dist/temp-root.global-setup.js +2 -0
- package/dist/vitest.d.ts +97 -163
- package/dist/vitest.js +374 -507
- package/docs/README.md +82 -36
- package/docs/concepts/architecture.md +118 -0
- package/docs/concepts/conventions.md +333 -0
- package/docs/guides/api-specs.md +374 -0
- package/docs/guides/cli-specs.md +564 -0
- package/docs/guides/component-specs.md +252 -0
- package/docs/guides/develop.md +94 -0
- package/docs/guides/integration-specs.md +181 -0
- package/docs/guides/job-specs.md +180 -0
- package/docs/guides/lint-specs.md +174 -0
- package/docs/guides/mobile-specs.md +319 -0
- package/docs/guides/module-tests.md +149 -0
- package/docs/guides/release.md +43 -0
- package/docs/guides/spec-documents.md +189 -0
- package/docs/guides/website-specs.md +413 -0
- package/docs/reference/assertions.md +345 -0
- package/docs/reference/glossary.md +20 -0
- package/docs/reference/linting.md +483 -0
- package/docs/reference/services.md +236 -0
- package/docs/reference/stubs.md +498 -0
- package/docs/reference/time-and-doubles.md +137 -0
- package/docs/reference/tokens.md +258 -0
- package/docs/reference/visitor.md +221 -0
- package/docs/start/getting-started.md +187 -0
- package/docs/start/set-up-a-project.md +214 -0
- package/package.json +50 -30
- package/schema/spec.schema.json +43 -17
- package/dist/appium.adapter.js +0 -489
- package/dist/artifacts.js +0 -32
- package/dist/fixtures.js +0 -110
- package/dist/literate.js +0 -1747
- package/dist/match.js +0 -804
- package/dist/playwright.adapter.js +0 -349
- package/dist/server.js +0 -160
- package/dist/stream.js +0 -321
- package/dist/substring-warning.js +0 -244
- package/docs/01-architecture.md +0 -95
- package/docs/02-developing.md +0 -351
- package/docs/03-testing.md +0 -495
- package/docs/04-operating.md +0 -101
- package/docs/05-module-tests.md +0 -152
- package/docs/06-integration.md +0 -180
- package/docs/07-component.md +0 -246
- package/docs/08-website.md +0 -404
- package/docs/09-mobile.md +0 -270
- package/docs/10-api.md +0 -264
- package/docs/11-jobs.md +0 -157
- package/docs/12-cli.md +0 -677
- package/docs/13-elements.md +0 -242
- package/docs/14-assertions.md +0 -314
- package/docs/15-tokens.md +0 -253
- package/docs/16-contracts.md +0 -438
- package/docs/17-services.md +0 -188
- package/docs/18-conventions.md +0 -305
- package/docs/19-linting.md +0 -510
- package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
- package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
- package/docs/decisions/_template.md +0 -22
- package/docs/reference/classes/Matcher.md +0 -120
- package/docs/reference/classes/ProcessHandle.md +0 -368
- package/docs/reference/functions/defineContract.md +0 -39
- package/docs/reference/functions/defineContracts.md +0 -36
- package/docs/reference/functions/disabled.md +0 -28
- package/docs/reference/functions/enabled.md +0 -20
- package/docs/reference/functions/focused.md +0 -26
- package/docs/reference/functions/mockOf.md +0 -79
- package/docs/reference/functions/postgres.md +0 -31
- package/docs/reference/functions/process.md +0 -33
- package/docs/reference/functions/redis.md +0 -28
- package/docs/reference/functions/required.md +0 -41
- package/docs/reference/functions/selected.md +0 -26
- package/docs/reference/functions/sqlite.md +0 -33
- package/docs/reference/functions/testId.md +0 -20
- package/docs/reference/functions/text.md +0 -40
- package/docs/reference/functions/valued.md +0 -28
- package/docs/reference/functions/waitUntil.md +0 -46
- package/docs/reference/functions/within.md +0 -29
- package/docs/reference/index.md +0 -196
- package/docs/reference/interfaces/BaseResult.md +0 -87
- package/docs/reference/interfaces/CallResult.md +0 -160
- package/docs/reference/interfaces/CaptureScope.md +0 -82
- package/docs/reference/interfaces/CliResult.md +0 -253
- package/docs/reference/interfaces/ContainerAccessor.md +0 -166
- package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
- package/docs/reference/interfaces/FetchResult.md +0 -180
- package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
- package/docs/reference/interfaces/HttpResult.md +0 -126
- package/docs/reference/interfaces/JsonAccessor.md +0 -46
- package/docs/reference/interfaces/PageResult.md +0 -356
- package/docs/reference/interfaces/RenderResult.md +0 -72
- package/docs/reference/interfaces/RenderedText.md +0 -110
- package/docs/reference/interfaces/ResponseAccessor.md +0 -47
- package/docs/reference/interfaces/ScreenResult.md +0 -128
- package/docs/reference/interfaces/TableAccessor.md +0 -26
- package/docs/reference/interfaces/TextAccessor.md +0 -96
- package/docs/reference/type-aliases/ApiHandle.md +0 -68
- package/docs/reference/type-aliases/ApiSpecification.md +0 -213
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
- package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
- package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
- package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
- package/docs/reference/type-aliases/BrowserPage.md +0 -132
- package/docs/reference/type-aliases/BrowserPort.md +0 -51
- package/docs/reference/type-aliases/CliEnv.md +0 -11
- package/docs/reference/type-aliases/CliHandle.md +0 -80
- package/docs/reference/type-aliases/CliInput.md +0 -36
- package/docs/reference/type-aliases/CliOutput.md +0 -46
- package/docs/reference/type-aliases/CliPort.md +0 -58
- package/docs/reference/type-aliases/CliSpecification.md +0 -144
- package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
- package/docs/reference/type-aliases/ComponentChain.md +0 -156
- package/docs/reference/type-aliases/ComponentScenario.md +0 -20
- package/docs/reference/type-aliases/ComponentUi.md +0 -47
- package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
- package/docs/reference/type-aliases/ContainerPort.md +0 -113
- package/docs/reference/type-aliases/Contract.md +0 -68
- package/docs/reference/type-aliases/ContractInput.md +0 -13
- package/docs/reference/type-aliases/ContractRequest.md +0 -95
- package/docs/reference/type-aliases/ContractResponder.md +0 -23
- package/docs/reference/type-aliases/ContractResponse.md +0 -78
- package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
- package/docs/reference/type-aliases/Contracts.md +0 -48
- package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
- package/docs/reference/type-aliases/DatabasePort.md +0 -72
- package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
- package/docs/reference/type-aliases/DevicePort.md +0 -51
- package/docs/reference/type-aliases/DeviceScreen.md +0 -32
- package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
- package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
- package/docs/reference/type-aliases/DomMount.md +0 -21
- package/docs/reference/type-aliases/ElementKind.md +0 -27
- package/docs/reference/type-aliases/ElementMatch.md +0 -74
- package/docs/reference/type-aliases/ElementOptions.md +0 -29
- package/docs/reference/type-aliases/ElementRef.md +0 -128
- package/docs/reference/type-aliases/ExecOptions.md +0 -37
- package/docs/reference/type-aliases/FileAccessor.md +0 -54
- package/docs/reference/type-aliases/HonoApp.md +0 -31
- package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
- package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
- package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
- package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
- package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
- package/docs/reference/type-aliases/Intercept.md +0 -19
- package/docs/reference/type-aliases/InterceptOptions.md +0 -28
- package/docs/reference/type-aliases/InterceptScope.md +0 -11
- package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
- package/docs/reference/type-aliases/JobHandle.md +0 -34
- package/docs/reference/type-aliases/JobsHandle.md +0 -57
- package/docs/reference/type-aliases/JobsResult.md +0 -16
- package/docs/reference/type-aliases/JobsSpecification.md +0 -94
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
- package/docs/reference/type-aliases/LandmarkKind.md +0 -20
- package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
- package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
- package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
- package/docs/reference/type-aliases/MatchableRequest.md +0 -61
- package/docs/reference/type-aliases/MatcherKind.md +0 -34
- package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
- package/docs/reference/type-aliases/MobileElementKind.md +0 -14
- package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
- package/docs/reference/type-aliases/MobileElementRef.md +0 -20
- package/docs/reference/type-aliases/MobileHandle.md +0 -65
- package/docs/reference/type-aliases/MobileScenario.md +0 -20
- package/docs/reference/type-aliases/MobileSpecification.md +0 -51
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
- package/docs/reference/type-aliases/MobileVisitor.md +0 -81
- package/docs/reference/type-aliases/MockOfOptions.md +0 -26
- package/docs/reference/type-aliases/MockPort.md +0 -10
- package/docs/reference/type-aliases/PinnedClock.md +0 -10
- package/docs/reference/type-aliases/PostgresOptions.md +0 -32
- package/docs/reference/type-aliases/ProcessOptions.md +0 -112
- package/docs/reference/type-aliases/RedisOptions.md +0 -20
- package/docs/reference/type-aliases/RenderSubject.md +0 -12
- package/docs/reference/type-aliases/ScreenNode.md +0 -67
- package/docs/reference/type-aliases/ServerPort.md +0 -36
- package/docs/reference/type-aliases/ServerResponse.md +0 -46
- package/docs/reference/type-aliases/ServerSpec.md +0 -21
- package/docs/reference/type-aliases/ServiceHandle.md +0 -216
- package/docs/reference/type-aliases/ServiceRecord.md +0 -12
- package/docs/reference/type-aliases/SpecDocument.md +0 -84
- package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
- package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
- package/docs/reference/type-aliases/SpecFixture.md +0 -30
- package/docs/reference/type-aliases/SpecKind.md +0 -8
- package/docs/reference/type-aliases/SpecRun.md +0 -108
- package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
- package/docs/reference/type-aliases/SpecStream.md +0 -32
- package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
- package/docs/reference/type-aliases/SqliteOptions.md +0 -36
- package/docs/reference/type-aliases/SseEvent.md +0 -58
- package/docs/reference/type-aliases/StreamBody.md +0 -61
- package/docs/reference/type-aliases/TextFilter.md +0 -22
- package/docs/reference/type-aliases/VisitScenario.md +0 -20
- package/docs/reference/type-aliases/Visitor.md +0 -215
- package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
- package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
- package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
- package/docs/reference/variables/anthropic.md +0 -19
- package/docs/reference/variables/banner.md +0 -10
- package/docs/reference/variables/button.md +0 -10
- package/docs/reference/variables/clock.md +0 -16
- package/docs/reference/variables/complementary.md +0 -10
- package/docs/reference/variables/component.md +0 -10
- package/docs/reference/variables/content.md +0 -10
- package/docs/reference/variables/contentinfo.md +0 -10
- package/docs/reference/variables/dialog.md +0 -10
- package/docs/reference/variables/field.md +0 -10
- package/docs/reference/variables/form.md +0 -10
- package/docs/reference/variables/heading.md +0 -10
- package/docs/reference/variables/http.md +0 -40
- package/docs/reference/variables/intercept.md +0 -11
- package/docs/reference/variables/link.md +0 -10
- package/docs/reference/variables/listitem.md +0 -10
- package/docs/reference/variables/main.md +0 -10
- package/docs/reference/variables/match.md +0 -8
- package/docs/reference/variables/navigation.md +0 -10
- package/docs/reference/variables/openai.md +0 -21
- package/docs/reference/variables/option.md +0 -17
- package/docs/reference/variables/region.md +0 -10
- package/docs/reference/variables/row.md +0 -10
- package/docs/reference/variables/search.md +0 -10
- package/docs/reference/variables/specification.md +0 -8
- package/docs/reference/variables/status.md +0 -10
- package/docs/reference/variables/table.md +0 -10
package/docs/04-operating.md
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# 04 — Operating
|
|
2
|
-
|
|
3
|
-
This repository ships one thing: the npm package `@jterrazz/test`. Nothing here deploys, nothing here runs as a service, and merging to `main` publishes nothing — a release is a deliberate, separate act. This chapter says what that act is, what leaves the tree when it happens, and what a consumer has to have for the published artefact to work.
|
|
4
|
-
|
|
5
|
-
## Releasing
|
|
6
|
-
|
|
7
|
-
A release is three steps, in this order.
|
|
8
|
-
|
|
9
|
-
1. **Bump and land the version.** The version in `package.json` moves, alone, in a commit whose subject is `chore: <version> — <what changed>`. The commit log is the changelog: this repository carries no `CHANGELOG.md`, by decision, and the release notes plus that subject line are the record.
|
|
10
|
-
2. **Create the GitHub release**, tagged at that commit. This is the trigger and the only one — `.github/workflows/release.yaml` listens on `release: types: [created]`.
|
|
11
|
-
3. **Watch the gate.** The workflow delegates to the estate's `jterrazz/jterrazz-actions/.github/workflows/release-npm.yaml`, which runs the FULL validate job first — the same build, lint and test the push gate runs, chromium included — and publishes only if it is green.
|
|
12
|
-
|
|
13
|
-
The publish step is `npm publish --access public --provenance`, on Node 24, with `id-token: write` granted so npm records a provenance attestation linking the tarball to the workflow run that built it. The registry is npm's, stated as `publishConfig` in the manifest.
|
|
14
|
-
|
|
15
|
-
Because validate runs inside the release workflow, a red suite stops a publication rather than a merge. That is the design: the tree on `main` is always publishable, and nothing decides for you when it is published.
|
|
16
|
-
|
|
17
|
-
## What leaves the tree
|
|
18
|
-
|
|
19
|
-
The tarball is what `files` declares and nothing else: `dist/`, minus `dist/catalog.*` and `dist/coverage.*`, plus `docs/` and `schema/`. The catalogue generator is a development gesture with no consumer — it writes this repository's own projections — so it is built but never shipped.
|
|
20
|
-
|
|
21
|
-
**The manual ships with the code**, because the code points at it: every relative link of the shipped `README.md` and every `(… — see docs/19-linting.md#…)` a rule message ends on is read from inside an install, and a chapter that is not there makes both a dead end. The `package-exports` meta-test holds it — a citation whose file the manifest does not publish fails the suite.
|
|
22
|
-
|
|
23
|
-
The entries that reach a consumer are the whole public contract, and they are listed once — [01 — Architecture § What the tree publishes](01-architecture.md#what-the-tree-publishes). What matters here is only that the ROOT entry resolves by CONDITION: a bundler serving a page takes `browser`, node takes `import`, and both are described by one `types` entry.
|
|
24
|
-
|
|
25
|
-
One binary ships with them: `jterrazz-test-check`, the conventions checker, pointed at `dist/checker.js`. It answers three runs — a specs tree by path, one workspace member with `--member`, and the whole project path-less (a path that is NOT a specs tree is read as a project root and takes that third run, anchored there) — and `--format json` publishes the findings under the `jterrazz-check(<id>)` codes ([19 — Linting](19-linting.md#the-member-pass-and-the-json-contract)). When `typescript check` runs it, which install it resolves it from, and what the floor version is, are the toolchain's: `@jterrazz/typescript` `docs/06-quality-checks.md` § The Test Conventions pass.
|
|
26
|
-
|
|
27
|
-
## What a consumer must bring
|
|
28
|
-
|
|
29
|
-
The package refuses to guess at its environment, so several things it uses are the consuming project's to install.
|
|
30
|
-
|
|
31
|
-
- **`vitest ^5` is the required peer**, and the only one: the framework registers its matchers into vitest, and there is no standalone runner. Why 5 alone, why the four native seams became optional peers, and where every artefact goes: [ADR-008](decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md).
|
|
32
|
-
- **Every seam is an OPTIONAL peer**, loaded lazily by the one module that owns it and NAMED at startup when it is missing: `playwright` for a page, `appium`/`webdriverio` for a screen, `@vitest/browser-playwright`, `vitest-browser-react`, `vite`, `react` and `react-dom` for a component, and the four native ones — `better-sqlite3`, `pg`, `redis`, `testcontainers` — for the services a chain declares. A project that specifies none of those installs none of them.
|
|
33
|
-
|
|
34
|
-
The refusal names the facet that asked, the peer, and the install command of the package manager the project's own lockfile names. Under pnpm a native binding also has to be listed to be BUILT, so the message adds that line:
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
sqlite() requires `better-sqlite3`, an optional peer dependency of @jterrazz/test: pnpm add -D better-sqlite3.
|
|
38
|
-
Under pnpm the binding is not built unless the package is listed: add "better-sqlite3" to `onlyBuiltDependencies`
|
|
39
|
-
in package.json (or `only-built-dependencies` in .npmrc) and reinstall.
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**A native peer has a second way of not being there**, and it is the one that costs an afternoon: the package installed, and its `.node` never compiled, because the package manager withheld the install script — npm since 11, pnpm since 10 and bun since 1.2 all do, by default. `sqlite()` opens one in-memory database the first time it loads the driver, so that failure is caught where it can be named rather than surfacing as a `bindings` stack trace from whichever spec seeded first:
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
sqlite() found `better-sqlite3` but its native binding is not built — npm does not run a
|
|
46
|
-
dependency's install script unless it is told to: npm install-scripts approve better-sqlite3
|
|
47
|
-
&& npm rebuild better-sqlite3.
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
([ADR-008](decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md) records what the four seams cost as dependencies and what the change buys under each package manager.)
|
|
51
|
-
|
|
52
|
-
- **The browser provider is pinned to the runner, patch included.** `@vitest/browser-playwright@X` peers `vitest: X` EXACTLY, so the two move together in one change or not at all. This package therefore declares the provider as `*` rather than a range of its own — a second, weaker statement of a constraint the provider already makes would be wrong the day the pair moves — and `component()` asserts the equality when the project is built, naming both versions:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm install -D vitest@5.0.1 @vitest/browser-playwright@5.0.1 # one unit, both versions
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
The peer range is `^5` and nothing else: Vitest 5 is where the artefact paths the preset routes exist, and a package holding two majors would be holding two answers about where a run writes.
|
|
59
|
-
|
|
60
|
-
- **Docker must be running** for the container-backed services. `sqlite()` and plain CLI specs need none.
|
|
61
|
-
- **Node 24 or newer**, as `engines` states — the toolchain's floor, and vitest 5's.
|
|
62
|
-
- **A chromium**, for a page or a component: `npx playwright install chromium`, once. The component facet drives the same browser the website facet does, through the same peer.
|
|
63
|
-
- **The ARIA dialect a tree golden is written in is Playwright's**, produced by its `ariaSnapshot()` and therefore PINNED by the `playwright` peer: a major of that peer can reword a role line, and every committed `.aria.yaml` moves with it in one regeneration. It is the same dialect on a page and on a mounted unit ([07 — Component specs](07-component.md), [14 — Assertions](14-assertions.md)).
|
|
64
|
-
- **A Vite the peer range names** — `^6.4 || ^7 || ^8`. `component()` reads the installed major and states the JSX default on the key that Vite transforms with (`oxc` from 8, `esbuild` before it), so a project that states no pipeline of its own gets the automatic runtime on every one of the three. This package's own suite runs on Vite 8.1; the Vite 7 path is exercised by a consumer, not by this suite — see [07 — Component specs](07-component.md).
|
|
65
|
-
|
|
66
|
-
`msw` is a direct dependency, not a peer — outgoing interception is part of the framework rather than a choice a consumer makes.
|
|
67
|
-
|
|
68
|
-
## The footprint it leaves
|
|
69
|
-
|
|
70
|
-
Nothing persistent. Inside a consuming project the framework writes only under `.artifacts/` — vite's transform cache, the coverage directory if a provider is installed, and the SQLite schema template it reuses across runs. Everything else it creates is per-RUN scratch in the OS temp directory: the fresh working directory of each CLI spec, the per-worker database copies, the profile directory a browser or a simulator needs. One `rm -rf .artifacts` is a clean slate, and [02 — Developing](02-developing.md) holds the table of what lands where.
|
|
71
|
-
|
|
72
|
-
Containers are the one resource that outlives a process badly, which is why `afterAll(cleanup)` is a rule and not a suggestion: a suite that forgets it leaks its infrastructure into the next one.
|
|
73
|
-
|
|
74
|
-
## What a 16.0 adoption meets
|
|
75
|
-
|
|
76
|
-
The renames and the new rows land through the toolchain's own flow, with three
|
|
77
|
-
things to know before they surprise a reader:
|
|
78
|
-
|
|
79
|
-
- **A member whose ROOT is the specs tree** (`packages: ['specs']`) is judged by
|
|
80
|
-
`jterrazz-test-check .` or by a path-less run from that member. The
|
|
81
|
-
toolchain's own walk looks for a CHILD directory named `specs`, so until it
|
|
82
|
-
makes the same move its Test Conventions pass reports the member findings
|
|
83
|
-
alone — `TODO.md` carries the ask.
|
|
84
|
-
- **An include entry that names `.test.ts`** stops collecting the moment `--fix`
|
|
85
|
-
renames a facet spec. The mover names the ones it BROKE — a glob whose prefix
|
|
86
|
-
covers a file it moved, a literal naming one, and never a comment — so a
|
|
87
|
-
notice is always about a file that just moved; update them in the same commit,
|
|
88
|
-
and E7w holds the class afterwards.
|
|
89
|
-
- **`typescript baseline` in an npm-hoisted member** cannot resolve oxlint
|
|
90
|
-
today, so the step the adopt flow prescribes "where red" has to be run from a
|
|
91
|
-
directory where `node_modules/.bin/oxlint` does resolve. Also in `TODO.md`.
|
|
92
|
-
|
|
93
|
-
## Pitfalls
|
|
94
|
-
|
|
95
|
-
- **Pushing the bump and expecting a publish.** `main` is not a release trigger. Without a created GitHub release, the version on npm does not move.
|
|
96
|
-
- **Releasing from a red tree.** The workflow will refuse, and it refuses AFTER the tag exists — so the tag has to be re-cut once the fix lands.
|
|
97
|
-
- **Adding a file to the tarball by putting it in the repository.** Only what `files` names ships. A new published asset is a manifest change, and a deliberate one.
|
|
98
|
-
|
|
99
|
-
## Related
|
|
100
|
-
|
|
101
|
-
[01 — Architecture](01-architecture.md) · [02 — Developing](02-developing.md) · [03 — Testing](03-testing.md)
|
package/docs/05-module-tests.md
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# 05 — Module tests (`*.test.ts`, beside the module)
|
|
2
|
-
|
|
3
|
-
A module test is the majority kind: one file of source code, met through its own exports, with nothing started. It needs no constructor, no folder and no runner — it is the rung the doubles ladder opens on, and the one every other chapter measures itself against.
|
|
4
|
-
|
|
5
|
-
| The shape | Held below |
|
|
6
|
-
| ------------------- | ------------------------------------------------------------------ |
|
|
7
|
-
| What it specifies | [What it specifies](#what-it-specifies) |
|
|
8
|
-
| Where it is written | [The constructor: there is none](#the-constructor-there-is-none) |
|
|
9
|
-
| The chain | [The chain: there is none either](#the-chain-there-is-none-either) |
|
|
10
|
-
| What it may hold | [What it may and may not do](#what-it-may-and-may-not-do) |
|
|
11
|
-
| The result | [The result is the return value](#the-result-is-the-return-value) |
|
|
12
|
-
| What only it does | [Unique here](#unique-here) |
|
|
13
|
-
| What it will refuse | [Pitfalls](#pitfalls) |
|
|
14
|
-
|
|
15
|
-
## What it specifies
|
|
16
|
-
|
|
17
|
-
A module test answers one question: **given these inputs and these ports, what does this module return, and what does it ask of its collaborators?** Its subject is a unit of code, so it sits beside that code:
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
src/domain/posts/
|
|
21
|
-
├── ranking.ts
|
|
22
|
-
├── ranking.test.ts # the module's spec — the `.test.ts` suffix IS the kind
|
|
23
|
-
└── ranking.fixtures.ts # the payloads it feeds itself, if any
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
The suffix decides which project collects the file and therefore which rules judge it. `unit()` takes `**/*.test.ts` outside `specs/` and excludes `**/*.test.tsx`; a `.test.ts` UNDER `specs/<facet>/` is rule C12's error, with the fix "rename it `.spec.ts`", and a rendered thing in a `.test.ts` is rule G4's, with the fix "rename it `.test.tsx`" ([07 — Component specs](07-component.md)).
|
|
27
|
-
|
|
28
|
-
Nothing in this kind is slow, so the `unit` project is the one every developer runs on every save: this package gates its own at twelve seconds ([03 — Testing](03-testing.md#the-seven-projects)).
|
|
29
|
-
|
|
30
|
-
## The constructor: there is none
|
|
31
|
-
|
|
32
|
-
Every facet below this chapter starts something — a server, a database, a binary, a browser, a simulator — so every one of them has a `*.specification.ts` holding what was started. A module starts nothing. This kind has no folder under `specs/` at all — no specification file, and no project helper to configure beyond `unit()`.
|
|
33
|
-
|
|
34
|
-
That is the fork's whole criterion, and it runs both ways: the moment a test needs a real service, a real binary or a real screen, it is no longer this kind, and the chapter that owns it says which one it became ([18 — Conventions § The fork](18-conventions.md#the-fork--which-kind-of-test-this-is)).
|
|
35
|
-
|
|
36
|
-
## The chain: there is none either
|
|
37
|
-
|
|
38
|
-
A module test is plain vitest: `test`, `expect`, the native matchers. What the framework adds are four primitives it may reach for by name — and each of them is a scope that gives back what it took.
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { clock } from '@jterrazz/test';
|
|
42
|
-
import { expect, test } from 'vitest';
|
|
43
|
-
|
|
44
|
-
import { twoPosts } from './ranking.fixtures.js';
|
|
45
|
-
import { rank } from './ranking.js';
|
|
46
|
-
|
|
47
|
-
test('ranks a fresher post above an older one of equal score', () => {
|
|
48
|
-
// Given - two posts of equal score, one written this morning
|
|
49
|
-
using _ = clock.at('2026-03-04T09:30:00Z');
|
|
50
|
-
|
|
51
|
-
// Then - the fresher one leads
|
|
52
|
-
expect(rank(twoPosts).map((post) => post.id)).toStrictEqual(['fresh', 'stale']);
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## What it may and may not do
|
|
57
|
-
|
|
58
|
-
The table is the rule set, and every row names the rule that holds it ([19 — Linting](19-linting.md)).
|
|
59
|
-
|
|
60
|
-
| May | May not | Rule |
|
|
61
|
-
| ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
|
|
62
|
-
| import its module and pure helpers; a sibling `<file>.fixtures.ts` | import a data asset (`.json` / `.txt` / `.sql` / `.yaml`); keep a `__mocks__/` or `__fixtures__/` directory | I4 |
|
|
63
|
-
| `mockOf<Port>()` / `mockOf<Port>({ deep: false })` for an injected interface; `vi.fn<Fn>()`; `vi.spyOn` on an object passed in | `vi.mock` / `vi.doMock`, except a specifier on the consumer's `modules` allow-list with the reason written beside it | I4 |
|
|
64
|
-
| `await using _ = await intercept(http.get(url), http.json(…))` | `vi.stubGlobal('fetch')`, `msw/node`, `nock` | M3, F6 |
|
|
65
|
-
| `using _ = clock.at('2026-03-04T09:30:00Z')`; `using _ = clock.run()`; `await clock.advance(ms)` | `Date.now()`, zero-argument `new Date()`, `Math.random()`, `randomUUID()` reaching an oracle; `vi.useFakeTimers` / `vi.setSystemTime` | D16/D16w, M3 |
|
|
66
|
-
| `vi.stubEnv('X', 'y')` for a module whose contract IS an env read — it restores itself | `process.env.X = …`, a raw assignment nothing gives back | E9w |
|
|
67
|
-
| flat `test()`; one level of `describe` for grouping; a `test.each` table (B4 judges the table once, not once per row) | a nested `describe`; a `beforeEach` / `beforeAll` that builds the Given | J10, J6w |
|
|
68
|
-
| `expect(text(value)).toContain(…)`; `toStrictEqual` against a literal | a golden (`toMatch('<file>')`) — a module with a golden is an integration spec ([06](06-integration.md)); any `toMatchSnapshot` matcher | D20 |
|
|
69
|
-
| — | `document` / `window` / `navigator`; `@vitest-environment happy-dom`; `react-dom/server` | G4, E5, F6 |
|
|
70
|
-
|
|
71
|
-
A module that fetches a RELATIVE url is browser code and cannot run under node as it stands: either state where it resolves (`intercept(contracts, { origin })`, below) or specify it where it renders — a component spec.
|
|
72
|
-
|
|
73
|
-
### Doubles
|
|
74
|
-
|
|
75
|
-
`mockOf<Port>()` is the fourth rung of the ladder and the only one a module test builds itself. It is deep by default: reading a member nobody stubbed hands back another double, however far the port nests. `{ deep: false }` gives the flat proxy — every member of the port and nothing beneath them, which is what a port with a nested namespace wants when the test only ever touches the first level.
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
const gateway = mockOf<PaymentGateway>();
|
|
79
|
-
const settings = mockOf<Settings>({ deep: false });
|
|
80
|
-
const onDone = vi.fn<() => void>();
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Everything else the ladder refuses is refused here by name: `vi.mock` doubles a module for everyone, so it is I4's error unless a native module a consumer cannot inject earns an allow-list entry with its reason — matched in either spelling, `vi.mock('<spec>')` and the `vi.mock(import('<spec>'))` form `vitest/prefer-import-in-mock` asks for, so the two rules have a spelling they both accept; `vi.stubGlobal` is the network replaced by a function the test wrote, so it is M3's ([18 — Conventions § The doubles ladder](18-conventions.md#the-doubles-ladder)).
|
|
84
|
-
|
|
85
|
-
### `intercept()` — the network, declared, with no chain
|
|
86
|
-
|
|
87
|
-
A module that reaches the network states what it expects to find there, in the same contract vocabulary every facet uses — the same double, with no chain to hang it on.
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
test('reads the feed the gateway publishes', async () => {
|
|
91
|
-
// Given - the feed answers with two posts
|
|
92
|
-
await using _ = await intercept(
|
|
93
|
-
http.get('https://console.test/api/posts'),
|
|
94
|
-
http.json({ posts: [{ id: 'fresh' }, { id: 'stale' }] }),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// Then - the client hands back what the feed carried
|
|
98
|
-
await expect(fetchPosts()).resolves.toHaveLength(2);
|
|
99
|
-
});
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The two awaits, `{ origin }` for a subject that calls a RELATIVE url, the refusal of an empty contract list, and everything about the contracts themselves — selection, `times`, `required`, the facades, the streams — are [16 — Contracts § `intercept()`](16-contracts.md#intercept--the-same-double-with-no-chain)'s.
|
|
103
|
-
|
|
104
|
-
### Time
|
|
105
|
-
|
|
106
|
-
`clock.at(iso)` pins `Date` and nothing else, so a subject that awaits, polls or renders keeps running. `clock.run(iso?)` takes the scheduler too — `setTimeout` and friends queue instead of firing — and `await clock.advance(ms)` is what makes them due; that is the one primitive for a subject whose behaviour IS elapsed time (a debounce, a retry backoff), where waiting for real would be the arbitrary sleep J2 forbids. One clock per test: a second scope taken inside the first is refused rather than nested, because ending the inner one would silently end the outer one too.
|
|
107
|
-
|
|
108
|
-
### Fixtures
|
|
109
|
-
|
|
110
|
-
A payload a module test feeds itself lives in a sibling `<file>.fixtures.ts` — TypeScript, type-checked with the test, reviewed in the same diff. A `.json` beside a module test is I4's error: an asset the compiler never reads is a second source of truth, and the kind that legitimately owns assets is the one that has a folder for them.
|
|
111
|
-
|
|
112
|
-
### Tables
|
|
113
|
-
|
|
114
|
-
`test.each` is the form for a rule with many cases, and B4 judges the table ONCE — the Given/Then narration is written on the table, not repeated in every row.
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
test.each([
|
|
118
|
-
{ expected: 'fresh', label: 'a fresher post of equal score', posts: equalScore },
|
|
119
|
-
{ expected: 'high', label: 'a higher score of equal age', posts: equalAge },
|
|
120
|
-
])(
|
|
121
|
-
// Given - $label
|
|
122
|
-
// Then - it leads the ranking
|
|
123
|
-
'ranks $label first',
|
|
124
|
-
({ expected, posts }) => {
|
|
125
|
-
expect(rank(posts)[0]?.id).toBe(expected);
|
|
126
|
-
},
|
|
127
|
-
);
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## The result is the return value
|
|
131
|
-
|
|
132
|
-
There is no result object here, because there is no chain to hand one back. The subject is the module's own return value, its thrown error, or a call recorded on a port double — and those are vitest's native matchers' to judge ([14 — Assertions](14-assertions.md#scalars--native-expect)). `text(value)` is the one helper that borrows the framework's stream vocabulary for a plain string.
|
|
133
|
-
|
|
134
|
-
## Unique here
|
|
135
|
-
|
|
136
|
-
- **It is the only kind with no folder.** Every other kind is collected by a path (`specs/<facet>/`); this one is collected by a suffix, beside what it specifies. Moving a module test away from its module is C18's error in one direction and C12's in the other.
|
|
137
|
-
- **It is the only kind that may build its own double.** Below this chapter a double is DECLARED — a contract, a service, a seeded row — because the subject is an assembled product and the test does not hold its wiring. A module test is handed the wiring, so `mockOf` is in its hands.
|
|
138
|
-
- **It is the only kind with no golden.** A golden is a file, and a file beside a module test is an asset; a module whose answer deserves a golden is describing something assembled, and the integration facet is where that goes ([06](06-integration.md#the-golden-half--a-pure-module-no-services-at-all)).
|
|
139
|
-
- **It is the kind the budget is written for.** The `unit` project is the fast one, and this package gates its own at twelve seconds so the loop stays a loop.
|
|
140
|
-
|
|
141
|
-
## Pitfalls
|
|
142
|
-
|
|
143
|
-
- **Reaching for `vi.mock` to avoid injecting a port.** The mock outlives the test's intent: every importer of that specifier gets the double, and the design pressure the injection would have applied is gone. Double the port.
|
|
144
|
-
- **`vi.setSystemTime` instead of `clock`.** A fake timer taken without `using` outlasts the test that took it, and the next test in the file inherits a clock nobody declared. M3 refuses it and names the primitive.
|
|
145
|
-
- **Importing a `.json` payload.** I4 refuses it. Put the payload in `<file>.fixtures.ts`, where the compiler reads it.
|
|
146
|
-
- **Nesting `describe`.** One level groups; two describe a structure the test file does not have. J10 refuses the second.
|
|
147
|
-
- **Asserting on a DOM.** There is no document here, and there will not be one: G4 refuses the globals, E5 refuses the `@vitest-environment` comment that would conjure them, and the route is a `.test.tsx` in a real browser.
|
|
148
|
-
- **Letting the Given drift into `beforeEach`.** A Given nobody can read in the test is a Given nobody maintains. J6w says so.
|
|
149
|
-
|
|
150
|
-
## Related
|
|
151
|
-
|
|
152
|
-
[02 — Developing](02-developing.md) · [03 — Testing](03-testing.md) · [06 — Integration specs](06-integration.md) · [07 — Component specs](07-component.md) · [14 — Assertions](14-assertions.md) · [15 — Tokens](15-tokens.md) · [16 — Contracts](16-contracts.md) · [18 — Conventions](18-conventions.md) · [19 — Linting](19-linting.md)
|
package/docs/06-integration.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# 06 — Integration specs: a module against the real thing
|
|
2
|
-
|
|
3
|
-
`specification.integration()` specifies a MODULE — not an entry. No HTTP, no binary, no page: the subject is a function, and it is called. What makes it a spec rather than a module test beside its code is what it stands on — a real database, a declared contract, a golden file — and that is exactly the fork this facet closes.
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
// specs/integration/integration.specification.ts
|
|
7
|
-
import { postgres, specification } from '@jterrazz/test';
|
|
8
|
-
import { afterAll } from 'vitest';
|
|
9
|
-
|
|
10
|
-
export const { cleanup, integration } = await specification.integration({
|
|
11
|
-
services: { db: postgres() },
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterAll(cleanup);
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
// specs/integration/orders/orders.spec.ts
|
|
19
|
-
import { expect, test } from 'vitest';
|
|
20
|
-
|
|
21
|
-
import { listOrders } from '../../../src/orders/orders.js';
|
|
22
|
-
import { integration } from '../integration.specification.js';
|
|
23
|
-
|
|
24
|
-
test('reads the orders the day left behind', async () => {
|
|
25
|
-
// Given - two orders in the real database
|
|
26
|
-
const result = await integration
|
|
27
|
-
.seed('two-orders.sql')
|
|
28
|
-
.call(async ({ db }) => await listOrders(db.connectionString));
|
|
29
|
-
|
|
30
|
-
// Then - the module answered with both
|
|
31
|
-
expect(result.value).toMatch('two-orders.json');
|
|
32
|
-
});
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## What it specifies
|
|
36
|
-
|
|
37
|
-
Three questions decide, in order ([18 — Conventions](18-conventions.md)):
|
|
38
|
-
|
|
39
|
-
1. Does the subject answer HTTP, run as a binary, render a page or a screen? Then it is that facet's, not this one.
|
|
40
|
-
2. Does it need a real service — a database, a cache, a process — or is its oracle a GOLDEN file? Then it is an integration spec.
|
|
41
|
-
3. Otherwise it is a module test, `<file>.test.ts` beside `<file>.ts`, with no runner at all.
|
|
42
|
-
|
|
43
|
-
The second question has two halves and both land here. A module against real infrastructure was, before this facet, a `specs/` folder running plain vitest with a hand-built sqlite template and a `beforeAll` starting a container. A module whose oracle is a golden was a `toMatchSnapshot` — a second golden mechanism beside the one the package owns. Neither needed a new idea; both needed a constructor.
|
|
44
|
-
|
|
45
|
-
## The constructor
|
|
46
|
-
|
|
47
|
-
| Option | Means |
|
|
48
|
-
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
49
|
-
| `services` | the named record the module is specified against — `postgres()`, `redis()`, `sqlite()`, `process()`. Omit it for the golden-only half |
|
|
50
|
-
| `root` | project-root override (rule A9), auto-discovered from the calling file |
|
|
51
|
-
|
|
52
|
-
The handle is `{ integration, cleanup }`, destructured with those names (rule A3) and cleaned up with `afterAll(cleanup)` (rule A4).
|
|
53
|
-
|
|
54
|
-
## The chain
|
|
55
|
-
|
|
56
|
-
Setups: `.seed()`, `.intercept()`, `.clock()`. Terminal action: `.call(subject)` (rule B2).
|
|
57
|
-
|
|
58
|
-
| Setup | Means |
|
|
59
|
-
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
60
|
-
| `.seed('rows.sql')` | run `_seeds/rows.sql` before the call; `{ database }` targets one of several |
|
|
61
|
-
| `.intercept(contracts)` | declare what the outside world replies ([16](16-contracts.md)) |
|
|
62
|
-
| `.clock('2026-03-04T09:30:00Z')` | pin the module's `Date` for this chain ([18](18-conventions.md#time--one-primitive-two-depths)) |
|
|
63
|
-
|
|
64
|
-
`.call(subject)` hands the STARTED services record to the subject, so the module is built with the real connection strings rather than with a double:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
const result = await integration.call(async ({ db }) => await listOrders(db.connectionString));
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Databases reset at the start of every chain, exactly as on api and jobs (rules B1, B7): one spec is one call, and no spec depends on a previous one.
|
|
71
|
-
|
|
72
|
-
## The result
|
|
73
|
-
|
|
74
|
-
Two readings, never both.
|
|
75
|
-
|
|
76
|
-
| Accessor | Is |
|
|
77
|
-
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
78
|
-
| `result.value` | what the call RETURNED — a `TextAccessor` for a string, a `JsonAccessor<T>` for anything else, the union of both for a `T` that may be either |
|
|
79
|
-
| `result.error` | what it THREW, as text: an `Error`'s message, or the value itself. Empty when it returned |
|
|
80
|
-
| `result.table()` | a table of a declared database, for row-level assertions |
|
|
81
|
-
|
|
82
|
-
A refusal is a reading, never a `try`/`catch`:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
test('refuses an order that is not one', async () => {
|
|
86
|
-
// Given - the module asked to record an order worth nothing
|
|
87
|
-
const result = await integration.call(
|
|
88
|
-
async ({ db }) => await placeOrder(db.connectionString, { reference: 'ORD-4', total: 0 }),
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Then - what it threw is the reading, and nothing was written
|
|
92
|
-
expect(result.error).toMatch('refused.txt');
|
|
93
|
-
await expect(result.table('orders')).toBeEmpty();
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
That is what keeps a spec of a refusal the same size as a spec of a success, and what stops an "it should throw" spec from passing when nothing throws at all — `await expect(result.error).toBeEmpty()` is how a chain says "and it did not refuse". The `await` is not optional: `toBeEmpty` answers a promise on every subject it takes, a dropped one passes the test while the real failure surfaces as an unhandled rejection, and rule D2 refuses the bare form.
|
|
98
|
-
|
|
99
|
-
`.call<T>()` carries `T` through to the result, so one field is read where one field is what the spec means — `expect(result.value.value.ok).toBe(true)` for JSON, `result.value.text` for a string — and a golden file is kept for what a golden file is for: a shape worth freezing whole.
|
|
100
|
-
|
|
101
|
-
**The accessor follows the value, and the type says so.** A subject typed exactly `string` reads as a `TextAccessor`; one that can never be a string reads as a `JsonAccessor<T>`; one that MAY be a string (`string | undefined`, `string | number`) is typed as the union of the two, and the spec narrows it — because only the value in hand decides, and a type that guessed handed back a `JsonAccessor` whose `.value` was silently `undefined` while the runtime had built a `TextAccessor`.
|
|
102
|
-
|
|
103
|
-
**What crosses is JSON, not the object.** A non-string value is serialised, so a `Map`, a `Set`, a `Date` or a class instance arrives as the plain JSON it serialises to — `{}`, `{}`, an ISO string, an object of its own fields — while `T` still claims the original type. A call whose answer is one of those projects it to plain data INSIDE the call, where the projection is visible:
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
// the Map is projected where the spec can see it, not silently at the seam
|
|
107
|
-
const result = await integration.call(async () => Object.fromEntries(await loadIndex()));
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Both accessors are the package's ordinary subjects, so the golden mechanism reaches them whole: `toMatch('<name>.json'|'<name>.txt')` under `_expected/`, the `{{token}}` grammar for what moves, `{ frozen }`, and `TEST_UPDATE=1` ([15](15-tokens.md), [14](14-assertions.md)).
|
|
111
|
-
|
|
112
|
-
A call that a declared contract never accepted fails the CHAIN rather than becoming `result.error`: a refusal the module chose is the subject's behaviour, an undeclared outgoing call is the spec's own mistake (rule D7).
|
|
113
|
-
|
|
114
|
-
## Unique here
|
|
115
|
-
|
|
116
|
-
### The golden half — a pure module, no services at all
|
|
117
|
-
|
|
118
|
-
`specification.integration()` with no options is the pure-module runner: nothing starts, and what earns the folder is the golden.
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
export const { cleanup, integration } = await specification.integration();
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
A table of cases is one `.call()` inside `test.each`, which is what turns fifty-three hand-paired fixture files into one spec:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
test.each(CASES)('renders $name', async ({ name, input }) => {
|
|
128
|
-
// Given - one case of the table
|
|
129
|
-
const result = await integration.call(() => render(input));
|
|
130
|
-
|
|
131
|
-
// Then - the golden for that case
|
|
132
|
-
expect(result.value).toMatch(`${name}.json`);
|
|
133
|
-
await expect(result.error).toBeEmpty();
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
The golden is named by a template literal, and the checker's C9 dead-fixture pass reads that form: a fixture whose name matches the literal's static ends is one the table could have asked for, so a wall of `_expected/` files stays alive without a hand-written literal each.
|
|
138
|
-
|
|
139
|
-
### The `integration()` project
|
|
140
|
-
|
|
141
|
-
`integration()` from `@jterrazz/test/vitest` is the canonical project: it collects `specs/integration/**/*.spec.ts`, carries the preset's budgets and artefact directory, and runs in group 0 with the other node facets.
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
import { defineSpecConfig, integration, unit } from '@jterrazz/test/vitest';
|
|
145
|
-
|
|
146
|
-
export default defineSpecConfig({ test: { projects: [unit(), integration()] } });
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
An env-gated sub-suite is an option, not a second project — the whole point of `{ include, exclude, timeout }`:
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
integration({
|
|
153
|
-
exclude: process.env.E2E_NETWORK ? [] : ['specs/integration/network/**'],
|
|
154
|
-
});
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Folder layout
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
specs/integration/
|
|
161
|
-
├── integration.specification.ts # the runner, at the facet root
|
|
162
|
-
├── pure.specification.ts # a second runner: no services, goldens only
|
|
163
|
-
└── orders/ # a domain
|
|
164
|
-
├── orders.spec.ts # under specs/, the suffix is .spec.ts (rule C12)
|
|
165
|
-
├── _seeds/two-orders.sql # ground: database state
|
|
166
|
-
└── _expected/ # ground: every expected fixture, flat
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
**`required` is verified per `.call()`.** A chain's scope is ONE terminal action: the contracts it declared are checked when that call ends, and a contract declared `required` that was never requested fails there. A second `.call()` is a second chain with a second verification — it does not inherit the first's obligations.
|
|
170
|
-
|
|
171
|
-
## Pitfalls
|
|
172
|
-
|
|
173
|
-
- **Reaching for `try`/`catch`.** A thrown error is `result.error`. A `try` around `.call()` brings back the shape where a spec passes because nothing threw.
|
|
174
|
-
- **Writing a module test here.** A module alone, with no service and no golden, belongs beside its code as `<file>.test.ts` (rule I2). The folder is not a home for tests that were hard to place.
|
|
175
|
-
- **Goldening a volatile value.** An id, a timestamp or a duration inside `result.value` is tokened (`{{int}}`, `{{iso8601}}`), or pinned with `.clock()`. A literal that moves is a test that fails tomorrow (rule D16).
|
|
176
|
-
- **Asserting on a double.** The facet exists so the subject meets the real thing. A `mockOf<T>()` inside `.call()` is a module test that started a container for nothing.
|
|
177
|
-
|
|
178
|
-
## Related
|
|
179
|
-
|
|
180
|
-
[10 — API specs](10-api.md) · [14 — Assertions](14-assertions.md) · [16 — Contracts](16-contracts.md) · [17 — Services](17-services.md) · [18 — Conventions](18-conventions.md)
|