@jterrazz/test 16.1.1 → 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.
Files changed (304) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +2 -2980
  3. package/dist/browser/services.js +2 -0
  4. package/dist/browser/stubs.js +2 -0
  5. package/dist/checker.d.ts +6 -1
  6. package/dist/checker.js +4534 -2628
  7. package/dist/config.d.ts +40 -0
  8. package/dist/config.js +309 -0
  9. package/dist/in-place.global-setup.d.ts +11 -0
  10. package/dist/in-place.global-setup.js +21 -0
  11. package/dist/index.d.ts +1653 -3004
  12. package/dist/index.js +5226 -3845
  13. package/dist/mobile-session.global-setup.d.ts +14 -0
  14. package/dist/mobile-session.global-setup.js +31 -0
  15. package/dist/oxlint.cjs +6069 -5634
  16. package/dist/oxlint.d.cts +43 -7212
  17. package/dist/oxlint.d.ts +43 -7212
  18. package/dist/oxlint.js +6127 -5693
  19. package/dist/route-coverage.global-setup.d.ts +42 -0
  20. package/dist/route-coverage.global-setup.js +2 -0
  21. package/dist/services.d.ts +3 -0
  22. package/dist/services.global-setup.d.ts +43 -0
  23. package/dist/services.global-setup.js +2 -0
  24. package/dist/services.js +597 -0
  25. package/dist/shared-appium.adapter.js +428 -0
  26. package/dist/shared-artifacts.js +32 -0
  27. package/dist/shared-chapter.js +7 -0
  28. package/dist/shared-container.port.d.ts +31 -0
  29. package/dist/shared-derived-projects.js +58 -0
  30. package/dist/shared-device.port.d.ts +189 -0
  31. package/dist/shared-exchange.js +447 -0
  32. package/dist/shared-in-place.js +172 -0
  33. package/dist/shared-index.browser.js +3191 -0
  34. package/dist/shared-index.services.d.ts +346 -0
  35. package/dist/shared-index.stubs.d.ts +287 -0
  36. package/dist/shared-json-shape.js +17 -0
  37. package/dist/shared-landmarks.js +71 -0
  38. package/dist/shared-ledger.js +68 -0
  39. package/dist/shared-literate-plugin.js +183 -0
  40. package/dist/shared-manifest.js +1321 -0
  41. package/dist/shared-pause.js +11 -0
  42. package/dist/shared-peer.js +88 -0
  43. package/dist/shared-playwright.adapter.js +330 -0
  44. package/dist/shared-process.adapter.js +74 -0
  45. package/dist/shared-refusal.js +35 -0
  46. package/dist/shared-resolve.js +24 -0
  47. package/dist/shared-route-coverage.global-setup.js +187 -0
  48. package/dist/shared-scenario-failure.js +198 -0
  49. package/dist/shared-serve.js +365 -0
  50. package/dist/shared-service-images.js +10 -0
  51. package/dist/shared-services.global-setup.js +179 -0
  52. package/dist/shared-session-ledger.js +301 -0
  53. package/dist/shared-setup-entry.js +12 -0
  54. package/dist/shared-source-scan.js +133 -0
  55. package/dist/shared-spec-document.js +365 -0
  56. package/dist/shared-specifications.js +26 -0
  57. package/dist/shared-stream-body.js +130 -0
  58. package/dist/shared-stub-server.js +190 -0
  59. package/dist/shared-temp-root.global-setup.js +80 -0
  60. package/dist/shared-test-baseline.js +26 -0
  61. package/dist/shared-test-section.js +58 -0
  62. package/dist/shared-testcontainers.adapter.js +203 -0
  63. package/dist/shared-tokens.js +55 -0
  64. package/dist/shared-yaml-document.js +119 -0
  65. package/dist/stubs.d.ts +2 -0
  66. package/dist/stubs.js +657 -0
  67. package/dist/temp-root.global-setup.d.ts +7 -0
  68. package/dist/temp-root.global-setup.js +2 -0
  69. package/dist/vitest.d.ts +97 -163
  70. package/dist/vitest.js +373 -525
  71. package/docs/README.md +82 -36
  72. package/docs/concepts/architecture.md +118 -0
  73. package/docs/concepts/conventions.md +333 -0
  74. package/docs/guides/api-specs.md +374 -0
  75. package/docs/guides/cli-specs.md +564 -0
  76. package/docs/guides/component-specs.md +252 -0
  77. package/docs/guides/develop.md +94 -0
  78. package/docs/guides/integration-specs.md +181 -0
  79. package/docs/guides/job-specs.md +180 -0
  80. package/docs/guides/lint-specs.md +174 -0
  81. package/docs/guides/mobile-specs.md +319 -0
  82. package/docs/guides/module-tests.md +149 -0
  83. package/docs/guides/release.md +43 -0
  84. package/docs/guides/spec-documents.md +189 -0
  85. package/docs/guides/website-specs.md +413 -0
  86. package/docs/reference/assertions.md +345 -0
  87. package/docs/reference/glossary.md +20 -0
  88. package/docs/reference/linting.md +483 -0
  89. package/docs/reference/services.md +236 -0
  90. package/docs/reference/stubs.md +498 -0
  91. package/docs/reference/time-and-doubles.md +137 -0
  92. package/docs/reference/tokens.md +258 -0
  93. package/docs/reference/visitor.md +221 -0
  94. package/docs/start/getting-started.md +187 -0
  95. package/docs/start/set-up-a-project.md +214 -0
  96. package/package.json +50 -30
  97. package/schema/spec.schema.json +43 -17
  98. package/dist/appium.adapter.js +0 -489
  99. package/dist/artifacts.js +0 -32
  100. package/dist/fixtures.js +0 -110
  101. package/dist/literate.js +0 -1747
  102. package/dist/match.js +0 -804
  103. package/dist/playwright.adapter.js +0 -438
  104. package/dist/server.js +0 -160
  105. package/dist/stream.js +0 -321
  106. package/dist/substring-warning.js +0 -282
  107. package/docs/01-architecture.md +0 -95
  108. package/docs/02-developing.md +0 -351
  109. package/docs/03-testing.md +0 -495
  110. package/docs/04-operating.md +0 -101
  111. package/docs/05-module-tests.md +0 -152
  112. package/docs/06-integration.md +0 -180
  113. package/docs/07-component.md +0 -246
  114. package/docs/08-website.md +0 -404
  115. package/docs/09-mobile.md +0 -270
  116. package/docs/10-api.md +0 -264
  117. package/docs/11-jobs.md +0 -157
  118. package/docs/12-cli.md +0 -677
  119. package/docs/13-elements.md +0 -248
  120. package/docs/14-assertions.md +0 -314
  121. package/docs/15-tokens.md +0 -253
  122. package/docs/16-contracts.md +0 -438
  123. package/docs/17-services.md +0 -188
  124. package/docs/18-conventions.md +0 -332
  125. package/docs/19-linting.md +0 -512
  126. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  127. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  128. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  129. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  130. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  131. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  132. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  133. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  134. package/docs/decisions/_template.md +0 -22
  135. package/docs/reference/classes/Matcher.md +0 -120
  136. package/docs/reference/classes/ProcessHandle.md +0 -368
  137. package/docs/reference/functions/defineContract.md +0 -39
  138. package/docs/reference/functions/defineContracts.md +0 -36
  139. package/docs/reference/functions/disabled.md +0 -28
  140. package/docs/reference/functions/enabled.md +0 -20
  141. package/docs/reference/functions/focused.md +0 -26
  142. package/docs/reference/functions/mockOf.md +0 -79
  143. package/docs/reference/functions/postgres.md +0 -31
  144. package/docs/reference/functions/process.md +0 -33
  145. package/docs/reference/functions/redis.md +0 -28
  146. package/docs/reference/functions/required.md +0 -41
  147. package/docs/reference/functions/selected.md +0 -26
  148. package/docs/reference/functions/sqlite.md +0 -33
  149. package/docs/reference/functions/testId.md +0 -20
  150. package/docs/reference/functions/text.md +0 -40
  151. package/docs/reference/functions/valued.md +0 -28
  152. package/docs/reference/functions/waitUntil.md +0 -46
  153. package/docs/reference/functions/within.md +0 -29
  154. package/docs/reference/index.md +0 -196
  155. package/docs/reference/interfaces/BaseResult.md +0 -87
  156. package/docs/reference/interfaces/CallResult.md +0 -160
  157. package/docs/reference/interfaces/CaptureScope.md +0 -82
  158. package/docs/reference/interfaces/CliResult.md +0 -253
  159. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  160. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  161. package/docs/reference/interfaces/FetchResult.md +0 -180
  162. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  163. package/docs/reference/interfaces/HttpResult.md +0 -126
  164. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  165. package/docs/reference/interfaces/PageResult.md +0 -356
  166. package/docs/reference/interfaces/RenderResult.md +0 -72
  167. package/docs/reference/interfaces/RenderedText.md +0 -110
  168. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  169. package/docs/reference/interfaces/ScreenResult.md +0 -128
  170. package/docs/reference/interfaces/TableAccessor.md +0 -26
  171. package/docs/reference/interfaces/TextAccessor.md +0 -96
  172. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  173. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  174. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  175. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  176. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  177. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  178. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  179. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  180. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  181. package/docs/reference/type-aliases/CliEnv.md +0 -11
  182. package/docs/reference/type-aliases/CliHandle.md +0 -80
  183. package/docs/reference/type-aliases/CliInput.md +0 -36
  184. package/docs/reference/type-aliases/CliOutput.md +0 -46
  185. package/docs/reference/type-aliases/CliPort.md +0 -58
  186. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  187. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  188. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  189. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  190. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  191. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  192. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  193. package/docs/reference/type-aliases/Contract.md +0 -68
  194. package/docs/reference/type-aliases/ContractInput.md +0 -13
  195. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  196. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  197. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  198. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  199. package/docs/reference/type-aliases/Contracts.md +0 -48
  200. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  201. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  202. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  203. package/docs/reference/type-aliases/DevicePort.md +0 -51
  204. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  205. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  206. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  207. package/docs/reference/type-aliases/DomMount.md +0 -21
  208. package/docs/reference/type-aliases/ElementKind.md +0 -27
  209. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  210. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  211. package/docs/reference/type-aliases/ElementRef.md +0 -128
  212. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  213. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  214. package/docs/reference/type-aliases/HonoApp.md +0 -31
  215. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  216. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  217. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  218. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  219. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  220. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  221. package/docs/reference/type-aliases/Intercept.md +0 -19
  222. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  223. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  224. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  225. package/docs/reference/type-aliases/JobHandle.md +0 -34
  226. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  227. package/docs/reference/type-aliases/JobsResult.md +0 -16
  228. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  229. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  230. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  231. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  232. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  233. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  234. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  235. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  236. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  237. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  238. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  239. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  240. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  241. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  242. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  243. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  244. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  245. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  246. package/docs/reference/type-aliases/MockPort.md +0 -10
  247. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  248. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  249. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  250. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  251. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  252. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  253. package/docs/reference/type-aliases/ServerPort.md +0 -36
  254. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  255. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  256. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  257. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  258. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  259. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  260. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  261. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  262. package/docs/reference/type-aliases/SpecKind.md +0 -8
  263. package/docs/reference/type-aliases/SpecRun.md +0 -108
  264. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  265. package/docs/reference/type-aliases/SpecStream.md +0 -32
  266. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  267. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  268. package/docs/reference/type-aliases/SseEvent.md +0 -58
  269. package/docs/reference/type-aliases/StreamBody.md +0 -61
  270. package/docs/reference/type-aliases/TextFilter.md +0 -22
  271. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  272. package/docs/reference/type-aliases/Visitor.md +0 -215
  273. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  274. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  275. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  276. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  277. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  278. package/docs/reference/variables/anthropic.md +0 -19
  279. package/docs/reference/variables/banner.md +0 -10
  280. package/docs/reference/variables/button.md +0 -10
  281. package/docs/reference/variables/clock.md +0 -16
  282. package/docs/reference/variables/complementary.md +0 -10
  283. package/docs/reference/variables/component.md +0 -10
  284. package/docs/reference/variables/content.md +0 -10
  285. package/docs/reference/variables/contentinfo.md +0 -10
  286. package/docs/reference/variables/dialog.md +0 -10
  287. package/docs/reference/variables/field.md +0 -10
  288. package/docs/reference/variables/form.md +0 -10
  289. package/docs/reference/variables/heading.md +0 -10
  290. package/docs/reference/variables/http.md +0 -40
  291. package/docs/reference/variables/intercept.md +0 -11
  292. package/docs/reference/variables/link.md +0 -10
  293. package/docs/reference/variables/listitem.md +0 -10
  294. package/docs/reference/variables/main.md +0 -10
  295. package/docs/reference/variables/match.md +0 -8
  296. package/docs/reference/variables/navigation.md +0 -10
  297. package/docs/reference/variables/openai.md +0 -21
  298. package/docs/reference/variables/option.md +0 -17
  299. package/docs/reference/variables/region.md +0 -10
  300. package/docs/reference/variables/row.md +0 -10
  301. package/docs/reference/variables/search.md +0 -10
  302. package/docs/reference/variables/specification.md +0 -8
  303. package/docs/reference/variables/status.md +0 -10
  304. package/docs/reference/variables/table.md +0 -10
@@ -0,0 +1,345 @@
1
+ # Assertions: the reference
2
+
3
+ Every assertion goes through `expect()` (rule `result-accessor-read-only`). The framework auto-registers its matchers into vitest and types them **by subject**: a table subject only offers table matchers, a stream subject only stream matchers; the wrong pairing is a compile error. Result accessors are read-only; there are no assertion methods on accessors (`result.stdout.toContain(...)` does not exist; the methods live on `expect()`).
4
+
5
+ This page is the exhaustive matrix: every matcher, for every valid subject, with its sync/async rule and the file it reads.
6
+
7
+ ## The two global rules
8
+
9
+ ### Sync vs async
10
+
11
+ `await expect(...)` is used **only** for matchers that perform IO, and this table is the whole list. The types say the same thing, so a mistaken `await` is a toolchain error (`typescript/await-thenable`) in a consumer and a missed one is an assertion that never runs:
12
+
13
+ | IO matcher subjects | Why |
14
+ | -------------------------------------------------------------------------- | ------------------------------------ |
15
+ | `result.table(...)`: `toMatch(<rows>)` and `toBeEmpty()` on it | Runs a SQL query |
16
+ | `toMatch(directory(...))` on `result.filesystem` / `result.directory(...)` | Walks the disk |
17
+ | container subjects | Talks to the Docker daemon |
18
+ | every accessor of a COMPONENT result (`tree`, `html`, `content`, ...) | The capture crosses the browser seam |
19
+
20
+ An `await` is judged by the types, not by a rule of this package. Every IO matcher is typed `Promise<void>` (`toBeEmpty` and `toBeRunning` whatever they are handed, a `toMatch` by its subject: a table's rows, a tree's `directory(...)`), so the type-aware `typescript/no-floating-promises` every profile of `@jterrazz/typescript` turns on refuses one dropped. A synchronous matcher returns no promise, so `typescript/await-thenable` refuses an `await` in front of it.
21
+
22
+ Everything else is synchronous, and that includes the subjects it is easiest to get wrong: `result.value`, `result.error`, `result.response`, `result.stdout`, `result.stderr`, `result.json`, and `result.tree` on a WEBSITE page, which is captured on the node side and is a plain text subject there. Only a component's accessors are read inside the page.
23
+
24
+ ### One verb compares a whole result, and its argument says where the expectation lives
25
+
26
+ `toMatch` on a result the framework produced (a response, a stream, a json, a table, a tree) compares the WHOLE result, and its argument is the expectation: a value written in place, the default, or `file()`/`directory()` when the expectation lives on disk (rule `one-verb-per-meaning`):
27
+
28
+ ```typescript
29
+ expect(result.response).toMatch(`
30
+ HTTP/1.1 201 Created
31
+ content-type: application/json
32
+
33
+ {"id": "{{uuid}}", "name": "Alice"}
34
+ `);
35
+ await expect(result.table('users')).toMatch([[1, 'Alice', 'alice@test.com']]);
36
+ expect(result.stdout).toMatch(file('./__expected__/help.txt'));
37
+ ```
38
+
39
+ | Subject | The value written in place | On disk |
40
+ | ----------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------- |
41
+ | `result.response` | its `.http` text: status line, the headers it lists, the body | `file('./<name>.http')` |
42
+ | a stream (`stdout`, `stderr`, `error`, `tree`, `text(...)`) | its text | `file('./<name>.txt')` |
43
+ | a json (`json`, `value`, `head`, `screen`) | its value: an object, a list, a scalar | `file('./<name>.json')` |
44
+ | `result.table(...)` | its rows: one array of cells per row, every column in the table's order | none |
45
+ | `result.directory(...)`, `result.filesystem` | none: a value states no tree | `directory('./.../')` |
46
+ | a container | none: asserted running, `toBeRunning()`; its logs are streams | none |
47
+
48
+ A text written in place that opens on a line break is read without it, without the whitespace of its closing line, and without the indentation its lines share: a block indents with the test around it. The `{{token}}` grammar and `match.*` apply to a value in place exactly as to a file, and a mismatch prints the diff the same file would. A string handed a stream is a whole text, never vitest's substring: a RegExp matches a part, and on a result subject it does not compile, and past the compiler it is refused, naming the form that takes it, `expect(result.stdout.text).toMatch(/built/)`. On a plain string (`result.stdout.text` is one), `toMatch`, `toBe` and `toContain` are vitest's, untouched.
49
+
50
+ The disk is named by one of two calls, `file()` and `directory()`, with the path relative to the spec file, spelled in full, and called in the test itself, never in a helper that would resolve it per caller (rule `paths-named-in-the-test`); the type refuses a bare string where a file is meant (rule `disk-named-by-relative-path`):
51
+
52
+ - `toMatch(file('./__expected__/help.txt'))`: a golden FILE, for a response, a stream, a JSON body, a call's value, an ARIA tree. The extension is part of the path and mandatory (rule `golden-path-has-extension`); a path may name a subfolder, `./__expected__/build/verbose.txt`, and update mode creates it (rule `golden-path-creates-folders`).
53
+ - `toMatch(directory('./__expected__/shop-scaffold/'))`: a golden TREE, for `result.directory(...)` or `result.filesystem`. The path ends in `/`.
54
+
55
+ Nothing resolves against a hidden folder: every golden is read where its path says, for every subject (rule `golden-read-from-its-path`). By convention it sits in `__expected__/` beside the spec, one of the four reserved ground folders ([Conventions](../concepts/conventions.md)).
56
+
57
+ `toContain` handed a stream accessor does not compile: a stream is no list, and vitest would search an empty one; its text is `result.stdout.text`, the comparable text (ANSI stripped, the runner's `transform` applied, the text its golden compares).
58
+
59
+ All file-based comparisons understand the [`{{token}}` grammar](tokens.md); all code-side dynamic values use `match.*`.
60
+
61
+ ## Scalars: native `expect`
62
+
63
+ Plain values take vitest's native matchers. No framework matcher exists (or is needed) for them.
64
+
65
+ | Subject | Type | Example |
66
+ | ------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
67
+ | `result.status` | `number` | `expect(result.status).toBe(201)` |
68
+ | `result.exitCode` | `number` | `expect(result.exitCode).toBe(0)` |
69
+ | `result.filesystem.cwd` | `string` | `expect(result.filesystem.cwd).toContain('/tmp/')` |
70
+ | `result.response.body` | parsed body | `expect(result.response.body).toEqual({ error: 'User 999 not found' })` |
71
+ | `result.json.value` | parsed JSON | `expect(result.json.value).toMatchObject({ name: 'shoply' })` |
72
+ | `result.stdout.grep(pattern)` | `TextAccessor` | `expect(result.stdout.grep('broken.yaml').text).toContain('missing price')` |
73
+ | `result.file(path).exists` | `boolean` | `expect(result.file('dist/index.js').exists).toBe(true)` |
74
+ | `result.file(path).content` | `string` | `expect(result.file('shoply.yaml').content).toContain('name: my-shop')` |
75
+ | `result.stdout.text` | `string` | `expect(result.stdout.text).toBe('')`: ANSI stripped, `transform` applied |
76
+ | `result.stdout.raw` | `string` | `expect(result.stdout.raw).toMatch(/\u001B\[31m/u)`: the capture as it was |
77
+ | `result.containerIds` | `string[]` | `expect(result.containerIds).toHaveLength(1)` |
78
+ | `result.requests` | `{ body, headers, method, url }[]` | `expect(result.requests.map(({ body }) => body)).toStrictEqual([{ report: 'daily' }])`: what the binary sent its declared backend, in order |
79
+ | `await result.filesystem.files()` | `string[]` | `expect(await result.filesystem.files()).toContain('shoply.lock')` |
80
+ | `await cli.run(file('./<case>.spec.yaml'))` | `CliResult` | the document asserts itself; the LAST run's result comes back; see [Spec documents](../guides/spec-documents.md#the-three-doors-one-runner) |
81
+
82
+ ## `result.response`: HTTP response (api)
83
+
84
+ | Matcher | Sync/async | Example |
85
+ | -------------------------------- | ---------- | --------------------------------------------------------------------------- |
86
+ | `toMatch(<.http text>)` | sync | ``expect(result.response).toMatch(`HTTP/1.1 201 Created ...`)`` |
87
+ | `toMatch(file('./<name>.http'))` | sync | `expect(result.response).toMatch(file('./__expected__/user-created.http'))` |
88
+
89
+ Checks, in order: the status line, the listed headers (**subset**: unlisted response headers are unconstrained, rule `expected-headers-are-a-subset`), then the body. Placeholders apply in headers _and_ body, with `#ref` captures shared across both:
90
+
91
+ ```http
92
+ HTTP/1.1 201 Created
93
+ Location: /orders/{{uuid#order}}
94
+
95
+ { "id": "{{uuid#order}}" }
96
+ ```
97
+
98
+ On mismatch the failure shows which part diverged. A status mismatch is a three-line message naming the fixture:
99
+
100
+ ```text
101
+ Response status mismatch (./__expected__/user-created.http)
102
+ expected: 201
103
+ received: 500
104
+ ```
105
+
106
+ A header mismatch has the same shape (`Response header mismatch (name)`, with `header:` / `expected:` / `received:` lines; a missing header shows `received: (absent)`). A body mismatch prints a line-by-line structural diff (`Response mismatch (name)`, `- Expected` / `+ Received`) of the expected body (with matchers and tokens rendered as their placeholder text, `{{uuid#order}}`) against the actual body: a JSON value as its pretty-printed lines, and a text body (when both sides are text) as its own lines, so an HTML page or an event stream reads as written. The diff is literal: it does not annotate captured ref values, and a failed `{{uuid#order}}` re-occurrence surfaces only as the two differing lines.
107
+
108
+ `.not`: `expect(result.response).not.toMatch(file('./__expected__/user-created.http'))` passes when any part diverges. Rarely useful; prefer positive fixtures.
109
+
110
+ ## `result.table(name, { database }?)`: database tables (api, job, cli, integration)
111
+
112
+ Always `await expect(...)` (IO). When to name the `database:` is [Services § The `database:` rule (`database-named-only-when-several`)](services.md#the-database-rule)'s.
113
+
114
+ | Matcher | Sync/async | Example |
115
+ | ----------------- | ---------- | ---------------------------------------------------------------------- |
116
+ | `toMatch(<rows>)` | async | see below |
117
+ | `toBeEmpty()` | async | `await expect(result.table('orders', { database: 'db' })).toBeEmpty()` |
118
+
119
+ The table is compared WHOLE: every row, and every column in the order the table declares it. A reset restarts the generated ids, so the first row a chain inserts is `1` ([Services](services.md#per-worker-isolation)); an instant is compared as its ISO text.
120
+
121
+ A table has no order of its own, so its rows compare as a multiset: each row written in place is paired with a row of the table it matches, whatever order the database hands them back in, since a first column of random ids orders them differently run to run. When the order IS the claim, `toMatch(rows, { ordered: true })` compares row by row in the order of the table's first column (the rows are read `ORDER BY 1`). A token that captures, `{{uuid#event}}`, binds on the pairing that holds, and update mode writes the rows in the order the value in place stated them, a new row after them.
122
+
123
+ ```typescript
124
+ await expect(result.table('orders', { database: 'db' })).toMatch([
125
+ [1, 'pending', 1250, '{{iso8601}}'],
126
+ ]);
127
+ ```
128
+
129
+ Cell values are literals, [tokens](tokens.md) or `match.*` matchers, including `match.ref(name)`, which captures a generated value in one table and requires it in another ([Tokens § `match.*`](tokens.md#match-the-same-vocabulary-in-code)).
130
+
131
+ `toBeEmpty` is a table's matcher, and async: the type-aware `typescript/no-floating-promises`, on in every profile of `@jterrazz/typescript`, refuses the bare `expect(x).toBeEmpty();`: dropped, it passes the test and surfaces the real failure as an unhandled rejection. A stream is not a table: an empty one is an empty text, `expect(result.stderr.text).toBe('')`, vitest's own, sync, on the text a golden compares. `toBeEmpty` handed a stream does not compile, and at run time it is refused with that form.
132
+
133
+ A failing table `toMatch` prints the expected grid against the actual rows, every column named, cell by cell: a row that matches prints once, unmarked, and each stated row that matches none is shown against the unpaired row of the table it resembles most (the most cells in common), a row too many or too few alone. Matcher cells render as their placeholder text (`Matcher.toString()`): `match.uuid()` shows as `{{uuid}}`, `match.ref('order')` as `{{ref#order}}`, `match.ref('intent', { not: 'order' })` as `{{ref#intent!order}}`, `match.regex(/.../)` as `{{regex:...}}`. A failing `toBeEmpty` reports how many rows it found (the count, not the rows themselves). `.not` inverts both (`.not.toBeEmpty()` = at least one row).
134
+
135
+ ## `result.stdout` / `result.stderr`: stream subjects (cli, container `exec`)
136
+
137
+ Streams are compared **after ANSI stripping** ([Tokens § Where tokens work](tokens.md#where-tokens-work)): `.text` is that comparable text, `.raw` the capture as it was.
138
+
139
+ | Matcher | Sync/async | Example |
140
+ | ------------------------------- | ---------- | ---------------------------------------------------------------------- |
141
+ | `toMatch(<text>)` | sync | `expect(result.stdout).toMatch('built in {{duration}}\n')` |
142
+ | `toMatch(file('./<name>.txt'))` | sync | `expect(result.stdout).toMatch(file('./__expected__/help.txt'))` |
143
+ | `toContain('needle')` | sync | `expect(result.stderr.text).toContain("Unknown command 'frobnicate'")` |
144
+ | `toMatch(/pattern/)` | sync | `expect(result.stdout.text).toMatch(/built in \d+ms/u)` |
145
+
146
+ `toMatch` on a stream is a full-text snapshot, its text written in place or in a file; the golden may contain any [token](tokens.md) (`{{semver}}`, `{{duration}}`, `{{workdir}}`, ...). Token matching decides **pass or fail** only: `textEquals` resolves the placeholders to determine whether the output matches. The rendered failure, though, is a **literal** line-by-line diff (`Output mismatch (name)`, `- Expected` / `+ Received`) of the fixture text against the stripped output: tokens are not resolved in the diff, so a fixture line `Done in {{duration}}` is printed verbatim on the expected side whenever any line diverges, even if the duration itself matched.
147
+
148
+ > A stream compared whole takes its text, in place or `file(...)`. A part of it is vitest's, on its text: `expect(result.stdout.text).toMatch(/built/)`, `.toContain('built')`.
149
+
150
+ `toContain` is vitest's, so its failure is vitest's, printing the needle and the comparable haystack. `.not.toContain(...)` asserts absence.
151
+
152
+ ```typescript
153
+ // .grep() returns a TextAccessor: chainable and snapshot-able, so the same
154
+ // Token grammar applies as on the stream itself.
155
+ expect(result.stdout.grep('products/ok.yaml').text).not.toContain('error'); // absence probe
156
+ expect(result.stdout.grep('products/broken.yaml')).toMatch(file('./__expected__/broken-block.txt')); // snapshot a block
157
+ ```
158
+
159
+ ## `text(value)`: any string as a stream subject
160
+
161
+ `text(value)` wraps an arbitrary string in the same `TextAccessor` streams surface, anchored on the calling test's directory via the same caller-detection the runners use, the directory its golden's path is read from. It promotes an ad-hoc string (most often a thrown **error message**, a checker line, or a report) into a first-class snapshot subject: ANSI is stripped before comparison (`.text` is the stripped text, `.raw` the string as given), the [`{{token}}`](tokens.md) grammar applies to the fixture, and `.grep()` composes exactly as on a captured stream.
162
+
163
+ The product surface of a test framework **is** its error messages and reports; golden them in full instead of reconstructing them with a cluster of `toContain` probes.
164
+
165
+ | Matcher | Sync/async | Example |
166
+ | ------------------------------- | ---------- | ----------------------------------------------------------------------- |
167
+ | `toMatch(file('./<name>.txt'))` | sync | `expect(text(message)).toMatch(file('./__expected__/parse-error.txt'))` |
168
+ | `toContain('needle')` | sync | `expect(text(message).text).toContain('did you mean')` |
169
+
170
+ ```typescript
171
+ import { text } from '@jterrazz/test';
172
+
173
+ // Capture a rejection message cleanly, then golden the whole thing.
174
+ async function catchMessage(assertion: () => unknown): Promise<string> {
175
+ try {
176
+ await assertion();
177
+ } catch (error: any) {
178
+ return error.message;
179
+ }
180
+ throw new Error('expected the assertion to throw, but it passed');
181
+ }
182
+
183
+ const message = await catchMessage(() =>
184
+ expect(result.response).toMatch(file('./__expected__/wrong-body.http')),
185
+ );
186
+ // Volatile fragments (paths, durations, ids) go through tokens: {{path}}, {{duration}}, {{uuid}}...
187
+ expect(text(message)).toMatch(file('./__expected__/errors/wrong-body-error.txt'));
188
+ ```
189
+
190
+ ## `result.json`: parsed stdout (cli)
191
+
192
+ | Matcher | Sync/async | Example |
193
+ | -------------------------------- | ---------- | ----------------------------------------------------------------- |
194
+ | `toMatch(<value>)` | sync | `expect(result.json).toMatch({ name: 'shoply', id: '{{uuid}}' })` |
195
+ | `toMatch(file('./<name>.json'))` | sync | `expect(result.json).toMatch(file('./__expected__/config.json'))` |
196
+
197
+ Deep-equal against the value in place or the JSON golden: every key, both ways; the fixture may embed tokens (`"id": "{{uuid}}"`). Failure prints a structural object diff (missing keys, extra keys, per-key value mismatches). For partial checks, read `.value` and use native `toMatchObject`.
198
+
199
+ ## `result.file(path)`: single files in the cwd (cli)
200
+
201
+ A pure read accessor, with no framework matcher; assert on its properties with native `expect`:
202
+
203
+ | Property | Type | Example |
204
+ | ---------- | --------- | ------------------------------------------------------------------------------- |
205
+ | `.exists` | `boolean` | `expect(result.file('my-shop/.env').exists).toBe(false)` |
206
+ | `.content` | `string` | `expect(result.file('my-shop/shoply.yaml').content).toContain('name: my-shop')` |
207
+
208
+ ## `result.directory(name)`: tree snapshots (cli)
209
+
210
+ | Matcher | Sync/async | Example |
211
+ | ------------------------------ | ---------- | ----------------------------------------------------------------------------------------------- |
212
+ | `toMatch(directory('./.../'))` | **async** | `await expect(result.directory('my-shop')).toMatch(directory('./__expected__/shop-scaffold/'))` |
213
+
214
+ Compares the tree rooted at `<cwd>/name` against the golden directory `./__expected__/shop-scaffold/`: structure _and_ file contents (contents honour tokens). Failure is a structured diff in three groups: `added` (on disk, not in the golden), `removed` (in the golden, not on disk), `changed` (content mismatch, with a per-file diff). The path ends in `/`; the type and the matcher both refuse one that does not.
215
+
216
+ ## `result.filesystem`: the whole cwd (cli)
217
+
218
+ | Matcher / accessor | Sync/async | Example |
219
+ | ------------------------------ | ---------- | ------------------------------------------------------------------------------------- |
220
+ | `toMatch(directory('./.../'))` | **async** | `await expect(result.filesystem).toMatch(directory('./__expected__/upgraded-shop/'))` |
221
+ | `.files()` | async read | `expect(await result.filesystem.files()).toContain('shoply.lock')` |
222
+
223
+ Same semantics as `result.directory(...)`, rooted at the cwd itself. `.files()` returns the sorted recursive file list, a read, so follow it with native matchers.
224
+
225
+ ## Container subjects (docker-aware cli)
226
+
227
+ Available when the runner declares `docker: { envVar, nameLabel, testRunLabel }`; results must be bound with `await using` (rule `docker-exec-bound-with-await-using`). `result.container(name)` looks a container up by its `nameLabel` value, lazily: no Docker call happens until you touch it. Absent containers return `exists: false` instead of throwing.
228
+
229
+ The runner handle itself also exposes a `docker(containerId)` reader (returned by `specification.api()` and `specification.cli()`, not `specification.job()`): given a raw container id, it lazily runs `docker inspect` and returns the **same** `ContainerAccessor` type, so it works with `await expect(spec.docker(id)).toBeRunning()` and every read accessor below. An unknown id yields an accessor with `exists: false` rather than throwing. The app of an api spec runs in the test process, but it can still start containers through the Docker daemon, as an API that deploys or provisions something does: the response hands back an id, and `docker(id)` reads the container the request left behind.
230
+
231
+ | Subject / matcher | Sync/async | Example |
232
+ | ----------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------ |
233
+ | `container.exists` (read) | sync | `expect(result.container('nope').exists).toBe(false)` |
234
+ | `expect(container).toBeRunning()` | **async** | `await expect(shop).toBeRunning()` |
235
+ | `container.file(path).content` (read) | sync API | `expect(shop.file('/app/shoply.yaml').content).toContain('name: alpha')` |
236
+ | `container.exec(cmd)` (read) | async read | `const inside = await shop.exec('ls /app')` then `expect(inside.stdout.text).toContain('shoply.yaml')` |
237
+ | `container.stdout` / `.stderr` (logs, stream subject) | sync | `expect(shop.stdout.text).toContain('shop ready')` |
238
+
239
+ `container.exec()` resolves to a result whose `stdout`/`stderr` are ordinary stream subjects, the same matchers as host streams. `.not.toBeRunning()` asserts a stopped (but existing) container; for a _removed_ container assert `exists` instead.
240
+
241
+ **The sync container-read exception (as implemented):** container property reads (`exists`, `running`, `status`, `file(path).exists`, `file(path).content`, log streams) are synchronous, backed by one-shot `docker inspect` / `docker exec` shell-outs captured lazily on first access. This is the documented exception to "await only IO matchers": only the _matchers_ (`toBeRunning`) are async; property reads stay sync so container assertions read exactly like host-side ones.
242
+
243
+ ## `result.tree`: the accessibility outline (website, component)
244
+
245
+ The one golden a rendered surface wants. It is the outline a screen reader walks, it is the same dialect on a page and on a mounted unit, and it is deterministic where a screenshot is not: a font hint or a scrollbar moves a picture and moves nothing here.
246
+
247
+ ```typescript
248
+ // A website page: the capture is already on the node side.
249
+ expect(result.tree).toMatch(file('./__expected__/home.aria.yaml'));
250
+
251
+ // A component: the capture is read INSIDE the page, so the read is IO.
252
+ await expect(result.tree).toMatch(file('./__expected__/two-of-two-hundred.aria.yaml'));
253
+ ```
254
+
255
+ The `await` is the component's, not the tree's: a subject captured inside a page crosses the browser seam through a server command, and every accessor of a `component` result is awaited for that reason. It carries what the tree carries and nothing else: enablement (`[disabled]`), selection (`[selected]`), the roles and the accessible names. It does NOT carry focus, so a keyboard assertion is a verb, never a golden ([The visitor](visitor.md#modifiers)).
256
+
257
+ ## `result.html`: the markup (component)
258
+
259
+ The escape hatch for what the tree cannot say: a class name, a `data-` attribute, an element the accessibility tree flattens away.
260
+
261
+ ```typescript
262
+ expect(result.html.text).toContain('row row--live');
263
+ ```
264
+
265
+ Reach for it after the tree, not instead of it. A test that asserts only on markup is testing an implementation the user never meets, which is the failure the vocabulary exists to prevent.
266
+
267
+ ## `result.value` / `result.error`: what a call returned (integration)
268
+
269
+ `.call()` resolves one way or the other, and both sides are subjects.
270
+
271
+ `result.value` is a **JSON accessor** when the call returned an object and a **text accessor** when it returned a string, so the same golden vocabulary reaches both:
272
+
273
+ ```typescript
274
+ expect(result.value).toMatch(file('./__expected__/found.json'));
275
+ expect(result.value.text).toContain('Alice');
276
+ ```
277
+
278
+ `result.error` is what the call THREW, read as a subject of its own: a refusal is an answer, and it deserves a golden like any other:
279
+
280
+ ```typescript
281
+ expect(result.error).toMatch(file('./__expected__/refused.txt'));
282
+ expect(result.error.text).toBe(''); // nothing was thrown
283
+ ```
284
+
285
+ A call that threw leaves `result.value` empty and vice versa; asserting on both in one test is asserting on a branch that cannot happen.
286
+
287
+ ## Two helpers that are not matchers
288
+
289
+ `expect()` is the only way an assertion is made (rule `result-accessor-read-only`), but two gestures around an assertion had been hand-written in every repository that needed them.
290
+
291
+ **`required(value, why)`**: the value, or a failure that says what was missing and why it mattered. A test reaching into a captured structure meets `T | undefined` and has to answer for it; `!` erases the question and a three-line `if (...) throw` is the same three lines everywhere.
292
+
293
+ ```typescript
294
+ import { required } from '@jterrazz/test';
295
+
296
+ const body: { id?: number } = { id: 7 };
297
+ const id: number = required(body.id, 'the creation reply carries the new id');
298
+ ```
299
+
300
+ `null` and `undefined` are the only values it refuses: `0`, `''` and `false` are present, and go through.
301
+
302
+ **`waitUntil(predicate, { timeout?, interval?, why? })`**: wait on a CONDITION, never on a duration. A spec that sleeps for a guessed number of milliseconds is slow on a fast machine and flaky on a slow one, which is why rule `no-sleep` refuses a sleep in any test file. Every specification that drives something already waits on a condition; this is the primitive for the cases no specification owns: a background write to land, a queue to drain, a file to appear.
303
+
304
+ ```typescript
305
+ import { waitUntil } from '@jterrazz/test';
306
+
307
+ const outbox: string[] = [];
308
+ await waitUntil(() => outbox.length === 1, { why: 'the job wrote its one message' });
309
+ ```
310
+
311
+ The budget defaults to 5 000 ms and the poll to 50 ms; the failure names the condition and the budget, so a timeout reads as a sentence rather than as "expected true, got false". The budget is measured on `performance.now()`, never on `Date`, so `clock.at()` may freeze the calendar around a wait and the timeout still arrives with its own sentence. Not for a subject under `clock.run()`; there the scheduler belongs to the test and `clock.advance(ms)` is what makes time pass ([Time and doubles § The clock](time-and-doubles.md#the-clock)).
312
+
313
+ ## Matcher summary
314
+
315
+ | Matcher | Valid subjects | Sync/async | Reads |
316
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | -------------------- |
317
+ | `toMatch(<value>)` | `response` (its `.http` text), `stdout`, `stderr`, `error`, `tree`, `text(...)` (a text), `json`, `value` (a value), `table(...)` (rows) | sync, except a table and inside a page (async) | the test itself |
318
+ | `toMatch(file(...))` | `response`, `stdout`, `stderr`, `json`, `value`, `error`, `tree`, `text(...)` | sync, except inside a page (async) | the path it is given |
319
+ | `toMatch(directory(...))` | `directory(...)`, `filesystem` | async | the path it is given |
320
+ | `toBeEmpty` | `table(...)`; an empty stream is `expect(<stream>.text).toBe('')` | async | none |
321
+ | `toBeRunning` | `container(...)` | async | none |
322
+ | native vitest | every scalar/read accessor (`status`, `exitCode`, `filesystem.cwd`, `.text`, `.raw`, `.value`, `.exists`, `.content`, `grep(...)`, `files()`, `containerIds`, `requests`), and `toMatch` with a value, on a string | per vitest | none |
323
+
324
+ `.not` is supported on every framework matcher.
325
+
326
+ ## Update mode
327
+
328
+ `TEST_UPDATE=1` (or `vitest -u`) rewrites a mismatching golden from the actual output instead of failing, preserving the tokens already in the file, and `{ frozen: true }` opts one fixture out, for a fixture whose mismatch IS the behaviour under test. The rewrite, the flag, the discipline it asks for and the frozen two-pass are all [Tokens](tokens.md#update-mode-tokens-are-preserved)'s; this page only notes which matchers the flag reaches, and it is `toMatch` on every result subject: a value written in place is rewritten in the test itself, a `file()` or a `directory()` on disk.
329
+
330
+ ## Common mistakes
331
+
332
+ - **Calling assertion methods on accessors.** `result.stdout.toContain('x')` does not compile; accessors are read-only (rule `result-accessor-read-only`). Write `expect(result.stdout.text).toContain('x')`.
333
+ - **Missing `await` on IO matchers.** `expect(result.table('users')).toMatch([...])` without `await` never queries the database and the test passes vacuously (`typescript/no-floating-promises` refuses it).
334
+ - **Putting `await` in front of a sync matcher.** A `toMatch(file(...))` on a stream, a JSON body, a response or a call's value runs either way, and then the toolchain refuses the file: the matcher is typed as returning a value, so awaiting it is the toolchain error [above](#sync-vs-async). The sync/async split is the contract the types carry, and `typescript/await-thenable` refuses the needless `await`.
335
+ - **`file('./__expected__/help')` without extension.** The extension is part of the path (rule `golden-path-has-extension`); a tree is `directory('./__expected__/help/')`.
336
+ - **A golden written as a value on `.text`.** `expect(result.stdout.text).toMatch('help.txt')` is vitest's substring check, which passes when the output contains the words "help.txt". The whole stream is `expect(result.stdout).toMatch(...)`, its text in place or `file('./__expected__/help.txt')` (rule `one-verb-per-meaning`).
337
+ - **A bare name.** `file('help.txt')` does not compile, and forced through it is refused: every file is named by its path from the spec (rules `disk-named-by-relative-path`, `disk-named-by-relative-path-at-runtime`).
338
+ - **Asserting raw ANSI.** Streams are stripped before comparison; if you truly need the raw bytes, that is what `.raw` is for ([Tokens § Where tokens work](tokens.md#where-tokens-work)); a RegExp on it is allowed, as in the scalar table above, while a substring check (`toContain`) on it is refused (`no-raw-bypass`).
339
+ - **`database:` on a single-database project** (or missing on a multi-database one): see [Services § The `database:` rule (`database-named-only-when-several`)](services.md#the-database-rule).
340
+ - **Using `.not.toMatch(file(...))` as a lazy negative.** It passes for _any_ divergence, including ones you did not intend. Prefer a positive fixture or a targeted `toContain`.
341
+ - **A deliberately-wrong fixture without `{ frozen: true }`.** `TEST_UPDATE=1` overwrites it; see [Tokens § Frozen fixtures](tokens.md#frozen-fixtures-opting-a-negative-fixture-out-of-the-rewrite).
342
+
343
+ ## Related
344
+
345
+ [API specs](../guides/api-specs.md) · [CLI specs](../guides/cli-specs.md) · [Tokens](tokens.md) · [Conventions](../concepts/conventions.md)
@@ -0,0 +1,20 @@
1
+ # Glossary
2
+
3
+ The words this manual uses, one meaning each.
4
+
5
+ | Word | Means |
6
+ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | kind, specification | A kind of test: api, cli, website ... A **specification** is the code behind one kind: its constructor (`specification.api()`), its chain and its result, composed in `src/specifications/<name>/<name>.composition.ts`. It is not the `kind` a repository declares in `jterrazz.config.ts`: the two agree by design (a repository of kind `api` is specified by the `api` specification) but stay distinct, since a kind says what the repository IS (its build, its presets) and a specification says how a test drives it, so a `library` holds `cli` or `lint` specifications |
8
+ | module, unit | The kind that tests one module, from the `__tests__/` of its folder, is a **module** test; the vitest project and the specification that run it are named `unit` (`test.projects.unit`, `--project unit`) |
9
+ | runner | What a constructor returns, such as `api` from `specification.api()`: the object every chain of the suite starts from |
10
+ | handle | What a service factory returns, such as `postgres()`: the service before it starts, then its `connectionString` once it runs |
11
+ | chain | One test's sentence on a runner: zero or more setups (`.seed()`, `.intercept()` ...), then exactly one action (`.get()`, `.exec()` ...) that resolves to a result |
12
+ | oracle | What a test compares the result against: the value inside `expect(...).toMatch(...)` |
13
+ | golden | An oracle kept in a file under `__expected__/`, compared whole and rewritten by `TEST_UPDATE=1` |
14
+ | ground | The files a test stands on, in the `__seeds__/`, `__requests__/`, `__fixtures__/` and `__expected__/` folders |
15
+ | seam | A place where the framework meets an outside tool it loads on demand: a database driver, a browser, a simulator. Each is an optional peer |
16
+ | ratchet | A count of debt kept in `.jterrazz/baseline.json` that may only fall: an unreached route, a convention not yet followed. A run that raises it fails, and `TEST_UPDATE=1` records a fall |
17
+ | specimen | A small sample app a testing tool keeps under `specs/__fixtures__/` to test itself against. Only a repository that tests a testing tool has one |
18
+ | door | Where a test file may sit: in the `__tests__/` of its module's folder (a unit), under `specs/<kind>/` (an assembled product), or in a repository suite (the third door) |
19
+
20
+ The examples use made-up products: `shoply`, a shop CLI; a news app with an events feed; and hosts under `.test` and `.example`, which never resolve on the internet.