@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
package/docs/15-tokens.md DELETED
@@ -1,253 +0,0 @@
1
- # 15 — Tokens: dynamic values in fixtures
2
-
3
- Real systems generate UUIDs, timestamps, ports, and paths. The framework handles them with **one unified token grammar** (rule D4): the same `{{token}}` vocabulary works in every fixture file, and the same vocabulary is available in code as `match.*`. The vocabulary is fixed — 21 tokens, defined, tested, and documented in the package — and an unknown token in a fixture is an error, not a silent literal.
4
-
5
- ## Where tokens work
6
-
7
- | Fixture kind | Files | Coverage |
8
- | -------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
9
- | HTTP responses | `_expected/*.http` | **body and headers** |
10
- | JSON snapshots | `_expected/*.json` | any string value |
11
- | Text snapshots | `_expected/*.txt` (and other text files under `_expected/`) | anywhere in the text |
12
- | Spec documents | `<case>.spec.yaml`, beside the spec ([12](12-cli.md#spec-documents--casespecyaml)) | both streams of every run, and `files:` assertions — **never the description or the command**, which are prose |
13
-
14
- ```http
15
- ### _expected/order-created.http — tokens in a header AND the body
16
- HTTP/1.1 201 Created
17
- Location: /orders/{{uuid#order}}
18
-
19
- { "id": "{{uuid#order}}", "total": "{{number}}", "createdAt": "{{iso8601}}" }
20
- ```
21
-
22
- ```
23
- ### _expected/help.txt — tokens in a TEXT snapshot
24
- shoply v{{semver}}
25
- Started at {{iso8601}} in {{workdir}}
26
- Done in {{duration}}
27
- ```
28
-
29
- Stream comparisons strip ANSI escape sequences **before** token matching (rule D6), so fixtures never contain color codes — `.text` remains the raw capture.
30
-
31
- ## The vocabulary — all 21 tokens
32
-
33
- Every token matches one value of its family at its position. The normative definitions (the exact accepted grammar for each) are shipped and tested inside the package; the table below states each token's semantics and canonical accepted forms.
34
-
35
- ### Identifiers
36
-
37
- | Token | Matches | Example value |
38
- | ---------- | ----------------------------------------------- | -------------------------------------- |
39
- | `{{uuid}}` | RFC 4122 UUID — `8-4-4-4-12` hexadecimal groups | `3f2c9b6e-8a41-4c8f-9d2a-1b7e5f0c4d3e` |
40
- | `{{ulid}}` | ULID — 26 Crockford base32 characters | `01J2X3YV5T8Q4R9K7M6N1P0ZSD` |
41
-
42
- ### Time
43
-
44
- | Token | Matches | Example value |
45
- | -------------- | ------------------------------------------------------------- | ---------------------------- |
46
- | `{{iso8601}}` | Full ISO 8601 timestamp (date + time, `Z` or offset) | `2026-07-16T10:32:05.123Z` |
47
- | `{{date}}` | Calendar date, `YYYY-MM-DD` | `2026-07-16` |
48
- | `{{time}}` | Wall-clock time, `HH:MM[:SS]` (seconds and fraction optional) | `10:32:05` |
49
- | `{{duration}}` | Elapsed-time string as printed by tooling | `340ms`, `1.24s`, `2m`, `3h` |
50
-
51
- ### Numbers
52
-
53
- | Token | Matches | Example value |
54
- | ------------ | -------------------------------------- | ------------- |
55
- | `{{number}}` | Any numeric value (integer or decimal) | `109`, `4.5` |
56
- | `{{int}}` | Integer only | `42` |
57
- | `{{float}}` | Decimal with a fractional part | `0.97` |
58
-
59
- ### Versions & hashes
60
-
61
- | Token | Matches | Example value |
62
- | ------------ | ------------------------------------------------------------------ | --------------------- |
63
- | `{{semver}}` | Semantic version (`major.minor.patch`, optional pre-release/build) | `9.0.0`, `9.1.0-rc.1` |
64
- | `{{sha}}` | Commit/content hash digest (hexadecimal, git-style) | `f690adf…` |
65
- | `{{hex}}` | Hexadecimal string | `deadbeef` |
66
- | `{{base64}}` | Base64 string (padding included) | `aGVsbG8=` |
67
-
68
- ### Network
69
-
70
- | Token | Matches | Example value |
71
- | ----------- | ----------------------------- | ------------------------------------- |
72
- | `{{port}}` | TCP/UDP port number (0–65535) | `54321` |
73
- | `{{ip}}` | IP address | `127.0.0.1` |
74
- | `{{url}}` | Absolute URL with scheme | `https://rates.example.com/v1/latest` |
75
- | `{{email}}` | Email address | `contact@jterrazz.com` |
76
-
77
- ### Paths
78
-
79
- | Token | Matches | Example value |
80
- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
81
- | `{{path}}` | Filesystem path | `/tmp/x/dist/index.js` |
82
- | `{{workdir}}` | **The exact cwd of the current spec** — the framework knows the temp directory it created, so this is an equality check, not a pattern | `/tmp/shoply-spec-a1b2c3` |
83
-
84
- `{{workdir}}` holds the **resolved** cwd — the spelling a child process reports, after any symlink on the way (macOS's `$TMPDIR` sits under `/var`, a link to `/private/var`). The `$WORKDIR` token that `.env()` and a document's `env:` expand is that same resolved path, so a directory the runner hands to the binary comes back in its output matching the token: `.env({ CACHE_DIR: '$WORKDIR/cache' })` is asserted as `{{workdir}}/cache`.
85
-
86
- ### Generic
87
-
88
- | Token | Matches |
89
- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
90
- | `{{string}}` | Any string value |
91
- | `{{any}}` | Anything — always-true **as a whole value** (matches any type: object, number, `null`). Embedded in a frame (`A {{any}} Z`) it only widens the middle; the surrounding literal still has to match |
92
-
93
- ## `#ref` captures
94
-
95
- Any token becomes a capture by suffixing `#name`: `{{uuid#order}}`. The **first occurrence captures** the actual value; every subsequent occurrence of the same ref **must be equal** (rule D4). Refs are scoped to the current spec — one namespace shared by all fixture files and code-side `match.ref()` within that spec, resetting between specs.
96
-
97
- ```http
98
- ### _expected/order-created.http — {{uuid#order}} appears 3 times → all must be equal
99
- HTTP/1.1 201 Created
100
- Location: /orders/{{uuid#order}}
101
-
102
- {
103
- "id": "{{uuid#order}}",
104
- "paymentIntent": { "orderId": "{{uuid#order}}" }
105
- }
106
- ```
107
-
108
- A ref that re-occurs with a different value fails with both values in the message — the captured one and the conflicting one.
109
-
110
- ## `match.*` — the same vocabulary in code
111
-
112
- Every token has a code mirror for inline assertions (table rows, primarily):
113
-
114
- ```typescript
115
- import { match } from '@jterrazz/test';
116
-
117
- await expect(result.table('orders', { database: 'db' })).toMatchRows({
118
- columns: ['id', 'status', 'total_cents', 'created_at'],
119
- rows: [[match.uuid(), 'pending', match.number(), match.iso8601()]],
120
- });
121
- ```
122
-
123
- `match.uuid()`, `match.ulid()`, `match.iso8601()`, `match.date()`, `match.time()`, `match.duration()`, `match.number()`, `match.int()`, `match.float()`, `match.semver()`, `match.sha()`, `match.hex()`, `match.base64()`, `match.port()`, `match.ip()`, `match.url()`, `match.email()`, `match.path()`, `match.workdir()`, `match.string()`, `match.any()` — plus three code-only forms:
124
-
125
- | Code-only | Meaning |
126
- | --------------------------------- | ------------------------------------------------------------------------------------------- |
127
- | `match.regex(/…/)` | Arbitrary pattern — the escape hatch when no token fits |
128
- | `match.ref(name)` | Capture on first use, require equality afterwards (same namespace as `{{token#name}}` refs) |
129
- | `match.ref(name, { not: other })` | Additionally require **inequality** with another captured ref |
130
-
131
- ```typescript
132
- // intent.order_id === orders.id, but intent.id ≠ orders.id
133
- await expect(result.table('orders', { database: 'db' })).toMatchRows({
134
- columns: ['id'],
135
- rows: [[match.ref('order')]],
136
- });
137
- await expect(result.table('payment_intents', { database: 'db' })).toMatchRows({
138
- columns: ['id', 'order_id'],
139
- rows: [[match.ref('intent', { not: 'order' }), match.ref('order')]],
140
- });
141
- ```
142
-
143
- ## Update mode: tokens are preserved
144
-
145
- `TEST_UPDATE=1` (or `vitest -u`) rewrites fixtures from actual output — but it writes **tokens, not values** (rule D5):
146
-
147
- - Segments already covered by a placeholder are **preserved**: a fixture line `Done in {{duration}}` stays `Done in {{duration}}` after an update, no matter what duration the run printed.
148
- - Values the framework knows to be dynamic are substituted automatically: the spec's cwd becomes `{{workdir}}` in the written fixture without you doing anything.
149
-
150
- Example — starting from actual output:
151
-
152
- ```
153
- shoply v9.1.0
154
- Started at 2026-07-17T09:12:44.031Z in /tmp/shoply-spec-x9y8z7
155
- Done in 0.83s
156
- ```
157
-
158
- with an existing fixture:
159
-
160
- ```
161
- shoply v{{semver}}
162
- Started at {{iso8601}} in {{workdir}}
163
- Done in {{duration}}
164
- ```
165
-
166
- `TEST_UPDATE=1` leaves the fixture byte-identical: every changed segment was already covered by a token. If the CLI adds a new line `Cache dir: /tmp/shoply-spec-x9y8z7/.cache`, the update writes `Cache dir: {{workdir}}/.cache` — `{{workdir}}` is substituted automatically; a timestamp in a _new_ line would be written literally, for you to tokenize by hand (or it will fail on the next run, which is the signal).
167
-
168
- The same preservation and `{{workdir}}` substitution apply identically across every fixture kind — text snapshots (`_expected/*.txt`), JSON goldens (`_expected/*.json`), `.http` response bodies (`_expected/*.http`), and the streams of a `<case>.spec.yaml` (whose ground, commands and `files:` an update never rewrites). A cwd embedded in a JSON string value is written back as `{{workdir}}` exactly as it is in a text line, so an updated JSON golden matches the next run's (different) cwd instead of pinning a run-specific temp path.
169
-
170
- This is why `transform` on `specification.cli` is only an escape hatch (rule D6): the token grammar plus ANSI stripping covers the standard sources of nondeterminism, and a transform that merely re-implements standard tokens is a future lint warning.
171
-
172
- ### Frozen fixtures — opting a negative fixture out of the rewrite
173
-
174
- Update mode rewrites a mismatching fixture with the actual output. That is exactly wrong for a **deliberately-wrong fixture** — one whose _mismatch_ is the behaviour under test (a spec that asserts the diff, or that a missing fixture errors). Under `TEST_UPDATE=1` an unfrozen negative fixture is silently overwritten with the real output, and the assertion stops testing anything.
175
-
176
- Pass `{ frozen: true }` as the second argument to opt a single fixture out:
177
-
178
- ```typescript
179
- // A deliberately-wrong fixture: its diff rendering is the subject.
180
- expect(() => expect(result.response).toMatch('wrong-body.http', { frozen: true })).toThrow(
181
- /Response mismatch/,
182
- );
183
- ```
184
-
185
- A frozen fixture is **never written** in update mode — a frozen mismatch still throws its diff, and a frozen _missing_ fixture still throws "does not exist" (never created). Its failure says so rather than pointing at update mode: write the file by hand, or drop `{ frozen }` for one `TEST_UPDATE=1` run and put it back. It is available on every fixture subject (`result.response`, `result.stdout`/`result.stderr`, `result.json`, `result.directory`, `result.filesystem`). The static rule `d13w-unfrozen-negative-fixture` flags a `toMatch` wrapped in `expect(() => …).toThrow()` / `.rejects` that omits it.
186
-
187
- ### The discipline — update mode writes, it does not judge
188
-
189
- A rewritten fixture is not a passing test: it is this run's output, made into the expectation. Three steps, in this order, and the middle one is the whole point.
190
-
191
- 1. **Run it.** `TEST_UPDATE=1 npx vitest --run --project <kind>` — narrowed to the project whose goldens moved, never the whole suite, because a broad update rewrites files nobody looked at.
192
- 2. **Read the diff.** `git diff` is the review: every line that changed is a behaviour that changed. A golden diff that nobody can explain is a bug that just became the expectation, and this is the only moment it is cheap to catch.
193
- 3. **Run again, without the flag.** The second run is the one that proves anything. It reads the file as any other run would, and it is what a reviewer's machine will do.
194
-
195
- The round-trip is not a step the suite performs for you, and deliberately so: update mode writes the file from THIS run's actual value, so re-comparing inside the same run matches by construction and proves nothing. The real round-trip already exists — CI runs the same goldens on another machine, which is exactly the question "does this file still hold when the run is not the one that wrote it?"
196
-
197
- **The frozen two-pass.** A frozen fixture is never written, so changing one on purpose is two deliberate passes: drop `{ frozen: true }`, run once under `TEST_UPDATE=1`, read the diff, and put the option back in the same commit. The friction is the feature — a negative fixture should cost something to change.
198
-
199
- **What update mode never touches.** The ground of a `<case>.spec.yaml` — its `description:`, its commands, its `env:`/`serve:`/`fixture:` and its `files:` — is authored, never rewritten; only the expected streams move. A `_requests/*.http` is an input, not an expectation, and is never written either.
200
-
201
- ## Known limitations
202
-
203
- The grammar is deliberately small; three edges are worth knowing:
204
-
205
- - **No escaping for literal `{{…}}` text.** There is no escape sequence. A `{{uuid}}` written in a fixture is _always_ interpreted as the token — you cannot assert a literal, un-substituted `{{uuid}}` string in expected output. (An unknown name like `{{widget}}` is caught by the conventions checker (rule D4) as an error — the vocabulary is frozen, so an unknown token is never a usable literal escape.) For genuinely custom shapes, use `match.regex()` in code.
206
- - **Update-merge pairs one previous line with one actual line.** Placeholder preservation is by **pattern**: the aligned line is tried first, then what is left over is re-paired by matching each remaining placeholder line against each unresolved actual line, in file order. A mid-file insertion is therefore safe — the shifted `{{duration}}` line is recognised where it landed. What stays out of reach: a previous line is spent **once**, so when the output grows a SECOND line the same placeholder could cover, the first keeps the token and the new one is written as a raw value, for you to tokenize.
207
- - **`{{float}}` cannot require a decimal on a JSON number.** In JSON there is no distinction between `42` and `42.0`, so as a whole JSON value `{{float}}` accepts any finite number (an integer passes). The decimal requirement only holds in **text** contexts (`4.2` matches, `42` does not). This is a JSON limitation, not a token choice.
208
-
209
- ## Appendix — canonical accepted forms (normative, as implemented)
210
-
211
- The exact grammar of each token, one tested regular expression per token
212
- (defined in the package's match module). In embedded positions the pattern is
213
- anchored to its surroundings; as a whole JSON value, numeric tokens also
214
- accept native JSON numbers.
215
-
216
- | Token | Accepted form (regex) | Notes |
217
- | -------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
218
- | `{{uuid}}` | `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}` | any RFC 4122 variant |
219
- | `{{ulid}}` | `[0-9A-HJKMNP-TV-Z]{26}` | Crockford base32, uppercase |
220
- | `{{iso8601}}` | `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z\|[+-]\d{2}:\d{2})` | date + time, `Z` or offset required |
221
- | `{{date}}` | `\d{4}-\d{2}-\d{2}` | |
222
- | `{{time}}` | `\d{2}:\d{2}(:\d{2}(\.\d+)?)?` | seconds and fraction optional |
223
- | `{{duration}}` | `\d+(\.\d+)?(ms\|s\|m\|h)` | strict: `ms`, `s`, `m`, `h` suffixes only |
224
- | `{{number}}` | `-?\d+(\.\d+)?([eE][+-]?\d+)?` | JSON numbers also accepted as whole values |
225
- | `{{int}}` | `-?\d+` | whole JSON value: `Number.isInteger` |
226
- | `{{float}}` | `-?\d+\.\d+` | whole JSON value: any finite number |
227
- | `{{semver}}` | `\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?` | |
228
- | `{{sha}}` | `[0-9a-f]{7,64}` | 7–64 lowercase hex chars (short SHA → SHA-512) |
229
- | `{{hex}}` | `[0-9a-fA-F]+` | |
230
- | `{{base64}}` | `[A-Za-z0-9+/]+={0,2}` | |
231
- | `{{port}}` | `\d{1,5}` | range-checked to 0–65535 (embedded placeholders reject out-of-range values like `99999`) |
232
- | `{{ip}}` | `(\d{1,3}\.){3}\d{1,3}` | IPv4 only for now |
233
- | `{{url}}` | `https?://[^\s"'<>]+` | http(s) schemes only |
234
- | `{{email}}` | `[^\s@"'<>]+@[^\s@"'<>]+\.[^\s@"'<>]+` | |
235
- | `{{path}}` | `\.{0,2}/[^\s"'<>]*` | absolute (`/…`) or relative (`./…`, `../…`) |
236
- | `{{workdir}}` | — (exact string equality with the spec's cwd, realpath form) | never matches when the framework does not know the cwd |
237
- | `{{string}}` | any string; embedded: `[^\n]*?` (single line) | |
238
- | `{{any}}` | anything; embedded: `[\s\S]*?` (may cross lines) | |
239
-
240
- ## Pitfalls
241
-
242
- - **Inventing a token.** The vocabulary is fixed; `{{timestamp}}` in a fixture is an error (rule D4). Use `{{iso8601}}` — or `match.regex()` in code for genuinely custom shapes.
243
- - **Expecting `{{workdir}}` to be a pattern.** It matches the _exact_ cwd of the current spec — a path from another spec's cwd fails.
244
- - **Assuming refs span the suite.** Ref scope is the current spec; `{{uuid#order}}` in two different tests are unrelated captures.
245
- - **Using `{{any}}` where a precise token exists.** `{{any}}` matches garbage too; every widening of a fixture weakens the spec.
246
- - **Reading "always-true" as unframed.** `{{any}}` is only unconditional as the _whole_ value. Embedded (`A {{any}} Z`), the frame around it must still match — and a non-string/number actual cannot satisfy an embedded token at all.
247
- - **Hand-writing values that update mode would tokenize.** Run `TEST_UPDATE=1` first, then tighten: placeholders you wrote once are preserved forever after.
248
- - **Escaping into `transform` for tokenizable noise.** Only application noise _not_ covered by the grammar justifies a transform (rule D6).
249
- - **Leaving a deliberately-wrong fixture unfrozen.** A negative test whose `toMatch` asserts a mismatch (wrapped in `expect(() => …).toThrow()` / `.rejects`) must pass `{ frozen: true }` — otherwise `TEST_UPDATE=1` overwrites the wrong fixture with the real output and the assertion silently stops testing anything (rule `d13w-unfrozen-negative-fixture`).
250
-
251
- ## Related
252
-
253
- [14 — Assertions](14-assertions.md) · [10 — API specs](10-api.md) · [12 — CLI specs](12-cli.md)