@jterrazz/test 16.1.1 → 18.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 (309) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +1 -2980
  3. package/dist/browser/services.js +1 -0
  4. package/dist/browser/stubs.js +1 -0
  5. package/dist/browser-shared-index.browser.js +3233 -0
  6. package/dist/checker.d.ts +6 -1
  7. package/dist/checker.js +3721 -2624
  8. package/dist/config.d.ts +40 -0
  9. package/dist/config.js +309 -0
  10. package/dist/in-place.global-setup.d.ts +12 -0
  11. package/dist/in-place.global-setup.js +21 -0
  12. package/dist/index.d.ts +1641 -3004
  13. package/dist/index.js +5095 -3835
  14. package/dist/mobile-session.global-setup.d.ts +15 -0
  15. package/dist/mobile-session.global-setup.js +31 -0
  16. package/dist/oxlint.cjs +6069 -5634
  17. package/dist/oxlint.d.cts +43 -7212
  18. package/dist/oxlint.d.ts +43 -7212
  19. package/dist/oxlint.js +3979 -5799
  20. package/dist/route-coverage.global-setup.d.ts +43 -0
  21. package/dist/route-coverage.global-setup.js +1 -0
  22. package/dist/services.d.ts +3 -0
  23. package/dist/services.global-setup.d.ts +43 -0
  24. package/dist/services.global-setup.js +1 -0
  25. package/dist/services.js +597 -0
  26. package/dist/shared-appium.adapter.js +428 -0
  27. package/dist/shared-artifacts.js +34 -0
  28. package/dist/shared-chapter.js +7 -0
  29. package/dist/shared-container.port.d.ts +31 -0
  30. package/dist/shared-derived-projects.js +73 -0
  31. package/dist/shared-device.port.d.ts +189 -0
  32. package/dist/shared-exchange.js +447 -0
  33. package/dist/shared-in-place.js +172 -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-listened-port.js +19 -0
  40. package/dist/shared-literate-plugin.js +184 -0
  41. package/dist/shared-manifest.js +1321 -0
  42. package/dist/shared-pause.js +22 -0
  43. package/dist/shared-peer.js +88 -0
  44. package/dist/shared-playwright.adapter.js +328 -0
  45. package/dist/shared-process-alive.js +13 -0
  46. package/dist/shared-process.adapter.js +74 -0
  47. package/dist/shared-record-key.js +13 -0
  48. package/dist/shared-refusal.js +35 -0
  49. package/dist/shared-resolve.js +24 -0
  50. package/dist/shared-route-coverage.global-setup.js +187 -0
  51. package/dist/shared-scenario-failure.js +198 -0
  52. package/dist/shared-serve.js +407 -0
  53. package/dist/shared-service-images.js +10 -0
  54. package/dist/shared-services.global-setup.js +174 -0
  55. package/dist/shared-session-ledger.js +305 -0
  56. package/dist/shared-setup-entry.js +12 -0
  57. package/dist/shared-source-scan.js +133 -0
  58. package/dist/shared-spec-document.js +303 -0
  59. package/dist/shared-spec-grammar.js +66 -0
  60. package/dist/shared-specifications.js +26 -0
  61. package/dist/shared-specs-modules.js +1033 -0
  62. package/dist/shared-stub-reply.js +167 -0
  63. package/dist/shared-stub-server.js +142 -0
  64. package/dist/shared-temp-root.global-setup.js +73 -0
  65. package/dist/shared-test-baseline.js +26 -0
  66. package/dist/shared-test-section.js +58 -0
  67. package/dist/shared-testcontainers.adapter.js +306 -0
  68. package/dist/shared-tokens.js +55 -0
  69. package/dist/shared-yaml-document.js +119 -0
  70. package/dist/stubs.d.ts +2 -0
  71. package/dist/stubs.js +657 -0
  72. package/dist/temp-root.global-setup.d.ts +7 -0
  73. package/dist/temp-root.global-setup.js +1 -0
  74. package/dist/vitest.d.ts +93 -164
  75. package/dist/vitest.js +382 -550
  76. package/docs/README.md +82 -36
  77. package/docs/concepts/architecture.md +118 -0
  78. package/docs/concepts/conventions.md +333 -0
  79. package/docs/guides/api-specs.md +374 -0
  80. package/docs/guides/cli-specs.md +566 -0
  81. package/docs/guides/component-specs.md +252 -0
  82. package/docs/guides/develop.md +97 -0
  83. package/docs/guides/integration-specs.md +181 -0
  84. package/docs/guides/job-specs.md +180 -0
  85. package/docs/guides/lint-specs.md +174 -0
  86. package/docs/guides/mobile-specs.md +319 -0
  87. package/docs/guides/module-tests.md +149 -0
  88. package/docs/guides/release.md +44 -0
  89. package/docs/guides/spec-documents.md +189 -0
  90. package/docs/guides/website-specs.md +413 -0
  91. package/docs/reference/assertions.md +345 -0
  92. package/docs/reference/glossary.md +20 -0
  93. package/docs/reference/linting.md +483 -0
  94. package/docs/reference/services.md +236 -0
  95. package/docs/reference/stubs.md +500 -0
  96. package/docs/reference/time-and-doubles.md +137 -0
  97. package/docs/reference/tokens.md +258 -0
  98. package/docs/reference/visitor.md +221 -0
  99. package/docs/start/getting-started.md +187 -0
  100. package/docs/start/set-up-a-project.md +228 -0
  101. package/package.json +56 -36
  102. package/schema/spec.schema.json +43 -17
  103. package/dist/appium.adapter.js +0 -489
  104. package/dist/artifacts.js +0 -32
  105. package/dist/fixtures.js +0 -110
  106. package/dist/literate.js +0 -1747
  107. package/dist/match.js +0 -804
  108. package/dist/playwright.adapter.js +0 -438
  109. package/dist/server.js +0 -160
  110. package/dist/stream.js +0 -321
  111. package/dist/substring-warning.js +0 -282
  112. package/docs/01-architecture.md +0 -95
  113. package/docs/02-developing.md +0 -351
  114. package/docs/03-testing.md +0 -495
  115. package/docs/04-operating.md +0 -101
  116. package/docs/05-module-tests.md +0 -152
  117. package/docs/06-integration.md +0 -180
  118. package/docs/07-component.md +0 -246
  119. package/docs/08-website.md +0 -404
  120. package/docs/09-mobile.md +0 -270
  121. package/docs/10-api.md +0 -264
  122. package/docs/11-jobs.md +0 -157
  123. package/docs/12-cli.md +0 -677
  124. package/docs/13-elements.md +0 -248
  125. package/docs/14-assertions.md +0 -314
  126. package/docs/15-tokens.md +0 -253
  127. package/docs/16-contracts.md +0 -438
  128. package/docs/17-services.md +0 -188
  129. package/docs/18-conventions.md +0 -332
  130. package/docs/19-linting.md +0 -512
  131. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  132. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  133. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  134. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  135. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  136. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  137. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  138. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  139. package/docs/decisions/_template.md +0 -22
  140. package/docs/reference/classes/Matcher.md +0 -120
  141. package/docs/reference/classes/ProcessHandle.md +0 -368
  142. package/docs/reference/functions/defineContract.md +0 -39
  143. package/docs/reference/functions/defineContracts.md +0 -36
  144. package/docs/reference/functions/disabled.md +0 -28
  145. package/docs/reference/functions/enabled.md +0 -20
  146. package/docs/reference/functions/focused.md +0 -26
  147. package/docs/reference/functions/mockOf.md +0 -79
  148. package/docs/reference/functions/postgres.md +0 -31
  149. package/docs/reference/functions/process.md +0 -33
  150. package/docs/reference/functions/redis.md +0 -28
  151. package/docs/reference/functions/required.md +0 -41
  152. package/docs/reference/functions/selected.md +0 -26
  153. package/docs/reference/functions/sqlite.md +0 -33
  154. package/docs/reference/functions/testId.md +0 -20
  155. package/docs/reference/functions/text.md +0 -40
  156. package/docs/reference/functions/valued.md +0 -28
  157. package/docs/reference/functions/waitUntil.md +0 -46
  158. package/docs/reference/functions/within.md +0 -29
  159. package/docs/reference/index.md +0 -196
  160. package/docs/reference/interfaces/BaseResult.md +0 -87
  161. package/docs/reference/interfaces/CallResult.md +0 -160
  162. package/docs/reference/interfaces/CaptureScope.md +0 -82
  163. package/docs/reference/interfaces/CliResult.md +0 -253
  164. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  165. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  166. package/docs/reference/interfaces/FetchResult.md +0 -180
  167. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  168. package/docs/reference/interfaces/HttpResult.md +0 -126
  169. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  170. package/docs/reference/interfaces/PageResult.md +0 -356
  171. package/docs/reference/interfaces/RenderResult.md +0 -72
  172. package/docs/reference/interfaces/RenderedText.md +0 -110
  173. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  174. package/docs/reference/interfaces/ScreenResult.md +0 -128
  175. package/docs/reference/interfaces/TableAccessor.md +0 -26
  176. package/docs/reference/interfaces/TextAccessor.md +0 -96
  177. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  178. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  179. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  180. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  181. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  182. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  183. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  184. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  185. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  186. package/docs/reference/type-aliases/CliEnv.md +0 -11
  187. package/docs/reference/type-aliases/CliHandle.md +0 -80
  188. package/docs/reference/type-aliases/CliInput.md +0 -36
  189. package/docs/reference/type-aliases/CliOutput.md +0 -46
  190. package/docs/reference/type-aliases/CliPort.md +0 -58
  191. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  192. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  193. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  194. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  195. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  196. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  197. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  198. package/docs/reference/type-aliases/Contract.md +0 -68
  199. package/docs/reference/type-aliases/ContractInput.md +0 -13
  200. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  201. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  202. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  203. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  204. package/docs/reference/type-aliases/Contracts.md +0 -48
  205. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  206. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  207. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  208. package/docs/reference/type-aliases/DevicePort.md +0 -51
  209. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  210. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  211. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  212. package/docs/reference/type-aliases/DomMount.md +0 -21
  213. package/docs/reference/type-aliases/ElementKind.md +0 -27
  214. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  215. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  216. package/docs/reference/type-aliases/ElementRef.md +0 -128
  217. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  218. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  219. package/docs/reference/type-aliases/HonoApp.md +0 -31
  220. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  221. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  222. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  223. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  224. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  225. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  226. package/docs/reference/type-aliases/Intercept.md +0 -19
  227. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  228. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  229. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  230. package/docs/reference/type-aliases/JobHandle.md +0 -34
  231. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  232. package/docs/reference/type-aliases/JobsResult.md +0 -16
  233. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  234. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  235. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  236. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  237. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  238. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  239. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  240. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  241. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  242. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  243. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  244. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  245. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  246. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  247. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  248. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  249. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  250. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  251. package/docs/reference/type-aliases/MockPort.md +0 -10
  252. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  253. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  254. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  255. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  256. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  257. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  258. package/docs/reference/type-aliases/ServerPort.md +0 -36
  259. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  260. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  261. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  262. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  263. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  264. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  265. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  266. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  267. package/docs/reference/type-aliases/SpecKind.md +0 -8
  268. package/docs/reference/type-aliases/SpecRun.md +0 -108
  269. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  270. package/docs/reference/type-aliases/SpecStream.md +0 -32
  271. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  272. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  273. package/docs/reference/type-aliases/SseEvent.md +0 -58
  274. package/docs/reference/type-aliases/StreamBody.md +0 -61
  275. package/docs/reference/type-aliases/TextFilter.md +0 -22
  276. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  277. package/docs/reference/type-aliases/Visitor.md +0 -215
  278. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  279. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  280. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  281. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  282. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  283. package/docs/reference/variables/anthropic.md +0 -19
  284. package/docs/reference/variables/banner.md +0 -10
  285. package/docs/reference/variables/button.md +0 -10
  286. package/docs/reference/variables/clock.md +0 -16
  287. package/docs/reference/variables/complementary.md +0 -10
  288. package/docs/reference/variables/component.md +0 -10
  289. package/docs/reference/variables/content.md +0 -10
  290. package/docs/reference/variables/contentinfo.md +0 -10
  291. package/docs/reference/variables/dialog.md +0 -10
  292. package/docs/reference/variables/field.md +0 -10
  293. package/docs/reference/variables/form.md +0 -10
  294. package/docs/reference/variables/heading.md +0 -10
  295. package/docs/reference/variables/http.md +0 -40
  296. package/docs/reference/variables/intercept.md +0 -11
  297. package/docs/reference/variables/link.md +0 -10
  298. package/docs/reference/variables/listitem.md +0 -10
  299. package/docs/reference/variables/main.md +0 -10
  300. package/docs/reference/variables/match.md +0 -8
  301. package/docs/reference/variables/navigation.md +0 -10
  302. package/docs/reference/variables/openai.md +0 -21
  303. package/docs/reference/variables/option.md +0 -17
  304. package/docs/reference/variables/region.md +0 -10
  305. package/docs/reference/variables/row.md +0 -10
  306. package/docs/reference/variables/search.md +0 -10
  307. package/docs/reference/variables/specification.md +0 -8
  308. package/docs/reference/variables/status.md +0 -10
  309. package/docs/reference/variables/table.md +0 -10
@@ -0,0 +1,287 @@
1
+ //#region src/stubs/exchange.d.ts
2
+ /** An observed outgoing request, reduced to what stub matchers inspect. */
3
+ type MatchableRequest = {
4
+ /** Parsed JSON body when the payload is JSON, the raw text otherwise, or `null` when absent. */
5
+ body: unknown;
6
+ /** Request headers, keyed by lowercased header name. */
7
+ headers: Record<string, string>;
8
+ /** Uppercased HTTP method of the observed request. */
9
+ method: string;
10
+ /** The request URL: fully-qualified, or origin-relative for the stub backend. */
11
+ url: string;
12
+ };
13
+ /** The request half of a stub: an absolute URL, or a path form (`/articles/{{uuid}}`) matching any origin. */
14
+ type StubRequest = {
15
+ /** Adapter name: `http` | `openai` | `anthropic`. */
16
+ adapter: string;
17
+ /** HTTP method to match. `*` matches any method. */
18
+ method: string;
19
+ /** Absolute URL (string | RegExp) or an any-origin path form (`/articles/{{uuid}}`). */
20
+ url: RegExp | string;
21
+ /** Optional request matcher; the stub only fires if this returns true. */
22
+ match?: ((request: MatchableRequest) => boolean) | undefined;
23
+ /** Header names, case-insensitive, whose presence keeps the stub from firing; `http.*` writes them from `without.headers`. */
24
+ absentHeaders?: readonly string[] | undefined;
25
+ /** Transform raw data into a provider-specific response envelope. */
26
+ wrap: (data: unknown) => StubResponse;
27
+ };
28
+ /** The response half of a stub. */
29
+ type StubResponse = {
30
+ /** HTTP status code (default: 200). */
31
+ status?: number;
32
+ /** Response body: an object is JSON, a string text, `null` empty, a `StreamBody` arrives in pieces. */
33
+ body: unknown;
34
+ headers?: Record<string, string> | undefined;
35
+ /** Delay in ms before responding (for timeout testing). */
36
+ delay?: number | undefined;
37
+ /** A transport failure instead of a reply: `fetch` rejects, as no status code can express. */
38
+ transport?: 'network-error' | undefined;
39
+ };
40
+ /** Each adapter serialises an object as JSON, so a stream declared as a plain value would arrive as `{}`: the tag tells them apart. */
41
+ declare const STREAM_BODY = "@jterrazz/test:stream";
42
+ /** A body that arrives in pieces, as `http.stream()` and `http.sse()` build it. */
43
+ type StreamBody = {
44
+ /** The pieces, written in order. */
45
+ chunks: readonly string[];
46
+ /** The `content-type` the stream is served under. */
47
+ contentType: string;
48
+ /** Milliseconds between two chunks. Default 0: everything at once. */
49
+ delayBetweenChunks: number;
50
+ kind: typeof STREAM_BODY;
51
+ };
52
+ /** A response computed from the observed request at serve time. */
53
+ type StubResponder = (request: MatchableRequest) => StubResponse;
54
+ /** A fixed `StubResponse`, or a `StubResponder` evaluated per served request. */
55
+ type StubResponseValue = StubResponder | StubResponse;
56
+ //#endregion
57
+ //#region src/stubs/stub.d.ts
58
+ /** A declared external interaction: what to match and what to reply. */
59
+ type Stub = {
60
+ /** Which outgoing call this stub speaks for. */
61
+ request: StubRequest;
62
+ /** A fixed reply, or a function of the observed request. */
63
+ response: StubResponseValue;
64
+ /** How many times it may serve; omitted is unlimited. `0` with `required` is a never stub: docs/reference/stubs.md#never-a-call-that-must-not-happen. */
65
+ times?: number;
66
+ /** Fail the chain unless it was requested: exactly `times` times when set, so `times: 0` fails it once requested. */
67
+ required?: boolean;
68
+ };
69
+ /** A composite of stubs; `.with()` derives a variant without touching the original. */
70
+ type Stubs = {
71
+ /** The flattened stubs, in selection order. */
72
+ readonly stubs: readonly Stub[];
73
+ /** Overrides replace base stubs of the same route and are prepended, so a more specific override wins first match. */
74
+ with: (...overrides: (Stub | Stub[] | Stubs)[]) => Stubs;
75
+ };
76
+ /** Any accepted stub input: one, a list, or a composite. */
77
+ type StubInput = Stub | Stub[] | Stubs;
78
+ declare function defineStub(stub: Stub): Stub;
79
+ /** Compose stubs, lists and composites, order preserved; `.with()` derives a scenario. */
80
+ declare function defineStubs(...items: StubInput[]): Stubs;
81
+ //#endregion
82
+ //#region lint/kit/project/tokens.d.ts
83
+ declare const TOKEN_KINDS: readonly ["any", "base64", "date", "duration", "email", "float", "hex", "int", "ip", "iso8601", "number", "path", "port", "semver", "sha", "string", "time", "ulid", "url", "uuid", "workdir"];
84
+ type TokenKind = (typeof TOKEN_KINDS)[number];
85
+ //#endregion
86
+ //#region src/expectations/tokens/match.d.ts
87
+ /** The frozen token vocabulary (rule `no-unknown-token`) plus the code-only kinds. */
88
+ type MatcherKind = 'any' | 'base64' | 'date' | 'duration' | 'email' | 'float' | 'hex' | 'includes' | 'int' | 'ip' | 'iso8601' | 'number' | 'path' | 'port' | 'ref' | 'regex' | 'semver' | 'sha' | 'string' | 'time' | 'ulid' | 'url' | 'uuid' | 'workdir';
89
+ /** A dynamic-value matcher, created through the `match` factories. */
90
+ declare class Matcher {
91
+ /** For kind 'includes': the substring the actual value must contain. */
92
+ readonly includes?: string | undefined;
93
+ readonly kind: MatcherKind;
94
+ /** For kind 'ref': the capture to assert inequality against. */
95
+ readonly notRef?: string | undefined;
96
+ /** For kind 'ref': the capture name. */
97
+ readonly refName?: string | undefined;
98
+ /** For kind 'regex': the pattern the actual value must match. */
99
+ readonly regex?: RegExp | undefined;
100
+ constructor(kind: MatcherKind, options?: {
101
+ includes?: string | undefined;
102
+ notRef?: string | undefined;
103
+ refName?: string | undefined;
104
+ regex?: RegExp | undefined;
105
+ });
106
+ /** Placeholder-style rendering used in failure diffs and serialization. */
107
+ toString(): string;
108
+ toJSON(): string;
109
+ }
110
+ type TypedMatcher = () => Matcher;
111
+ /** Dynamic-value matchers, the code-side mirror of the `{{token}}` grammar (`no-unknown-token`). */
112
+ type MatchFactories = Record<TokenKind, TypedMatcher> & {
113
+ includes: (substring: string) => Matcher;
114
+ ref: (name: string, options?: {
115
+ not?: string;
116
+ }) => Matcher;
117
+ regex: (regex: RegExp) => Matcher;
118
+ };
119
+ declare const match: MatchFactories;
120
+ //#endregion
121
+ //#region src/stubs/kinds/text-filter.d.ts
122
+ /** A text filter on a provider request builder: a string is exact, a `RegExp` or `match.*` loosens it. */
123
+ type TextFilter = Matcher | RegExp | string;
124
+ //#endregion
125
+ //#region src/stubs/kinds/anthropic.d.ts
126
+ type AnthropicMessagesFilter = {
127
+ model?: TextFilter;
128
+ system?: TextFilter;
129
+ user?: TextFilter;
130
+ tools?: string[];
131
+ };
132
+ /** Init options for a streamed Anthropic reply. */
133
+ type AnthropicStreamInit = {
134
+ /** Milliseconds between two events. Default 0: every event at once. */
135
+ delay?: number;
136
+ };
137
+ /** Anthropic API stub helpers. */
138
+ declare const anthropic: {
139
+ /** Match Messages API calls, optionally through a gateway URL; a string filter is exact equality. */
140
+ messages(filter?: AnthropicMessagesFilter, url?: string): StubRequest;
141
+ /** A stub the chain fails on if the subject reaches this endpoint: `http.never()`. */
142
+ never(request: StubRequest): Stub;
143
+ /** Response: data wrapped in the Messages format. */
144
+ reply(data: unknown): StubResponse;
145
+ /** Response: the text streamed as server-sent events, one `text_delta` per piece. */
146
+ stream(pieces: readonly string[], init?: AnthropicStreamInit): StubResponse;
147
+ /** Response: return an Anthropic error. */
148
+ error(status: number, message?: string): StubResponse;
149
+ /** Response: simulate a timeout. */
150
+ timeout(): StubResponse;
151
+ };
152
+ //#endregion
153
+ //#region src/stubs/kinds/http-filter.d.ts
154
+ /** Request filters; a request matches when every provided field matches. */
155
+ type HttpStubFilter = {
156
+ /** An object is a deep subset match (leaves may be `match.*`); a string is containment, a RegExp `test()`s the raw body. */
157
+ body?: object | RegExp | string;
158
+ /** Header subset. Names are case-insensitive; string = exact value, RegExp = `test()`. */
159
+ headers?: Record<string, RegExp | string>;
160
+ /** Query-param subset. string = exact value, RegExp = `test()`. */
161
+ query?: Record<string, RegExp | string>;
162
+ /** Headers the request must not carry; a name present with any value keeps the stub from firing. */
163
+ without?: {
164
+ headers?: readonly string[];
165
+ };
166
+ };
167
+ //#endregion
168
+ //#region src/stubs/kinds/http.d.ts
169
+ /** Init options for a streamed reply. */
170
+ type HttpStreamInit = {
171
+ /** The `content-type` the stream is served under. Default `application/octet-stream`. */
172
+ contentType?: string;
173
+ /** Milliseconds between two chunks. Default 0: every chunk at once. */
174
+ delay?: number;
175
+ /** Response headers, merged over the builder's own. */
176
+ headers?: Record<string, string>;
177
+ /** HTTP status code. Default 200. */
178
+ status?: number;
179
+ };
180
+ /** One server-sent event, as `http.sse()` frames it. */
181
+ type SseEvent = {
182
+ /** The `data:` payload. An object is serialised as JSON; a string is sent as-is. */
183
+ data: unknown;
184
+ /** The `event:` name. Omitted for an unnamed (default `message`) event. */
185
+ event?: string;
186
+ /** The `id:` the client echoes as `Last-Event-ID` when it reconnects. */
187
+ id?: string;
188
+ /** The `retry:` hint, in milliseconds. */
189
+ retry?: number;
190
+ };
191
+ /** Init options shared by the response builders. */
192
+ type HttpResponseInit = {
193
+ /** Delay in ms before responding (for timeout testing). */
194
+ delay?: number;
195
+ /** Response headers, merged over the builder's own. */
196
+ headers?: Record<string, string>;
197
+ /** HTTP status code. */
198
+ status?: number;
199
+ };
200
+ /** Generic HTTP stub helpers: an absolute URL, or a path form (`/articles/{{uuid}}`) matching any origin. */
201
+ declare const http: {
202
+ any(url: RegExp | string, filter?: HttpStubFilter): StubRequest;
203
+ delete(url: RegExp | string, filter?: HttpStubFilter): StubRequest;
204
+ get(url: RegExp | string, filter?: HttpStubFilter): StubRequest;
205
+ patch(url: RegExp | string, filter?: HttpStubFilter): StubRequest;
206
+ post(url: RegExp | string, filter?: HttpStubFilter): StubRequest;
207
+ put(url: RegExp | string, filter?: HttpStubFilter): StubRequest;
208
+ /** A stub the chain fails on if the subject reaches its route: `required` with `times: 0`. */
209
+ never(request: StubRequest): Stub;
210
+ /** Response: a body-less reply (204 by default). */
211
+ empty(status?: number): StubResponse;
212
+ /** Response: an error status. Without a body, `{ error: 'HTTP <status>' }`. */
213
+ error(status: number, body?: unknown): StubResponse;
214
+ /** Response: a JSON body (200 by default). */
215
+ json(body: unknown, init?: HttpResponseInit): StubResponse;
216
+ /** Response: no response at all; `fetch` rejects, as when no server is running. */
217
+ unreachable(): StubResponse;
218
+ /** Response: a body that arrives in pieces, written in order. */
219
+ stream(chunks: readonly string[], init?: HttpStreamInit): StubResponse;
220
+ /** Response: a server-sent event stream, served as `text/event-stream`. */
221
+ sse(events: readonly SseEvent[], init?: Omit<HttpStreamInit, "contentType">): StubResponse;
222
+ /** Response: a text body, served as `text/plain` (200 by default). */
223
+ text(body: string, init?: HttpResponseInit): StubResponse;
224
+ };
225
+ //#endregion
226
+ //#region src/stubs/kinds/openai.d.ts
227
+ type OpenAIChatFilter = {
228
+ model?: TextFilter;
229
+ system?: TextFilter;
230
+ user?: TextFilter;
231
+ tools?: string[];
232
+ temperature?: number;
233
+ };
234
+ type OpenAIResponsesFilter = {
235
+ model?: TextFilter;
236
+ system?: TextFilter;
237
+ user?: TextFilter;
238
+ tools?: string[];
239
+ };
240
+ type OpenAIStreamInit = {
241
+ /** The API whose stream is served: Chat Completions (default) or Responses. */
242
+ api?: 'chat' | 'responses';
243
+ /** Milliseconds between two events. Default 0: every event at once. */
244
+ delay?: number;
245
+ /** The tokens the stream reports it spent: a Chat Completions stream ends on a usage chunk, as `stream_options.include_usage` asks, and a Responses stream carries them in its completed response. Default 10 and 10. */
246
+ usage?: {
247
+ completionTokens: number;
248
+ promptTokens: number;
249
+ };
250
+ };
251
+ /** OpenAI API stub helpers. */
252
+ declare const openai: {
253
+ /** Match Chat Completions calls, at `url` where the client reaches another host; a string filter is exact equality, a RegExp or `match.includes` loosens it. */
254
+ chat(filter?: OpenAIChatFilter, url?: string): StubRequest;
255
+ /** Match Responses API calls; a JSON file is wrapped in the Responses envelope. */
256
+ responses(filter?: OpenAIResponsesFilter, url?: string): StubRequest;
257
+ /** A stub the chain fails on if the subject reaches this endpoint: `http.never()`. */
258
+ never(request: StubRequest): Stub;
259
+ /** Response: data wrapped in the Chat Completions format. */
260
+ reply(data: unknown): StubResponse;
261
+ /** Response: the text streamed as server-sent events; `api` picks Chat Completions (default) or Responses. */
262
+ stream(pieces: readonly string[], init?: OpenAIStreamInit): StubResponse;
263
+ /** Response: return an OpenAI error. */
264
+ error(status: number, message?: string): StubResponse;
265
+ /** Response: return malformed content. */
266
+ malformed(content: string): StubResponse;
267
+ /** Response: simulate a timeout. */
268
+ timeout(): StubResponse;
269
+ };
270
+ //#endregion
271
+ //#region src/stubs/intercept/intercept.d.ts
272
+ /** Disposed at the end of the declaring scope: handlers dropped, an unmatched request thrown. */
273
+ type InterceptScope = AsyncDisposable;
274
+ /** What a scope may state about the network it declares. */
275
+ type InterceptOptions = {
276
+ /** The base a relative request resolves against for the life of the scope; under node `fetch('/api/posts')` cannot parse. */
277
+ origin?: string;
278
+ };
279
+ /** The module-scope network double; awaited, so the network double listens before the subject runs. */
280
+ type Intercept = ((stubs: StubInput, options?: InterceptOptions) => Promise<InterceptScope>) & ((request: StubRequest, response: StubResponder | StubResponse, options?: InterceptOptions) => Promise<InterceptScope>);
281
+ //#endregion
282
+ //#region src/stubs/delivery/same-process/intercept.d.ts
283
+ /** The module-scope network double, on msw's node server. */
284
+ declare const intercept: Intercept;
285
+ //#endregion
286
+ export { StubRequest as C, StubResponseValue as E, StreamBody as S, StubResponse as T, StubInput as _, openai as a, defineStubs as b, SseEvent as c, anthropic as d, TextFilter as f, Stub as g, match as h, InterceptScope as i, http as l, MatcherKind as m, Intercept as n, HttpResponseInit as o, Matcher as p, InterceptOptions as r, HttpStreamInit as s, intercept as t, HttpStubFilter as u, Stubs as v, StubResponder as w, MatchableRequest as x, defineStub as y };
287
+ //# sourceMappingURL=shared-index.stubs.d.ts.map
@@ -0,0 +1,17 @@
1
+ //#region lint/kit/project/json-shape.ts
2
+ /** `JSON.parse` answers `any`: these read a parsed value field by field, and a field of another shape reads as absent. */
3
+ function isRecord(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5
+ }
6
+ /** What a text that is no JSON stands for is the caller's to say. */
7
+ function parsedJson(text, otherwise) {
8
+ try {
9
+ return JSON.parse(text);
10
+ } catch {
11
+ return otherwise;
12
+ }
13
+ }
14
+ //#endregion
15
+ export { parsedJson as n, isRecord as t };
16
+
17
+ //# sourceMappingURL=shared-json-shape.js.map
@@ -0,0 +1,71 @@
1
+ //#region src/visitor/targets/roles.ts
2
+ const stated = (options) => options?.exact === void 0 ? {} : { exact: options.exact };
3
+ const named = (kind) => (name, options) => ({
4
+ kind,
5
+ name,
6
+ ...stated(options)
7
+ });
8
+ const optionallyNamed = (kind) => (name, options) => ({
9
+ kind,
10
+ ...name === void 0 ? {} : { name },
11
+ ...stated(options)
12
+ });
13
+ /** A button (or element with the button role), by accessible name. */
14
+ const button = named("button");
15
+ /** A form field, by label. */
16
+ const field = named("field");
17
+ /** A heading, by accessible name. */
18
+ const heading = named("heading");
19
+ /** A link, by accessible name. */
20
+ const link = named("link");
21
+ /** An element containing the given text. */
22
+ const content = named("text");
23
+ /** An option of a select or listbox, by label; asserted by presence, since a collapsed select's options have no box. */
24
+ const option = named("option");
25
+ /** A modal or non-modal `<dialog>`, by accessible name. A closed one is absent. */
26
+ const dialog = optionallyNamed("dialog");
27
+ /** A live region announcing a result: `role="status"`, an `<output>`. */
28
+ const status = optionallyNamed("status");
29
+ /** A table, by its caption or accessible name. */
30
+ const table = optionallyNamed("table");
31
+ /** A row of a table or grid, by the text of its cells. */
32
+ const row = optionallyNamed("row");
33
+ /** An item of a list, by its text. */
34
+ const listitem = optionallyNamed("listitem");
35
+ /** The escape hatch: an element by `data-testid`. Prefer user-facing targets. */
36
+ const testId = (id) => ({
37
+ kind: "testId",
38
+ name: id
39
+ });
40
+ //#endregion
41
+ //#region src/visitor/targets/landmarks.ts
42
+ /** The `banner` landmark: the page header. */
43
+ const banner = optionallyNamed("banner");
44
+ /** The `complementary` landmark: an `<aside>`, a sidebar. */
45
+ const complementary = optionallyNamed("complementary");
46
+ /** The `contentinfo` landmark: the page footer. */
47
+ const contentinfo = optionallyNamed("contentinfo");
48
+ /** The `form` landmark: a form carrying an accessible name. */
49
+ const form = optionallyNamed("form");
50
+ /** The `main` landmark: the primary content of the document. */
51
+ const main = optionallyNamed("main");
52
+ /** The `navigation` landmark: a `<nav>`. Name it when a page has several. */
53
+ const navigation = optionallyNamed("navigation");
54
+ /** The `region` landmark: a `<section>` carrying an accessible name. */
55
+ const region = optionallyNamed("region");
56
+ /** The `search` landmark. */
57
+ const search = optionallyNamed("search");
58
+ /** Restrict a target to the inside of another: `click(within(navigation(), link('Articles')))`. */
59
+ const within = (scope, target) => ({
60
+ ...target,
61
+ scope: target.scope ? within(scope, target.scope) : scope
62
+ });
63
+ function scopeChain(target) {
64
+ const chain = [];
65
+ for (let current = target; current; current = current.scope) chain.unshift(current);
66
+ return chain;
67
+ }
68
+ //#endregion
69
+ export { testId as S, listitem as _, main as a, status as b, scopeChain as c, button as d, content as f, link as g, heading as h, form as i, search as l, field as m, complementary as n, navigation as o, dialog as p, contentinfo as r, region as s, banner as t, within as u, option as v, table as x, row as y };
70
+
71
+ //# sourceMappingURL=shared-landmarks.js.map
@@ -0,0 +1,68 @@
1
+ import { t as isRecord } from "./shared-json-shape.js";
2
+ import { u as VITEST_ARTIFACTS_DIR } from "./shared-artifacts.js";
3
+ import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ //#region src/expectations/in-place/update.ts
7
+ /** `TEST_UPDATE=1` or vitest's `-u` / `--update`. */
8
+ function shouldUpdateSnapshots() {
9
+ if (process.env.TEST_UPDATE === "1") return true;
10
+ return process.argv.includes("-u") || process.argv.includes("--update");
11
+ }
12
+ const UPDATE_HINT = "Run with TEST_UPDATE=1 (or vitest -u) to create it.";
13
+ const FROZEN_HINT = "This golden is `{ frozen }`: update mode never writes it. Write the file by hand, or drop `{ frozen }` for one TEST_UPDATE=1 run and put it back.";
14
+ function missingHint(frozen) {
15
+ return frozen ? FROZEN_HINT : UPDATE_HINT;
16
+ }
17
+ //#endregion
18
+ //#region src/specifications/api/routes/ledger.ts
19
+ const isLedger = (value) => isRecord(value) && Array.isArray(value.reached) && (value.declared === void 0 || Array.isArray(value.declared));
20
+ function entriesOf(directory) {
21
+ try {
22
+ return readdirSync(directory, { withFileTypes: true });
23
+ } catch {
24
+ return [];
25
+ }
26
+ }
27
+ const ROUTES_DIR = `${VITEST_ARTIFACTS_DIR}/routes`;
28
+ function shardLedgersDir(shard) {
29
+ return `${ROUTES_DIR}/shard-${shard.index}-${shard.count}`;
30
+ }
31
+ function clearLedgers(directory) {
32
+ rmSync(directory, {
33
+ force: true,
34
+ recursive: true
35
+ });
36
+ }
37
+ function writeLedger(directory, ledger) {
38
+ mkdirSync(directory, { recursive: true });
39
+ writeFileSync(join(directory, `${randomUUID()}.json`), `${JSON.stringify(ledger)}\n`);
40
+ }
41
+ const parsed = (path) => JSON.parse(readFileSync(path, "utf8"));
42
+ function readLedgers(directory) {
43
+ return entriesOf(directory).filter((entry) => entry.name.endsWith(".json")).map((entry) => parsed(join(directory, entry.name))).filter((ledger) => isLedger(ledger));
44
+ }
45
+ const PARTIAL_MARK = "partial";
46
+ function markPartial(directory) {
47
+ mkdirSync(directory, { recursive: true });
48
+ writeFileSync(join(directory, PARTIAL_MARK), "");
49
+ }
50
+ function measure(ledgers) {
51
+ const tables = ledgers.map((ledger) => ledger.declared);
52
+ return {
53
+ declared: tables.every((table) => table !== void 0) ? tables.flat() : void 0,
54
+ reached: ledgers.flatMap((ledger) => ledger.reached)
55
+ };
56
+ }
57
+ async function routesLedger() {
58
+ try {
59
+ const { inject } = await import("vitest");
60
+ return inject("routesLedger");
61
+ } catch {
62
+ return;
63
+ }
64
+ }
65
+ //#endregion
66
+ export { readLedgers as a, writeLedger as c, measure as i, missingHint as l, clearLedgers as n, routesLedger as o, markPartial as r, shardLedgersDir as s, ROUTES_DIR as t, shouldUpdateSnapshots as u };
67
+
68
+ //# sourceMappingURL=shared-ledger.js.map
@@ -0,0 +1,19 @@
1
+ import { t as invariant } from "./shared-refusal.js";
2
+ //#region src/chain/listened-port.ts
3
+ /** Listens, on `host` when one is named, and answers the port it got; a port another server holds rejects with its error. */
4
+ async function listenedPort(server, { host, port = 0 } = {}) {
5
+ await new Promise((resolve, reject) => {
6
+ server.once("error", reject);
7
+ server.listen(port, host, () => {
8
+ server.off("error", reject);
9
+ resolve();
10
+ });
11
+ });
12
+ const address = server.address();
13
+ if (address === null || typeof address === "string") throw invariant("a server listening on a TCP port answered no port");
14
+ return address.port;
15
+ }
16
+ //#endregion
17
+ export { listenedPort as t };
18
+
19
+ //# sourceMappingURL=shared-listened-port.js.map
@@ -0,0 +1,184 @@
1
+ import { n as refusal } from "./shared-refusal.js";
2
+ import { c as isRelativePath, s as SpecSyntaxError } from "./shared-spec-grammar.js";
3
+ import { i as readSpecFile, o as dedent } from "./shared-spec-document.js";
4
+ import { readFileSync } from "node:fs";
5
+ import { basename, isAbsolute, resolve } from "node:path";
6
+ //#region src/disk/spec-path.ts
7
+ const DISK_KEY = "@jterrazz/test:disk";
8
+ const DISK = Symbol.for(DISK_KEY);
9
+ /** Name one file, by its path from the spec file. */
10
+ function file(path) {
11
+ requireRelativePath("file", path);
12
+ if (path.endsWith("/")) {
13
+ const bare = path.replace(/\/+$/u, "");
14
+ throw new TypeError(refusal("disk-named-by-relative-path-at-runtime", {
15
+ fix: `name the directory with directory(${JSON.stringify(path)}), or the file without its /: file(${JSON.stringify(bare)})`,
16
+ found: `file(${JSON.stringify(path)}), a path ending in /, which is a directory's`
17
+ }));
18
+ }
19
+ return diskValue("file", path);
20
+ }
21
+ function diskValue(brand, path) {
22
+ const value = {
23
+ [DISK]: brand,
24
+ path
25
+ };
26
+ Object.defineProperty(value, Symbol.match, { value: () => {
27
+ throw new TypeError(refusal("one-verb-per-meaning", {
28
+ fix: `wrap the string to golden it, \`expect(text(value)).toMatch(${brand}(...))\`, or compare it to a value, \`toMatch('...')\``,
29
+ found: `toMatch(${brand}(${JSON.stringify(path)})) on a string, a value with no golden`
30
+ }));
31
+ } });
32
+ return Object.freeze(value);
33
+ }
34
+ /** Name one directory, by its path from the spec file, ending in `/`. */
35
+ function directory(path) {
36
+ requireRelativePath("directory", path);
37
+ if (!path.endsWith("/")) throw new TypeError(refusal("disk-named-by-relative-path-at-runtime", {
38
+ fix: `end it in /: directory(${JSON.stringify(`${path}/`)}), or name a file with file(${JSON.stringify(path)})`,
39
+ found: `directory(${JSON.stringify(path)}), a path that does not end in /`
40
+ }));
41
+ return diskValue("directory", path);
42
+ }
43
+ function isDiskFile(value) {
44
+ return brandOf(value) === "file";
45
+ }
46
+ function isDiskDirectory(value) {
47
+ return brandOf(value) === "directory";
48
+ }
49
+ function isDiskTarget(value) {
50
+ return brandOf(value) !== void 0;
51
+ }
52
+ function brandOf(value) {
53
+ if (value === null || typeof value !== "object") return;
54
+ const brand = Reflect.get(value, DISK);
55
+ return brand === "file" || brand === "directory" ? brand : void 0;
56
+ }
57
+ function spelled(value) {
58
+ if (isDiskTarget(value)) return `${brandOf(value)}(${JSON.stringify(value.path)})`;
59
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
60
+ }
61
+ function requireDiskFile(verb, value, extension = "") {
62
+ if (!isDiskFile(value)) throw new TypeError(refusal("disk-named-by-relative-path-at-runtime", {
63
+ fix: `name one file: ${verb}(file('./${exampleOf(verb, extension)}'))`,
64
+ found: isDiskDirectory(value) ? `${verb}(${spelled(value)}), a directory where one file is read` : `${verb}(${spelled(value)}), a value where a file is read`
65
+ }));
66
+ if (!value.path.endsWith(extension)) throw new TypeError(refusal("disk-named-by-relative-path-at-runtime", {
67
+ fix: `name the ${extension} file ${verb}() reads: ${verb}(file('./${exampleOf(verb, extension)}'))`,
68
+ found: `${verb}(${spelled(value)}), a file not ending in ${extension}`
69
+ }));
70
+ return value;
71
+ }
72
+ function requireDiskTarget(verb, value) {
73
+ if (!isDiskTarget(value)) throw new TypeError(refusal("disk-named-by-relative-path-at-runtime", {
74
+ fix: `name the disk: ${verb}(file('./<path>')) for a file, ${verb}(directory('./<path>/')) for a directory`,
75
+ found: `${verb}(${spelled(value)}), a value where the disk is read`
76
+ }));
77
+ return value;
78
+ }
79
+ function exampleOf(verb, extension) {
80
+ return {
81
+ fixture: "__fixtures__/config.json",
82
+ request: "__requests__/create.http",
83
+ run: "case.spec.yaml",
84
+ seed: "__seeds__/rows.sql"
85
+ }[verb] ?? `__expected__/output${extension || ".txt"}`;
86
+ }
87
+ function asRelative(path) {
88
+ const bare = typeof path === "string" ? path.replace(/^\/+/u, "") : "";
89
+ return `./${bare === "" ? "__expected__/output.txt" : bare}`;
90
+ }
91
+ function requireRelativePath(call, path) {
92
+ if (typeof path !== "string" || !isRelativePath(path)) throw new TypeError(refusal("disk-named-by-relative-path-at-runtime", {
93
+ fix: `start it with ./ or ../, from the spec file: ${call}('${asRelative(path)}')`,
94
+ found: `${call}(${JSON.stringify(path)}), a path that is not relative to the spec file`
95
+ }));
96
+ }
97
+ //#endregion
98
+ //#region src/disk/resolved-spec-path.ts
99
+ function resolveSpecPath(verb, path, specDir) {
100
+ requireRelativePath(verb, path);
101
+ return resolve(specDir, path);
102
+ }
103
+ //#endregion
104
+ //#region src/expectations/in-place/render.ts
105
+ function escapeTemplate(text) {
106
+ return text.replaceAll("\\", String.raw`\\`).replaceAll("`", "\\`").replaceAll("${", "\\${");
107
+ }
108
+ function renderText(text, indent) {
109
+ if (!text.includes("\n")) return `\`${escapeTemplate(text)}\``;
110
+ const inner = `${indent} `;
111
+ const lines = text.split("\n");
112
+ const last = String(lines.pop());
113
+ const block = `\n${lines.map((line) => line === "" ? "" : `${inner}${line}`).join("\n")}\n${last === "" ? indent : `${inner}${last}`}`;
114
+ return dedent(block) === text ? `\`${escapeTemplate(block)}\`` : JSON.stringify(text);
115
+ }
116
+ const NUMBER_MARK = "@@in-place-number:";
117
+ const GROUPED_FROM = 1e4;
118
+ function grouped(value) {
119
+ return typeof value === "number" && Number.isSafeInteger(value) && Math.abs(value) >= GROUPED_FROM ? `${NUMBER_MARK}${String(value).replaceAll(/\B(?=(?:\d{3})+$)/gu, "_")}` : value;
120
+ }
121
+ const MARKED = new RegExp(`"${NUMBER_MARK}(?<digits>-?[\\d_]+)"`, "gu");
122
+ const literal = (value, space) => JSON.stringify(value, (_key, item) => grouped(item), space)?.replaceAll(MARKED, "$<digits>");
123
+ function renderValue(value, indent) {
124
+ return (literal(value, 4) ?? "undefined").split("\n").join(`\n${indent}`);
125
+ }
126
+ function renderRows(rows, indent) {
127
+ if (rows.length === 0) return "[]";
128
+ return `[\n${rows.map((row) => `${indent} [${row.map((cell) => literal(cell) ?? "null").join(", ")}],`).join("\n")}\n${indent}]`;
129
+ }
130
+ //#endregion
131
+ //#region src/specifications/cli/documents/literate-plugin.ts
132
+ const SPEC_FILE = /\.spec\.yaml(?:\?.*)?$/u;
133
+ function grammarRefusal(error, displayPath) {
134
+ return new Error(refusal({
135
+ fix: "write the key the grammar names; the reason prints the accepted set",
136
+ found: `${displayPath}:${error.line}: ${error.reason}`,
137
+ read: "docs/reference/linting.md#document-follows-its-grammar",
138
+ why: "A spec document follows one grammar, read by the parser the runner and the linter share: closed keys, `description:` and `runs:` required, `command:` and `exit:` in every run."
139
+ }), { cause: error });
140
+ }
141
+ function readDocument(content, displayPath) {
142
+ try {
143
+ return readSpecFile(content, displayPath);
144
+ } catch (error) {
145
+ throw error instanceof SpecSyntaxError ? grammarRefusal(error, displayPath) : error;
146
+ }
147
+ }
148
+ function literateModule(content, filePath, runnerPath) {
149
+ const { description } = readDocument(content, filePath).document;
150
+ return [
151
+ "import { test } from 'vitest';",
152
+ `import { cli } from ${JSON.stringify(runnerPath)};`,
153
+ "",
154
+ `test(${JSON.stringify(description)}, async () => {`,
155
+ ` await cli.run({ [Symbol.for(${JSON.stringify(DISK_KEY)})]: 'file', path: ${JSON.stringify(`./${basename(filePath)}`)} });`,
156
+ "});",
157
+ ""
158
+ ].join("\n");
159
+ }
160
+ function cleanId(id) {
161
+ return id.split("?")[0] ?? id;
162
+ }
163
+ function literate(options) {
164
+ const include = options.include ?? ["**/*.spec.yaml"];
165
+ let root = process.cwd();
166
+ return {
167
+ config: () => ({ test: { include } }),
168
+ configResolved: (resolved) => {
169
+ ({root} = resolved);
170
+ },
171
+ enforce: "pre",
172
+ load: (id) => {
173
+ if (!SPEC_FILE.test(id)) return null;
174
+ const filePath = cleanId(id);
175
+ const runnerPath = isAbsolute(options.runner) ? options.runner : resolve(root, options.runner);
176
+ return literateModule(readFileSync(filePath, "utf8"), filePath, runnerPath);
177
+ },
178
+ name: "jterrazz-test:literate"
179
+ };
180
+ }
181
+ //#endregion
182
+ export { renderValue as a, file as c, isDiskTarget as d, requireDiskFile as f, renderText as i, isDiskDirectory as l, literate as n, resolveSpecPath as o, requireDiskTarget as p, renderRows as r, directory as s, grammarRefusal as t, isDiskFile as u };
183
+
184
+ //# sourceMappingURL=shared-literate-plugin.js.map