@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.
Files changed (222) hide show
  1. package/README.md +120 -427
  2. package/dist/appium.adapter.js +71 -15
  3. package/dist/artifacts.js +25 -1735
  4. package/dist/browser/index.js +304 -233
  5. package/dist/checker.d.ts +1 -1
  6. package/dist/checker.js +1103 -97
  7. package/dist/fixtures.js +110 -0
  8. package/dist/index.d.ts +956 -971
  9. package/dist/index.js +2159 -2281
  10. package/dist/literate.js +1747 -0
  11. package/dist/match.js +625 -84
  12. package/dist/oxlint.cjs +3585 -1480
  13. package/dist/oxlint.d.cts +60 -48
  14. package/dist/oxlint.d.ts +60 -48
  15. package/dist/oxlint.js +3585 -1480
  16. package/dist/playwright.adapter.js +51 -8
  17. package/dist/{intercept.js → server.js} +2 -2
  18. package/dist/stream.js +6 -6
  19. package/dist/{ambiguity.js → substring-warning.js} +106 -7
  20. package/dist/vitest.d.ts +67 -48
  21. package/dist/vitest.js +229 -93
  22. package/docs/01-architecture.md +95 -0
  23. package/docs/02-developing.md +351 -0
  24. package/docs/03-testing.md +495 -0
  25. package/docs/04-operating.md +101 -0
  26. package/docs/05-module-tests.md +152 -0
  27. package/docs/06-integration.md +180 -0
  28. package/docs/07-component.md +246 -0
  29. package/docs/08-website.md +404 -0
  30. package/docs/09-mobile.md +270 -0
  31. package/docs/10-api.md +264 -0
  32. package/docs/11-jobs.md +157 -0
  33. package/docs/12-cli.md +677 -0
  34. package/docs/13-elements.md +242 -0
  35. package/docs/14-assertions.md +314 -0
  36. package/docs/15-tokens.md +253 -0
  37. package/docs/16-contracts.md +438 -0
  38. package/docs/17-services.md +188 -0
  39. package/docs/18-conventions.md +305 -0
  40. package/docs/19-linting.md +510 -0
  41. package/docs/README.md +36 -0
  42. package/docs/decisions/001-v9-conventions-on-three-channels.md +50 -0
  43. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +49 -0
  44. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +84 -0
  45. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +81 -0
  46. package/docs/decisions/005-the-doubles-ladder-is-closed.md +86 -0
  47. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +245 -0
  48. package/docs/decisions/007-compose-mode-leaves-the-package.md +53 -0
  49. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +75 -0
  50. package/docs/decisions/_template.md +22 -0
  51. package/docs/reference/classes/Matcher.md +120 -0
  52. package/docs/reference/classes/ProcessHandle.md +368 -0
  53. package/docs/reference/functions/defineContract.md +39 -0
  54. package/docs/reference/functions/defineContracts.md +36 -0
  55. package/docs/reference/functions/disabled.md +28 -0
  56. package/docs/reference/functions/enabled.md +20 -0
  57. package/docs/reference/functions/focused.md +26 -0
  58. package/docs/reference/functions/mockOf.md +79 -0
  59. package/docs/reference/functions/postgres.md +31 -0
  60. package/docs/reference/functions/process.md +33 -0
  61. package/docs/reference/functions/redis.md +28 -0
  62. package/docs/reference/functions/required.md +41 -0
  63. package/docs/reference/functions/selected.md +26 -0
  64. package/docs/reference/functions/sqlite.md +33 -0
  65. package/docs/reference/functions/testId.md +20 -0
  66. package/docs/reference/functions/text.md +40 -0
  67. package/docs/reference/functions/valued.md +28 -0
  68. package/docs/reference/functions/waitUntil.md +46 -0
  69. package/docs/reference/functions/within.md +29 -0
  70. package/docs/reference/index.md +196 -0
  71. package/docs/reference/interfaces/BaseResult.md +87 -0
  72. package/docs/reference/interfaces/CallResult.md +160 -0
  73. package/docs/reference/interfaces/CaptureScope.md +82 -0
  74. package/docs/reference/interfaces/CliResult.md +253 -0
  75. package/docs/reference/interfaces/ContainerAccessor.md +166 -0
  76. package/docs/reference/interfaces/DirectoryAccessor.md +33 -0
  77. package/docs/reference/interfaces/FetchResult.md +180 -0
  78. package/docs/reference/interfaces/FilesystemAccessor.md +46 -0
  79. package/docs/reference/interfaces/HttpResult.md +126 -0
  80. package/docs/reference/interfaces/JsonAccessor.md +46 -0
  81. package/docs/reference/interfaces/PageResult.md +356 -0
  82. package/docs/reference/interfaces/RenderResult.md +72 -0
  83. package/docs/reference/interfaces/RenderedText.md +110 -0
  84. package/docs/reference/interfaces/ResponseAccessor.md +47 -0
  85. package/docs/reference/interfaces/ScreenResult.md +128 -0
  86. package/docs/reference/interfaces/TableAccessor.md +26 -0
  87. package/docs/reference/interfaces/TextAccessor.md +96 -0
  88. package/docs/reference/type-aliases/ApiHandle.md +68 -0
  89. package/docs/reference/type-aliases/ApiSpecification.md +213 -0
  90. package/docs/reference/type-aliases/ApiSpecificationOptions.md +68 -0
  91. package/docs/reference/type-aliases/BrowserConsoleMessage.md +30 -0
  92. package/docs/reference/type-aliases/BrowserLinkElement.md +50 -0
  93. package/docs/reference/type-aliases/BrowserMetaElement.md +40 -0
  94. package/docs/reference/type-aliases/BrowserOpenOptions.md +87 -0
  95. package/docs/reference/type-aliases/BrowserPage.md +132 -0
  96. package/docs/reference/type-aliases/BrowserPort.md +51 -0
  97. package/docs/reference/type-aliases/CliEnv.md +11 -0
  98. package/docs/reference/type-aliases/CliHandle.md +80 -0
  99. package/docs/reference/type-aliases/CliInput.md +36 -0
  100. package/docs/reference/type-aliases/CliOutput.md +46 -0
  101. package/docs/reference/type-aliases/CliPort.md +58 -0
  102. package/docs/reference/type-aliases/CliSpecification.md +144 -0
  103. package/docs/reference/type-aliases/CliSpecificationOptions.md +134 -0
  104. package/docs/reference/type-aliases/ComponentChain.md +156 -0
  105. package/docs/reference/type-aliases/ComponentScenario.md +20 -0
  106. package/docs/reference/type-aliases/ComponentUi.md +47 -0
  107. package/docs/reference/type-aliases/ComponentVisitor.md +10 -0
  108. package/docs/reference/type-aliases/ContainerPort.md +113 -0
  109. package/docs/reference/type-aliases/Contract.md +68 -0
  110. package/docs/reference/type-aliases/ContractInput.md +13 -0
  111. package/docs/reference/type-aliases/ContractRequest.md +95 -0
  112. package/docs/reference/type-aliases/ContractResponder.md +23 -0
  113. package/docs/reference/type-aliases/ContractResponse.md +78 -0
  114. package/docs/reference/type-aliases/ContractResponseValue.md +13 -0
  115. package/docs/reference/type-aliases/Contracts.md +48 -0
  116. package/docs/reference/type-aliases/DatabaseKeys.md +16 -0
  117. package/docs/reference/type-aliases/DatabasePort.md +72 -0
  118. package/docs/reference/type-aliases/DeviceOpenOptions.md +46 -0
  119. package/docs/reference/type-aliases/DevicePort.md +51 -0
  120. package/docs/reference/type-aliases/DeviceScreen.md +32 -0
  121. package/docs/reference/type-aliases/DeviceTimeouts.md +39 -0
  122. package/docs/reference/type-aliases/DockerSpecConfig.md +43 -0
  123. package/docs/reference/type-aliases/DomMount.md +21 -0
  124. package/docs/reference/type-aliases/ElementKind.md +27 -0
  125. package/docs/reference/type-aliases/ElementMatch.md +74 -0
  126. package/docs/reference/type-aliases/ElementOptions.md +29 -0
  127. package/docs/reference/type-aliases/ElementRef.md +128 -0
  128. package/docs/reference/type-aliases/ExecOptions.md +37 -0
  129. package/docs/reference/type-aliases/FileAccessor.md +54 -0
  130. package/docs/reference/type-aliases/HonoApp.md +31 -0
  131. package/docs/reference/type-aliases/HttpContractFilter.md +49 -0
  132. package/docs/reference/type-aliases/HttpResponseInit.md +46 -0
  133. package/docs/reference/type-aliases/HttpStreamInit.md +58 -0
  134. package/docs/reference/type-aliases/IntegrationHandle.md +46 -0
  135. package/docs/reference/type-aliases/IntegrationSpecification.md +111 -0
  136. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +42 -0
  137. package/docs/reference/type-aliases/Intercept.md +19 -0
  138. package/docs/reference/type-aliases/InterceptOptions.md +28 -0
  139. package/docs/reference/type-aliases/InterceptScope.md +11 -0
  140. package/docs/reference/type-aliases/IsolationStrategy.md +75 -0
  141. package/docs/reference/type-aliases/JobHandle.md +34 -0
  142. package/docs/reference/type-aliases/JobsHandle.md +57 -0
  143. package/docs/reference/type-aliases/JobsResult.md +16 -0
  144. package/docs/reference/type-aliases/JobsSpecification.md +94 -0
  145. package/docs/reference/type-aliases/JobsSpecificationOptions.md +56 -0
  146. package/docs/reference/type-aliases/LandmarkKind.md +20 -0
  147. package/docs/reference/type-aliases/LiterateRunFlags.md +27 -0
  148. package/docs/reference/type-aliases/LiterateServeRegistration.md +70 -0
  149. package/docs/reference/type-aliases/MatchFixtureOptions.md +27 -0
  150. package/docs/reference/type-aliases/MatchableRequest.md +61 -0
  151. package/docs/reference/type-aliases/MatcherKind.md +34 -0
  152. package/docs/reference/type-aliases/MobileBackendOptions.md +28 -0
  153. package/docs/reference/type-aliases/MobileElementKind.md +14 -0
  154. package/docs/reference/type-aliases/MobileElementMatch.md +60 -0
  155. package/docs/reference/type-aliases/MobileElementRef.md +20 -0
  156. package/docs/reference/type-aliases/MobileHandle.md +65 -0
  157. package/docs/reference/type-aliases/MobileScenario.md +20 -0
  158. package/docs/reference/type-aliases/MobileSpecification.md +51 -0
  159. package/docs/reference/type-aliases/MobileSpecificationOptions.md +126 -0
  160. package/docs/reference/type-aliases/MobileVisitor.md +81 -0
  161. package/docs/reference/type-aliases/MockOfOptions.md +26 -0
  162. package/docs/reference/type-aliases/MockPort.md +10 -0
  163. package/docs/reference/type-aliases/PinnedClock.md +10 -0
  164. package/docs/reference/type-aliases/PostgresOptions.md +32 -0
  165. package/docs/reference/type-aliases/ProcessOptions.md +112 -0
  166. package/docs/reference/type-aliases/RedisOptions.md +20 -0
  167. package/docs/reference/type-aliases/RenderSubject.md +12 -0
  168. package/docs/reference/type-aliases/ScreenNode.md +67 -0
  169. package/docs/reference/type-aliases/ServerPort.md +36 -0
  170. package/docs/reference/type-aliases/ServerResponse.md +46 -0
  171. package/docs/reference/type-aliases/ServerSpec.md +21 -0
  172. package/docs/reference/type-aliases/ServiceHandle.md +216 -0
  173. package/docs/reference/type-aliases/ServiceRecord.md +12 -0
  174. package/docs/reference/type-aliases/SpecDocument.md +84 -0
  175. package/docs/reference/type-aliases/SpecEnvToken.md +21 -0
  176. package/docs/reference/type-aliases/SpecFileAssertion.md +27 -0
  177. package/docs/reference/type-aliases/SpecFixture.md +30 -0
  178. package/docs/reference/type-aliases/SpecKind.md +8 -0
  179. package/docs/reference/type-aliases/SpecRun.md +108 -0
  180. package/docs/reference/type-aliases/SpecServeEntry.md +40 -0
  181. package/docs/reference/type-aliases/SpecStream.md +32 -0
  182. package/docs/reference/type-aliases/SpecificationConfig.md +290 -0
  183. package/docs/reference/type-aliases/SqliteOptions.md +36 -0
  184. package/docs/reference/type-aliases/SseEvent.md +58 -0
  185. package/docs/reference/type-aliases/StreamBody.md +61 -0
  186. package/docs/reference/type-aliases/TextFilter.md +22 -0
  187. package/docs/reference/type-aliases/VisitScenario.md +20 -0
  188. package/docs/reference/type-aliases/Visitor.md +215 -0
  189. package/docs/reference/type-aliases/WaitUntilOptions.md +46 -0
  190. package/docs/reference/type-aliases/WebsiteBackendOptions.md +37 -0
  191. package/docs/reference/type-aliases/WebsiteHandle.md +51 -0
  192. package/docs/reference/type-aliases/WebsiteSpecification.md +118 -0
  193. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +40 -0
  194. package/docs/reference/variables/anthropic.md +19 -0
  195. package/docs/reference/variables/banner.md +10 -0
  196. package/docs/reference/variables/button.md +10 -0
  197. package/docs/reference/variables/clock.md +16 -0
  198. package/docs/reference/variables/complementary.md +10 -0
  199. package/docs/reference/variables/component.md +10 -0
  200. package/docs/reference/variables/content.md +10 -0
  201. package/docs/reference/variables/contentinfo.md +10 -0
  202. package/docs/reference/variables/dialog.md +10 -0
  203. package/docs/reference/variables/field.md +10 -0
  204. package/docs/reference/variables/form.md +10 -0
  205. package/docs/reference/variables/heading.md +10 -0
  206. package/docs/reference/variables/http.md +40 -0
  207. package/docs/reference/variables/intercept.md +11 -0
  208. package/docs/reference/variables/link.md +10 -0
  209. package/docs/reference/variables/listitem.md +10 -0
  210. package/docs/reference/variables/main.md +10 -0
  211. package/docs/reference/variables/match.md +8 -0
  212. package/docs/reference/variables/navigation.md +10 -0
  213. package/docs/reference/variables/openai.md +21 -0
  214. package/docs/reference/variables/option.md +17 -0
  215. package/docs/reference/variables/region.md +10 -0
  216. package/docs/reference/variables/row.md +10 -0
  217. package/docs/reference/variables/search.md +10 -0
  218. package/docs/reference/variables/specification.md +8 -0
  219. package/docs/reference/variables/status.md +10 -0
  220. package/docs/reference/variables/table.md +10 -0
  221. package/package.json +32 -11
  222. package/dist/spec-document.js +0 -651
@@ -0,0 +1,351 @@
1
+ # 02 — Developing
2
+
3
+ How a change is made. The first half is this repository's own loop — the toolchain, the order its gates demand, which file a change opens and what it owes when it lands. The second half is the walkthrough a consuming project follows, from `npm install` to two passing specs.
4
+
5
+ ## Changing this package
6
+
7
+ ### The loop
8
+
9
+ | Task | Command |
10
+ | -------------------------------- | --------------------------------- |
11
+ | Install dependencies | `npm install` (or `make install`) |
12
+ | Build the bundle | `npm run build` |
13
+ | Lint + format + typecheck + knip | `npm run lint` |
14
+ | Auto-fix what a fixer can | `npm run lint:fix` |
15
+ | Run every suite | `npm test` |
16
+ | Run the fast suite only | `npx vitest --run --project unit` |
17
+ | Regenerate the projections | `npm run docs` (or `make docs`) |
18
+
19
+ Each has a `make` alias that installs first, which is what CI calls — [03 — Testing](03-testing.md) § What CI runs.
20
+
21
+ `better-sqlite3` is a native dependency, and an npm that does not run install scripts by default leaves it unbuilt: the `sqlite()` specs of the `unit` project then fail on a missing binding. `npm rebuild better-sqlite3` after the install fixes it, once per checkout.
22
+
23
+ ### The build comes first
24
+
25
+ `oxlint.config.ts` loads this package's OWN plugin from `./dist/oxlint.js`, and the end-to-end lint specs load it too. Node's type-stripping does not resolve a `.js` specifier back to its `.ts` source, so **`npm run build` must precede `npm run lint`** and must precede the `unit` project. A lint run on a stale bundle judges the previous build's rules.
26
+
27
+ That config is also where this repository DECLARES its own architecture: `i1-layer-boundaries` ships inert, and `FRAMEWORK_LAYERS` in `oxlint.config.ts` is the enforced statement of the five trees [01 — Architecture](01-architecture.md) describes.
28
+
29
+ ### Which file a change opens
30
+
31
+ | Changing… | Opens |
32
+ | ------------------------------------------------- | ------------------------------------------------------------------------------------------ |
33
+ | A facet's chain, setups or terminal actions | `src/facets/<facet>/`, over `model/chain/builder.ts` |
34
+ | What a result exposes | `src/model/result/` — accessors stay READ-ONLY |
35
+ | A matcher, or update-mode behaviour | `src/model/goldens/` and `src/runner/` — the two sides of the runner coupling |
36
+ | An external dependency's adapter | `src/seams/<dep>/`, which imports that dep and `model/` and no more |
37
+ | A `{{token}}` or the structural comparison | `src/model/matching/` |
38
+ | The `<case>.spec.yaml` grammar | `src/model/literate/` — read by BOTH the runner and the checker |
39
+ | A mechanized rule | `src/lint/manifest.ts` **and** its implementation under `src/lint/rules/<facet-or-model>/` |
40
+ | A principle, or a criterion no machine can settle | [18 — Conventions](18-conventions.md), the constitution |
41
+
42
+ ### What a change owes
43
+
44
+ Four things land in the SAME commit as the change that makes them true.
45
+
46
+ - **The guard.** Every defect class discovered — in review, from a bug, during a migration — grows the thing that stops it recurring: a static rule, a meta-test, or a runtime refusal. That is rule K1, and it is what keeps the other channels growing instead of decaying. When no channel is possible, the change says so explicitly.
47
+ - **The regenerated projections.** `npm run docs` rewrites them all at once — the API reference under `docs/reference/`, the rule catalogue spliced into [19 — Linting](19-linting.md) and `skills/jterrazz-test/references/rules.md`, the capability matrix, the domain vocabulary and the siblingless table in [03 — Testing](03-testing.md), the fork in [18 — Conventions](18-conventions.md), the signature cards, and `schema/spec.schema.json`. Never edit one by hand: `npm run lint` runs the sync check and the freshness meta-tests, and both fail on a hand edit.
48
+
49
+ **`docs/reference/` is regenerated from a plain CLONE, never from a worktree.** typedoc writes a `Defined in:` link per symbol from whatever the git origin resolves to, and a worktree under `home/<brand>/work/worktrees/` resolves it to a local path — which turns every `https://github.com/jterrazz/package-test/blob/main/…` into a bare path, a whole-file diff that looks like content and is only an address. So `Docs (sync)` is red on `docs/reference/` in a worktree BY CONSTRUCTION, and the projection is refreshed by regenerating it in a clone and committing what that writes.
50
+
51
+ - **The chapter the behaviour falsified.** A page that still describes the old behaviour is a defect that ships. The corpus is mapped by [`docs/README.md`](README.md).
52
+ - **The skill, when the public surface moved.** `skills/jterrazz-test/` routes agents into these chapters; `README.md` is the vitrine and moves with a public API change too.
53
+
54
+ ## Using the framework
55
+
56
+ The rest of this chapter takes a consuming project from `npm install` to two passing specs: one HTTP API spec backed by a real Postgres container, and one CLI spec running a binary in a fresh temp directory. It also explains the one framework environment variable (`TEST_UPDATE`).
57
+
58
+ ### Install
59
+
60
+ ```bash
61
+ npm install -D @jterrazz/test vitest
62
+ ```
63
+
64
+ Peer dependencies:
65
+
66
+ | Package | Required | Needed for |
67
+ | -------- | -------- | ------------------------------------------------------------- |
68
+ | `vitest` | yes | Everything — the framework registers its matchers into vitest |
69
+
70
+ `msw` (outgoing HTTP interception for `.intercept()`) ships as a direct dependency — no separate install. API specs pass your web app to `server`; the adapter only needs an object with a `request()` method, so bring your own web framework (e.g. `hono`) in your project.
71
+
72
+ **Docker** must be running for container-backed services (`postgres()`, `redis()`). `sqlite()` and plain CLI specs need no Docker.
73
+
74
+ Trying an unreleased branch of the framework: install a `npm pack` tarball, never a `file:` link — a link makes the consumer resolve `vitest`'s types twice, and the matcher augmentation then lands on one copy and not the other, so `toMatch` types while `toBeEmpty` does not.
75
+
76
+ Everything a spec needs imports from the single package root; the tool subpaths beside it are the ones the `exports` map publishes, and they are listed once — [01 § What the tree publishes](01-architecture.md#what-the-tree-publishes). F1 and F3 read that map, so a specifier it does not publish is refused:
77
+
78
+ ```typescript
79
+ // the runners, the services, and what a chain stands on
80
+ import { postgres, process, redis, specification, sqlite } from '@jterrazz/test';
81
+ ```
82
+
83
+ ```typescript
84
+ // what a test says: contracts, dynamic values, doubles, time, assertions
85
+ import { anthropic, clock, defineContract, http, intercept } from '@jterrazz/test';
86
+ import { match, mockOf, openai, required, waitUntil } from '@jterrazz/test';
87
+ ```
88
+
89
+ ### The shape of every test
90
+
91
+ A **specification file** (`*.specification.ts`, under `specs/`) creates a runner once per suite. A **test file** imports the runner and writes specs. Every spec is one chain: zero or more setups, then exactly one terminal action, resolving to a typed result you assert on with `expect()`.
92
+
93
+ ```
94
+ specification.api(…) → { api, cleanup, docker }
95
+ specification.jobs(…) → { jobs, cleanup } // no docker — jobs never spawn containers
96
+ specification.cli(…) → { cli, cleanup, docker }
97
+ specification.integration(…) → { integration, cleanup } // a module, against real services or a golden
98
+ specification.website(…) → { website, cleanup, url } // no docker — a browser, not a container
99
+ specification.mobile(…) → { mobile, cleanup, udid } // no docker — a simulator, not a container
100
+ ```
101
+
102
+ The destructured names are canonical — no aliasing (`{ api: myApi }` is an error, rule A3) — and every specification file registers `afterAll(cleanup)` (rule A4).
103
+
104
+ ### First API spec
105
+
106
+ ```typescript
107
+ // specs/api/api.specification.ts
108
+ import { afterAll } from 'vitest';
109
+ import { specification, postgres } from '@jterrazz/test';
110
+ import { createApp } from '../../src/app.js';
111
+
112
+ export const { api, cleanup } = await specification.api({
113
+ services: {
114
+ db: postgres(), // reported as "db"; its init reads docker/db/ (chapter 17)
115
+ },
116
+ server: ({ db }) => createApp({ databaseUrl: db.connectionString }),
117
+ // root: absent — auto-discovered by walking up to the nearest package.json
118
+ });
119
+
120
+ afterAll(cleanup);
121
+ ```
122
+
123
+ ```http
124
+ ### specs/api/users/_requests/create-user.http — the COMPLETE request
125
+ POST /users
126
+ Content-Type: application/json
127
+
128
+ { "name": "Alice" }
129
+ ```
130
+
131
+ ```http
132
+ ### specs/api/users/_expected/user-created.http — status + header subset + body
133
+ HTTP/1.1 201 Created
134
+ Content-Type: application/json
135
+
136
+ { "id": "{{uuid}}", "name": "Alice" }
137
+ ```
138
+
139
+ ```typescript
140
+ // specs/api/users/users.spec.ts
141
+ import { expect, test } from 'vitest';
142
+ import { api } from '../api.specification.js';
143
+
144
+ test('creates a user', async () => {
145
+ // Given - empty database
146
+ const result = await api.request('create-user.http');
147
+
148
+ // Then - response matches the fixture; row landed in the database
149
+ expect(result.response).toMatch('user-created.http');
150
+ await expect(result.table('users')).toMatchRows({
151
+ columns: ['name'],
152
+ rows: [['Alice']],
153
+ });
154
+ });
155
+ ```
156
+
157
+ `{{uuid}}` is a placeholder from the unified [token grammar](15-tokens.md) — the response body must contain _a_ UUID there, whatever its value.
158
+
159
+ ### First CLI spec
160
+
161
+ ```typescript
162
+ // specs/cli/cli.specification.ts
163
+ import { resolve } from 'node:path';
164
+ import { afterAll } from 'vitest';
165
+ import { specification } from '@jterrazz/test';
166
+
167
+ export const { cli, cleanup } = await specification.cli(
168
+ resolve(import.meta.dirname, '../../bin/my-cli.sh'),
169
+ );
170
+
171
+ afterAll(cleanup);
172
+ ```
173
+
174
+ ```typescript
175
+ // specs/cli/help/help.spec.ts
176
+ import { expect, test } from 'vitest';
177
+ import { cli } from '../cli.specification.js';
178
+
179
+ test('shows help', async () => {
180
+ // Given
181
+ const result = await cli.exec('--help');
182
+
183
+ // Then - full snapshot of stdout against _expected/help.txt
184
+ expect(result.exitCode).toBe(0);
185
+ expect(result.stdout).toMatch('help.txt');
186
+ });
187
+ ```
188
+
189
+ ```
190
+ ### specs/cli/help/_expected/help.txt — tokens work in text snapshots too
191
+ my-cli v{{semver}}
192
+ Started at {{iso8601}} in {{workdir}}
193
+ Done in {{duration}}
194
+ ```
195
+
196
+ Each CLI spec runs in a fresh, empty temp directory. ANSI escape sequences are stripped before comparison by default (rule D6) — you never snapshot color codes.
197
+
198
+ ### vitest config: the preset
199
+
200
+ `vitest.config.ts` starts from `defineSpecConfig()` — the shared preset, imported from the tool subpath beside `literate()`. What you pass is a plain vite/vitest config merged **over** the defaults, so one call gives you the ecosystem's common ground and you still state whatever you want:
201
+
202
+ ```typescript
203
+ // vitest.config.ts
204
+ import { api, component, defineSpecConfig, unit, website } from '@jterrazz/test/vitest';
205
+
206
+ export default defineSpecConfig({
207
+ test: {
208
+ projects: [
209
+ unit(), // `**/*.test.ts` outside specs/ — no browser, no pipeline
210
+ component({ vite: './vite.config.ts', wrap: './src/providers.tsx' }),
211
+ website(), // `specs/website/**` — the served product, in its own group
212
+ api(), // `specs/api/**/*.spec.ts` — the app through HTTP, in-process
213
+ ],
214
+ },
215
+ });
216
+ ```
217
+
218
+ What every helper accepts, on top of the project it already is:
219
+
220
+ | Option | Does |
221
+ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
222
+ | `include` / `exclude` | Replace the canonical globs. A default `exclude` guards the default `include`, so state both or neither |
223
+ | `timeout` | Raise (or lower) the preset's 30 s for this project alone |
224
+ | `serial` | `fileParallelism: false` — the project's files run one at a time, for a facet whose files share one server, one database file, one app |
225
+
226
+ `component()` takes four more that are the APP's — `vite`, `wrap`, `viewport`, `timezone`/`locale`, and the `root` its relative paths are read against — and [07 — Component specs](07-component.md) owns them; `unit()` takes `roots`.
227
+
228
+ `unit()` and `component()` are a pair by construction: `unit()` collects `**/*.test.ts` outside `specs/` and excludes `**/*.test.tsx`, `component()` collects exactly those, so the suffix beside a file decides which project runs it and which rules judge it. `unit()` takes `roots?` where the modules are not at the repository root, or `include?` for the globs outright; the facet helpers collect `specs/<facet>/**/*.spec.ts` — the word for the assembled product (C12). All three carry `sequence.groupOrder` — node 0, `website` 1, `component` 2 — so the two browser projects never open two Chromiums at once on a 2-vCPU runner. What `component()` sets — the provider pinned to the runner's exact version, the msw worker served from this package's own install, the JSX transform the current Vite uses, the dependencies a cold cache must pre-bundle, the artefact directories, the group order that keeps two Chromiums apart — is [07 — Component specs](07-component.md)'s. `component()` is async, and a project may be a promise: `projects: [component({ … })]` needs no `await`.
229
+
230
+ #### What the preset sets
231
+
232
+ | Setting | Value | Why |
233
+ | -------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
234
+ | `cacheDir` | `.artifacts/vitest` | The artefact convention below — vite's transform cache leaves `node_modules/` |
235
+ | `test.coverage.reportsDirectory` | [the artefact path](#artefacts-live-under-artifacts) | Where a tool writes what it generates is the toolchain's convention ([`@jterrazz/typescript` — Developing](https://github.com/jterrazz/package-typescript/blob/main/docs/02-developing.md)); the preset sets it so no consumer writes it. The **provider is yours to install** (`@vitest/coverage-v8`) |
236
+ | `test.testTimeout` | `30_000` | Vitest's 5s never survived a container boot, a `prisma db push` or a `next build` |
237
+ | `test.hookTimeout` | `30_000` | Same reason, for the `beforeAll` that starts the infrastructure |
238
+ | `test.exclude` | vitest's defaults + `**/_fixtures/**` | What a spec stands on is an input, never a suite — a repository must not run its own counter-examples |
239
+ | `test.attachmentsDir` | `.artifacts/vitest/attachments` | Vitest 5 writes `context.annotate()` attachments to `.vitest/` at the repository root |
240
+ | `test.outputFile` | `.artifacts/vitest/{json,junit,html,blob}` | Vitest 5 turned the json and junit reporters into FILE writers; all four are artefacts |
241
+ | `test.retry` | `0` | A flaky test is fixed or deleted — a retry turns a real defect into a slow one and hides it |
242
+ | `test.restoreMocks` | `true` | A `vi.spyOn` a test forgets to restore fails a LATER file, one that did nothing wrong |
243
+ | `test.unstubGlobals` | `true` | Same for `vi.stubGlobal` — and it is what lets a test be written without an `afterEach` (rule J6w) |
244
+ | `test.unstubEnvs` | `true` | Same for `vi.stubEnv`, which is the sanctioned way to state a module's env contract (rule E9w) |
245
+ | `plugins` | `literate()`, when `literate:` is given | Turns every matching `<case>.spec.yaml` into a test file (see [12 — CLI specs](12-cli.md)) |
246
+
247
+ It deliberately sets **nothing else**. `fileParallelism` is a per-project truth (a container-lifecycle suite is serial, an isolated one is not), stated by the project rather than by the preset — a helper states it for you when you pass `serial`, and a hand-written project states it itself; so do `reporters`, `environment`, `env`, `globalSetup` and every `include` — a preset that guessed those would be wrong more often than right.
248
+
249
+ **Why the artefact paths are the preset's.** Every one of them is a path the TOOL pins and a consumer never writes: a repository does not choose where `context.annotate()` lands, it discovers it one CI run later, as an untracked `.vitest/` beside `.artifacts/`. Moving them here is the same rule the estate applies to every tool — what a tool generates lives at `.artifacts/<tool>/` — applied once instead of in fourteen configs.
250
+
251
+ Two behaviours worth knowing:
252
+
253
+ - **Projects inherit the defaults too.** Vitest resolves each project as its own config and it inherits nothing from the root, so the preset merges the budgets, the exclusions and the cache dir into every inline project. A project declared as a glob string, a promise or a function is handed back untouched.
254
+ - **Each glob is stated once.** A project's `exclude` REPLACES vitest's own defaults, so every project carries the preset's list in full; the root's list, which vitest concatenates into each project on top of that, then holds only what YOU stated there. The no-tests banner is the one place a consumer ever reads that list, and it read as three merges of the same three globs.
255
+ - **Arrays are ADDITIVE.** Vite's merge concatenates them, so your `exclude` adds to the preset's — you never spread `configDefaults.exclude` again — and your `plugins` join `literate()` rather than replacing it. Scalars (`testTimeout`, `name`, …) are plain overrides: what you state wins.
256
+
257
+ With `projects`, put `literate()` in the ONE project that collects those documents — its glob has to join that project's include — rather than in the top-level `literate:` key:
258
+
259
+ ```typescript
260
+ import { defineSpecConfig, literate } from '@jterrazz/test/vitest';
261
+
262
+ export default defineSpecConfig({
263
+ test: {
264
+ projects: [
265
+ {
266
+ plugins: [literate({ specification: './specs/cli/cli.specification.ts' })],
267
+ test: { name: 'cli', include: ['specs/cli/**/*.spec.ts'] },
268
+ },
269
+ ],
270
+ },
271
+ });
272
+ ```
273
+
274
+ #### Migrating a hand-rolled config
275
+
276
+ Swap the import, drop what the preset already says, keep what is yours:
277
+
278
+ ```diff
279
+ -import { configDefaults, defineConfig } from 'vitest/config';
280
+ -import { literate } from '@jterrazz/test/vitest';
281
+ +import { defineSpecConfig, literate } from '@jterrazz/test/vitest';
282
+
283
+ -export default defineConfig({
284
+ +export default defineSpecConfig({
285
+ test: {
286
+ - testTimeout: 30_000,
287
+ - hookTimeout: 30_000,
288
+
289
+ projects: [
290
+ {
291
+ test: {
292
+ name: 'api',
293
+ include: ['specs/api/**/*.spec.ts'],
294
+ - exclude: [...configDefaults.exclude, '**/_fixtures/**', 'specs/api/heavy/**'],
295
+ + exclude: ['specs/api/heavy/**'],
296
+ - testTimeout: 30_000,
297
+ },
298
+ },
299
+ ],
300
+ },
301
+ });
302
+ ```
303
+
304
+ Then add `.artifacts/` to `.gitignore` and drop `node_modules/.vite` from it if it was listed. A project whose timeouts were LOWER than 30s, or higher, keeps stating them — the preset is a floor to start from, not a ceiling.
305
+
306
+ ### Artefacts live under `.artifacts/`
307
+
308
+ The `.artifacts/<tool>/` convention is [`@jterrazz/typescript`](https://github.com/jterrazz/package-typescript/blob/main/docs/02-developing.md)'s, and its `check` enforces it: one folder per tool at the project root, one line in `.gitignore`, one `rm -rf .artifacts` for a clean slate. What this framework writes there is the only part this chapter answers for.
309
+
310
+ What this framework writes there:
311
+
312
+ | Path | Written by | Lifetime |
313
+ | ------------------------------------------------ | ---------------------------- | ------------------------------- |
314
+ | `.artifacts/vitest/` | vite's transform cache | Reused across runs |
315
+ | `.artifacts/vitest/coverage/` | v8, which the preset selects | Rewritten per coverage run |
316
+ | `.artifacts/vitest/sqlite/template-<key>.sqlite` | `sqlite()`'s schema template | Reused until the schema changes |
317
+
318
+ What it does **not** write there: the fresh temp directory each CLI spec runs in, the per-worker SQLite copies, the profile dirs a browser or a simulator needs. Those are per-RUN scratch, they stay in the OS temp dir, and moving them into the project would only put a `package.json` above a spec that must not see one.
319
+
320
+ ### Framework environment variables
321
+
322
+ You set exactly one variable, prefixed `TEST_` (rule E1): `TEST_UPDATE=1` turns on update mode, which [15 — Tokens § Update mode](15-tokens.md#update-mode-tokens-are-preserved) owns whole — what it writes, what it preserves, and the discipline it asks of a reviewer. The framework also reads vitest's own `VITEST_POOL_ID` (set by vitest, not you) to isolate each parallel worker's database schema and index.
323
+
324
+ ### Directory layout at a glance
325
+
326
+ ```
327
+ specs/
328
+ ├── api/
329
+ │ ├── api.specification.ts
330
+ │ └── users/
331
+ │ ├── users.spec.ts # <aspect>.spec.ts inside its domain (rule C1)
332
+ │ ├── _seeds/ # *.sql
333
+ │ ├── _requests/ # *.http — complete requests (inputs)
334
+ │ ├── contracts/ # <name>.contracts.ts facade + <provider>/<name>.ts units + their data
335
+ │ └── _expected/ # all expected fixtures, flat — incl. response *.http (a slash in the name creates a subfolder)
336
+ └── cli/
337
+ ├── cli.specification.ts # runner at the facet root (rule C1)
338
+ └── help/
339
+ └── help.spec.ts # under specs/, the suffix is .spec.ts (rule C12)
340
+ ```
341
+
342
+ ## Pitfalls
343
+
344
+ - **Renaming the destructured runner** (`const { api: usersApi } = …`). The canonical names `api`, `jobs`, `cli`, `website`, `mobile` are enforced (rule A3).
345
+ - **Forgetting `afterAll(cleanup)`.** Infrastructure leaks across suites; rule A4 requires it in every specification file.
346
+ - **Importing from a subpath the `exports` map does not publish** (`@jterrazz/test/services`). Everything a spec needs comes from `@jterrazz/test` (rule F1).
347
+ - **Writing `// Given` without `// Then`** (or vice versa). Every test carries both comments (rule B4); `// When` only when the action is not obvious — the chain _is_ the when.
348
+
349
+ ## Related
350
+
351
+ [10 — API specs](10-api.md) · [12 — CLI specs](12-cli.md) · [14 — Assertions](14-assertions.md) · [18 — Conventions](18-conventions.md) · [08 — Website specs](08-website.md) · [09 — Mobile specs](09-mobile.md)