@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,189 @@
1
+ import { Scalar } from "yaml";
2
+ //#region lint/kit/project/spec-grammar.d.ts
3
+ /** One `env:` entry: a bare word naming a registered set, or a `KEY=value` pair. */
4
+ type SpecEnvToken = {
5
+ key: string;
6
+ kind: 'pair';
7
+ line: number;
8
+ value: string;
9
+ } | {
10
+ kind: 'set';
11
+ line: number;
12
+ name: string;
13
+ };
14
+ /** One `serve:` entry: a registered server plus the extra env it is started with. */
15
+ type SpecServeEntry = {
16
+ env: Record<string, string>;
17
+ line: number;
18
+ name: string;
19
+ };
20
+ /** One `fixture:` entry: a path with the line it was declared on. */
21
+ type SpecFixture = {
22
+ line: number;
23
+ path: string;
24
+ };
25
+ /** An expected stream: its text, and the first line of that text in the file. */
26
+ type SpecStream = {
27
+ /** 1-based line the scalar's content starts on, where a token defect is reported. */
28
+ line: number;
29
+ text: string;
30
+ };
31
+ /** One `files:` assertion, keyed by a workdir-relative path. */
32
+ type SpecFileAssertion = {
33
+ contains: SpecStream[];
34
+ equals: null | SpecStream;
35
+ kind: 'content';
36
+ line: number;
37
+ path: string;
38
+ } | {
39
+ kind: 'absent';
40
+ line: number;
41
+ path: string;
42
+ } | {
43
+ kind: 'exists';
44
+ line: number;
45
+ path: string;
46
+ };
47
+ /** One run: a command, what it must exit with, and what it must have produced. */
48
+ type SpecRun = {
49
+ command: string;
50
+ /** 1-based line of the `command:` key, the frame a failure points at. */
51
+ commandLine: number;
52
+ exitCode: number;
53
+ files: SpecFileAssertion[];
54
+ /** `null` when the key is absent, which asserts an empty stream. */
55
+ stderr: null | SpecStream;
56
+ /** Written to the child then closed; `null` keeps the empty-pipe EOF. */
57
+ stdin: null | string;
58
+ stdout: null | SpecStream;
59
+ timeout: null | number;
60
+ /** Only on the last run: a long-running command ends the session. */
61
+ waitFor: null | string;
62
+ };
63
+ /** A parsed `<case>.spec.yaml`. */
64
+ type SpecDocument = {
65
+ /** The vitest test title. */
66
+ description: string;
67
+ /** 1-based line of the `description:` key. */
68
+ descriptionLine: number;
69
+ env: SpecEnvToken[];
70
+ fixtures: SpecFixture[];
71
+ runs: SpecRun[];
72
+ serve: SpecServeEntry[];
73
+ };
74
+ //#endregion
75
+ //#region src/visitor/targets/target.d.ts
76
+ /** The ARIA landmark roles: the containers a refusal can offer by name as a scope. */
77
+ type LandmarkKind = 'banner' | 'complementary' | 'contentinfo' | 'form' | 'main' | 'navigation' | 'region' | 'search';
78
+ /** The element kinds a visitor acts on. */
79
+ type ElementKind = 'button' | 'dialog' | 'field' | 'heading' | 'link' | 'listitem' | 'option' | 'row' | 'status' | 'table' | 'testId' | 'text';
80
+ /** What a visitor acts on, named as its user sees it; it must designate exactly one element (`target-matches-one-element`). */
81
+ type Target = {
82
+ /** Match the accessible name whole; absent means the default, exact. */
83
+ exact?: boolean;
84
+ /** Assert focus rather than presence, built by `focused(target)`. */
85
+ focused?: boolean;
86
+ /** Accepting input or not, built by `disabled(x)` / `enabled(x)`. */
87
+ disabled?: boolean;
88
+ kind: ElementKind | LandmarkKind;
89
+ name?: string;
90
+ /** The chosen option or not, built by `selected(x)`; read from `:checked`. */
91
+ selected?: boolean;
92
+ /** The field's live value, built by `valued(field, value)`. */
93
+ value?: string;
94
+ /** Search inside another target, built by `within(scope, target)`. */
95
+ scope?: Target;
96
+ };
97
+ /** Options accepted by every named target. */
98
+ type TargetOptions = {
99
+ /** Match the accessible name whole; `true` by default, `{ exact: false }` for a name with a variable part. */
100
+ exact?: boolean;
101
+ };
102
+ type NamedTargetFactory = (name: string, options?: TargetOptions) => Target;
103
+ type OptionallyNamedTargetFactory = (name?: string, options?: TargetOptions) => Target;
104
+ //#endregion
105
+ //#region src/visitor/verbs.d.ts
106
+ /** The verbs a page is acted on through; every action auto-waits, and `see()` and `gone()` are the synchronization. */
107
+ type VisitorVerbs = {
108
+ check: (target: Target) => Promise<void>;
109
+ click: (target: Target) => Promise<void>;
110
+ fill: (target: Target, value: string) => Promise<void>;
111
+ /** Wait until the element is gone, hidden or never in the document. */
112
+ gone: (target: Target) => Promise<void>;
113
+ hover: (target: Target) => Promise<void>;
114
+ /** Press a key (e.g. `Enter`). */
115
+ press: (key: string) => Promise<void>;
116
+ /** Wait until the element is visible. */
117
+ see: (target: Target) => Promise<void>;
118
+ select: (target: Target, option: string) => Promise<void>;
119
+ };
120
+ //#endregion
121
+ //#region src/specifications/mobile/device/device.port.d.ts
122
+ type MobileDevice = {
123
+ /** Simulator name, matched exactly (`iPhone 17`). */
124
+ name: string;
125
+ /** OS narrowing when the name exists on several runtimes: `'26.5'` or `'iOS 26.5'`. */
126
+ os?: string;
127
+ /** Explicit UDID; skips name/os resolution entirely. */
128
+ udid?: string;
129
+ };
130
+ /** The element kinds a mobile screen designates, each compiled to an iOS predicate; every other kind is refused. */
131
+ type MobileElementKind = 'button' | 'field' | 'testId' | 'text';
132
+ /** One candidate of an ambiguous target, as the refusal lists it. */
133
+ type MobileElementMatch = {
134
+ /** The accessibility identifier (`testId()` target), when one is set. */
135
+ identifier?: string;
136
+ /** The accessible label, whitespace-collapsed and truncated. */
137
+ label?: string;
138
+ /** Element type without the `XCUIElementType` prefix: `Button`, `StaticText`. */
139
+ type: string;
140
+ /** The element value (text-field content, adjustable value), when present. */
141
+ value?: string;
142
+ };
143
+ /** The verbs a mobile scenario is handed: a page's `see` and `fill`, and a `tap`; each polls until a visible match exists. */
144
+ type MobileVisitor = Pick<VisitorVerbs, 'fill' | 'see'> & {
145
+ tap: VisitorVerbs['click'];
146
+ };
147
+ /** The behavior of an open, the When of the spec; assertions stay in the Then. */
148
+ type MobileScenario = (visitor: MobileVisitor) => Promise<void>;
149
+ /** One node of the projected accessibility tree: noise wrappers dropped, `XCUIElementType` prefix removed. */
150
+ type ScreenNode = {
151
+ children?: ScreenNode[];
152
+ /** The accessibility identifier, only when it differs from the label. */
153
+ identifier?: string;
154
+ label?: string;
155
+ /** Element type without the `XCUIElementType` prefix: `Button`, `StaticText`. */
156
+ type: string;
157
+ value?: string;
158
+ };
159
+ /** The screen captured by an open, final state: the projected tree and the visible texts. */
160
+ type DeviceScreen = {
161
+ texts: string[];
162
+ tree: ScreenNode;
163
+ };
164
+ /** Per-open options forwarded to the device session. */
165
+ type DeviceOpenOptions = {
166
+ /** The app under test, terminated and relaunched for a fresh, deterministic state. */
167
+ bundleId: string;
168
+ /** Deep link applied after the relaunch (`news://events`); absent opens the app plainly. */
169
+ deepLink?: string | undefined;
170
+ /** The interaction scenario to run after launch; the capture reflects the final state. */
171
+ scenario?: MobileScenario | undefined;
172
+ };
173
+ /** How long a device session waits, in milliseconds; each field falls back to the default. */
174
+ type DeviceTimeouts = {
175
+ /** How long every verb polls for a visible match. Default `30_000`. */
176
+ action?: number | undefined;
177
+ /** How long the first session waits for WebDriverAgent to build and launch. Default `240_000`. */
178
+ launch?: number | undefined;
179
+ };
180
+ /** The device a mobile runner drives: one driver session, each open relaunching the app. */
181
+ type DevicePort = {
182
+ /** End the driver session, unless it is the run's (idempotent). */
183
+ close: () => Promise<void>;
184
+ /** Relaunch the app, apply the deep link, run the scenario, capture the final screen. */
185
+ open: (options: DeviceOpenOptions) => Promise<DeviceScreen>;
186
+ };
187
+ //#endregion
188
+ export { SpecServeEntry as C, SpecRun as S, TargetOptions as _, MobileDevice as a, SpecFileAssertion as b, MobileScenario as c, VisitorVerbs as d, ElementKind as f, Target as g, OptionallyNamedTargetFactory as h, DeviceTimeouts as i, MobileVisitor as l, NamedTargetFactory as m, DevicePort as n, MobileElementKind as o, LandmarkKind as p, DeviceScreen as r, MobileElementMatch as s, DeviceOpenOptions as t, ScreenNode as u, SpecDocument as v, SpecStream as w, SpecFixture as x, SpecEnvToken as y };
189
+ //# sourceMappingURL=shared-device.port.d.ts.map
@@ -0,0 +1,447 @@
1
+ import { t as isRecord } from "./shared-json-shape.js";
2
+ import { n as refusal } from "./shared-refusal.js";
3
+ import { t as TOKEN_KINDS } from "./shared-tokens.js";
4
+ //#region src/expectations/tokens/capture-scope.ts
5
+ /** Named captures of `match.ref()` / `{{type#ref}}`, one scope per spec result. */
6
+ var CaptureScope = class CaptureScope {
7
+ values = /* @__PURE__ */ new Map();
8
+ /** The spec's working directory when known (cli mode), for `{{workdir}}`. */
9
+ workdir;
10
+ constructor(workdir) {
11
+ this.workdir = workdir;
12
+ }
13
+ get(name) {
14
+ return this.values.get(name);
15
+ }
16
+ has(name) {
17
+ return this.values.has(name);
18
+ }
19
+ set(name, value) {
20
+ this.values.set(name, value);
21
+ }
22
+ /** A copy to try a comparison on: its captures stay there until adopted. */
23
+ fork() {
24
+ const copy = new CaptureScope(this.workdir);
25
+ for (const [name, value] of this.values) copy.set(name, value);
26
+ return copy;
27
+ }
28
+ /** Take every capture a fork of this scope recorded. */
29
+ adopt(fork) {
30
+ for (const [name, value] of fork.values) this.values.set(name, value);
31
+ }
32
+ };
33
+ //#endregion
34
+ //#region src/expectations/tokens/match.ts
35
+ /** A dynamic-value matcher, created through the `match` factories. */
36
+ var Matcher = class {
37
+ /** For kind 'includes': the substring the actual value must contain. */
38
+ includes;
39
+ kind;
40
+ /** For kind 'ref': the capture to assert inequality against. */
41
+ notRef;
42
+ /** For kind 'ref': the capture name. */
43
+ refName;
44
+ /** For kind 'regex': the pattern the actual value must match. */
45
+ regex;
46
+ constructor(kind, options = {}) {
47
+ this.includes = options.includes;
48
+ this.kind = kind;
49
+ this.notRef = options.notRef;
50
+ this.refName = options.refName;
51
+ this.regex = options.regex;
52
+ }
53
+ /** Placeholder-style rendering used in failure diffs and serialization. */
54
+ toString() {
55
+ if (this.kind === "includes") return `{{includes:${this.includes}}}`;
56
+ if (this.kind === "regex") return `{{regex:${this.regex?.source}}}`;
57
+ if (this.kind === "ref") return this.notRef === void 0 ? `{{ref#${this.refName}}}` : `{{ref#${this.refName}!${this.notRef}}}`;
58
+ return this.refName === void 0 ? `{{${this.kind}}}` : `{{${this.kind}#${this.refName}}}`;
59
+ }
60
+ toJSON() {
61
+ return this.toString();
62
+ }
63
+ };
64
+ const typed = (kind) => () => new Matcher(kind);
65
+ const match = {
66
+ any: typed("any"),
67
+ base64: typed("base64"),
68
+ /** Matches a calendar date (`YYYY-MM-DD`). */
69
+ date: typed("date"),
70
+ /** Matches a human duration (`12ms`, `1.5s`, `2m`, `3h`). */
71
+ duration: typed("duration"),
72
+ email: typed("email"),
73
+ /** Matches a float: any finite number in JSON; in text the decimal part is required. */
74
+ float: typed("float"),
75
+ hex: typed("hex"),
76
+ /** Matches a string containing the substring; code-only, like `match.regex`. */
77
+ includes: (substring) => new Matcher("includes", { includes: substring }),
78
+ /** Matches an integer (or an integer string in text contexts). */
79
+ int: typed("int"),
80
+ /** Matches an IPv4 address. */
81
+ ip: typed("ip"),
82
+ iso8601: typed("iso8601"),
83
+ /** Matches a number (or a numeric string in text contexts). */
84
+ number: typed("number"),
85
+ /** Matches a filesystem path (`/...` or `./...`). */
86
+ path: typed("path"),
87
+ /** Matches a TCP/UDP port number (0-65535). */
88
+ port: typed("port"),
89
+ /** Capture-and-compare: the first `ref(name)` captures, every later one must equal it; `{ not }` asserts inequality. */
90
+ ref: (name, options) => new Matcher("ref", {
91
+ notRef: options?.not,
92
+ refName: name
93
+ }),
94
+ regex: (regex) => new Matcher("regex", { regex }),
95
+ /** Matches a semantic version (`1.2.3`, `2.0.0-rc.1`). */
96
+ semver: typed("semver"),
97
+ /** Matches a git SHA (7-64 hex chars). */
98
+ sha: typed("sha"),
99
+ string: typed("string"),
100
+ /** Matches a wall-clock time (`HH:MM` or `HH:MM:SS`). */
101
+ time: typed("time"),
102
+ ulid: typed("ulid"),
103
+ /** Matches an http(s) URL. */
104
+ url: typed("url"),
105
+ uuid: typed("uuid"),
106
+ /** Matches the exact working directory of the current spec. */
107
+ workdir: typed("workdir")
108
+ };
109
+ //#endregion
110
+ //#region src/expectations/tokens/any-lines.ts
111
+ function isFramedInLine(expected, start, end) {
112
+ const before = expected.slice(expected.lastIndexOf("\n", start - 1) + 1, start);
113
+ const lineEnd = expected.indexOf("\n", end);
114
+ const after = expected.slice(end, lineEnd === -1 ? void 0 : lineEnd);
115
+ return before.trim() !== "" && after.trim() !== "";
116
+ }
117
+ function piecesMatch(pieces, text) {
118
+ const [first = "", ...rest] = pieces;
119
+ const last = rest.pop();
120
+ if (last === void 0) return text === first;
121
+ if (!text.startsWith(first)) return false;
122
+ let at = first.length;
123
+ for (const piece of rest) {
124
+ const found = text.indexOf(piece, at);
125
+ if (found === -1) return false;
126
+ at = found + piece.length;
127
+ }
128
+ return text.length - last.length >= at && text.endsWith(last);
129
+ }
130
+ //#endregion
131
+ //#region src/expectations/tokens/placeholders.ts
132
+ const UUID_SOURCE = "[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}";
133
+ const ULID_SOURCE = "[0-9A-HJKMNP-TV-Z]{26}";
134
+ const ISO8601_SOURCE = String.raw`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})`;
135
+ const DATE_SOURCE = String.raw`\d{4}-\d{2}-\d{2}`;
136
+ const TIME_SOURCE = String.raw`\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?`;
137
+ const DURATION_SOURCE = String.raw`\d+(?:\.\d+)?(?:ms|s|m|h)`;
138
+ const NUMBER_SOURCE = String.raw`-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?`;
139
+ const INT_SOURCE = String.raw`-?\d+`;
140
+ const FLOAT_SOURCE = String.raw`-?\d+\.\d+`;
141
+ const SEMVER_SOURCE = String.raw`\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?`;
142
+ const SHA_SOURCE = "[0-9a-f]{7,64}";
143
+ const HEX_SOURCE = "[0-9a-fA-F]+";
144
+ const BASE64_SOURCE = "[A-Za-z0-9+/]+={0,2}";
145
+ const PORT_SOURCE = String.raw`(?:6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|\d{1,4})`;
146
+ const IP_OCTET = String.raw`(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)`;
147
+ const IP_SOURCE = String.raw`(?:${IP_OCTET}\.){3}${IP_OCTET}`;
148
+ const URL_SOURCE = String.raw`https?:\/\/[^\s"'<>]+`;
149
+ const EMBEDDED_SOURCES = {
150
+ base64: BASE64_SOURCE,
151
+ date: DATE_SOURCE,
152
+ duration: DURATION_SOURCE,
153
+ email: String.raw`[^\s@"'<>]+@[^\s@"'<>]+\.[^\s@"'<>]+`,
154
+ float: FLOAT_SOURCE,
155
+ hex: HEX_SOURCE,
156
+ int: INT_SOURCE,
157
+ ip: IP_SOURCE,
158
+ iso8601: ISO8601_SOURCE,
159
+ number: NUMBER_SOURCE,
160
+ path: String.raw`\.{0,2}\/[^\s"'<>]*`,
161
+ port: PORT_SOURCE,
162
+ semver: SEMVER_SOURCE,
163
+ sha: SHA_SOURCE,
164
+ time: TIME_SOURCE,
165
+ ulid: ULID_SOURCE,
166
+ url: URL_SOURCE,
167
+ uuid: UUID_SOURCE
168
+ };
169
+ const wholeRe = (source) => new RegExp(`^(?:${source})$`, "u");
170
+ const WHOLE_RES = Object.fromEntries(Object.entries(EMBEDDED_SOURCES).map(([kind, source]) => [kind, wholeRe(source)]));
171
+ const wholeMatches = (kind, actual) => WHOLE_RES[kind]?.test(actual) === true;
172
+ const PLACEHOLDER_RE = new RegExp(String.raw`\{\{(?<kind>${[...TOKEN_KINDS].toSorted((a, b) => b.length - a.length).join("|")})(?:#(?<ref>[\w.-]+))?\}\}`, "gu");
173
+ function hasPlaceholders(value) {
174
+ PLACEHOLDER_RE.lastIndex = 0;
175
+ return PLACEHOLDER_RE.test(value);
176
+ }
177
+ function canonical(value) {
178
+ if (Array.isArray(value)) return value.map((item) => canonical(item));
179
+ if (!isRecord(value)) return value;
180
+ return Object.fromEntries(Object.keys(value).toSorted().map((key) => [key, canonical(value[key])]));
181
+ }
182
+ function capturedEquals(a, b) {
183
+ if (Object.is(a, b)) return true;
184
+ if (typeof a === "number" && typeof b === "string" || typeof a === "string" && typeof b === "number") return String(a) === String(b);
185
+ return typeof a === "object" && typeof b === "object" && JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
186
+ }
187
+ function recordRef(name, actual, scope) {
188
+ if (scope.has(name)) return capturedEquals(scope.get(name), actual);
189
+ scope.set(name, actual);
190
+ return true;
191
+ }
192
+ function isPortValue(value) {
193
+ return Number.isInteger(value) && value >= 0 && value <= 65535;
194
+ }
195
+ const NUMERIC_KINDS = {
196
+ float: Number.isFinite,
197
+ int: Number.isInteger,
198
+ number: Number.isFinite,
199
+ port: isPortValue
200
+ };
201
+ function kindMatches(kind, actual, scope) {
202
+ if (kind === "any") return true;
203
+ if (kind === "workdir") return typeof actual === "string" && actual === scope.workdir;
204
+ const numeric = NUMERIC_KINDS[kind];
205
+ if (numeric !== void 0 && typeof actual === "number") return numeric(actual);
206
+ if (typeof actual !== "string") return false;
207
+ if (kind === "string") return true;
208
+ return wholeMatches(kind, actual);
209
+ }
210
+ function matcherMatches(matcher, actual, scope) {
211
+ if (matcher.kind === "includes") return typeof actual === "string" && matcher.includes !== void 0 && actual.includes(matcher.includes);
212
+ if (matcher.kind === "regex") return typeof actual === "string" && matcher.regex?.test(actual) === true;
213
+ if (matcher.kind === "ref") {
214
+ if (matcher.notRef !== void 0 && scope.has(matcher.notRef) && capturedEquals(scope.get(matcher.notRef), actual)) return false;
215
+ return matcher.refName !== void 0 && recordRef(matcher.refName, actual, scope);
216
+ }
217
+ return kindMatches(matcher.kind, actual, scope);
218
+ }
219
+ function placeholderSource(kind, scope, inLine) {
220
+ if (kind === "workdir") return scope.workdir === void 0 ? "(?!)" : escapeRegExp(scope.workdir);
221
+ const source = EMBEDDED_SOURCES[kind];
222
+ if (source !== void 0) return source;
223
+ return kind === "any" && !inLine ? String.raw`[\s\S]*?` : String.raw`[^\n]*?`;
224
+ }
225
+ function escapeRegExp(text) {
226
+ return text.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`);
227
+ }
228
+ function parsePlaceholderString(expected, scope) {
229
+ PLACEHOLDER_RE.lastIndex = 0;
230
+ const tokens = [...expected.matchAll(PLACEHOLDER_RE)].flatMap((found) => {
231
+ const { kind: written, ref } = found.groups ?? {};
232
+ const kind = TOKEN_KINDS.find((known) => known === written);
233
+ return kind === void 0 ? [] : [{
234
+ found,
235
+ kind,
236
+ ref
237
+ }];
238
+ });
239
+ let source = "";
240
+ let lastIndex = 0;
241
+ const pieces = [];
242
+ let framed = false;
243
+ for (const { found, kind } of tokens) {
244
+ const inLine = isFramedInLine(expected, found.index, found.index + found[0].length);
245
+ framed ||= kind === "any" && inLine;
246
+ pieces.push(expected.slice(lastIndex, found.index));
247
+ source += `${escapeRegExp(expected.slice(lastIndex, found.index))}(${placeholderSource(kind, scope, inLine)})`;
248
+ lastIndex = found.index + found[0].length;
249
+ }
250
+ pieces.push(expected.slice(lastIndex));
251
+ source += escapeRegExp(expected.slice(lastIndex));
252
+ const [only] = tokens;
253
+ return {
254
+ pieces: !framed && tokens.every(({ kind, ref }) => kind === "any" && ref === void 0) ? pieces : null,
255
+ pattern: new RegExp(`^${source}$`, "u"),
256
+ refs: tokens.map(({ kind, ref }, index) => ({
257
+ index,
258
+ kind,
259
+ ref
260
+ })),
261
+ single: only?.found[0] === expected ? {
262
+ kind: only.kind,
263
+ ref: only.ref
264
+ } : null,
265
+ source
266
+ };
267
+ }
268
+ function placeholderPatternSource(expected) {
269
+ return parsePlaceholderString(expected, new CaptureScope()).source;
270
+ }
271
+ function placeholderStringMatches(expected, actual, scope) {
272
+ const parsed = parsePlaceholderString(expected, scope);
273
+ if (parsed.single) {
274
+ if (!kindMatches(parsed.single.kind, actual, scope)) return false;
275
+ if (parsed.single.ref !== void 0) return recordRef(parsed.single.ref, actual, scope);
276
+ return true;
277
+ }
278
+ if (typeof actual !== "string" && typeof actual !== "number") return false;
279
+ const text = String(actual);
280
+ if (parsed.pieces !== null) return piecesMatch(parsed.pieces, text);
281
+ const found = parsed.pattern.exec(text);
282
+ if (!found) return false;
283
+ for (const entry of parsed.refs) {
284
+ if (entry.ref === void 0) continue;
285
+ if (!recordRef(entry.ref, found[entry.index + 1], scope)) return false;
286
+ }
287
+ return true;
288
+ }
289
+ //#endregion
290
+ //#region src/expectations/tokens/structural.ts
291
+ function isPlainObject(value) {
292
+ return value !== null && typeof value === "object" && !Array.isArray(value);
293
+ }
294
+ function structurally(expected, actual, scope, subset) {
295
+ if (expected instanceof Matcher) return matcherMatches(expected, actual, scope);
296
+ if (typeof expected === "string" && hasPlaceholders(expected)) return placeholderStringMatches(expected, actual, scope);
297
+ if (Array.isArray(expected)) return Array.isArray(actual) && actual.length === expected.length && expected.every((item, index) => structurally(item, actual[index], scope, subset));
298
+ if (isPlainObject(expected)) return isPlainObject(actual) && (subset || Object.keys(actual).length === Object.keys(expected).length) && Object.keys(expected).every((key) => key in actual && structurally(expected[key], actual[key], scope, subset));
299
+ return Object.is(expected, actual);
300
+ }
301
+ function structuralEquals(expected, actual, scope) {
302
+ return structurally(expected, actual, scope, false);
303
+ }
304
+ function structuralSubset(expected, actual, scope) {
305
+ return structurally(expected, actual, scope, true);
306
+ }
307
+ /** A text without tokens reads as itself, every character literal. */
308
+ function textEquals(expected, actual, scope) {
309
+ return placeholderStringMatches(expected, actual, scope);
310
+ }
311
+ const UNICODE$1 = "u";
312
+ /** The fixture text becomes an unanchored pattern, a text without tokens a literal one. */
313
+ function textContains(expected, actual, scope) {
314
+ const parsed = parsePlaceholderString(expected, scope);
315
+ const found = new RegExp(parsed.source, UNICODE$1).exec(actual);
316
+ if (!found) return false;
317
+ return parsed.refs.every((entry) => entry.ref === void 0 || recordRef(entry.ref, found[entry.index + 1], scope));
318
+ }
319
+ function renderExpected(value) {
320
+ if (value instanceof Matcher) return value.toString();
321
+ if (Array.isArray(value)) return value.map((item) => renderExpected(item));
322
+ if (isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, renderExpected(item)]));
323
+ return value;
324
+ }
325
+ //#endregion
326
+ //#region src/stubs/matching/route.ts
327
+ const RELATIVE_BASE = "http://stub.invalid";
328
+ const UNICODE = "u";
329
+ function splitDeclared(url) {
330
+ const separator = url.indexOf("?");
331
+ if (separator === -1) return {
332
+ base: url,
333
+ query: new URLSearchParams()
334
+ };
335
+ return {
336
+ base: url.slice(0, separator),
337
+ query: new URLSearchParams(url.slice(separator + 1))
338
+ };
339
+ }
340
+ function safeDecode(text) {
341
+ try {
342
+ return decodeURIComponent(text);
343
+ } catch {
344
+ return text;
345
+ }
346
+ }
347
+ function wholeUrl(prefix, base) {
348
+ return new RegExp(String.raw`^${prefix}${placeholderPatternSource(base)}(?:\?[^#]*)?(?:#.*)?$`, UNICODE);
349
+ }
350
+ function valueMatches(declared, observed) {
351
+ return textEquals(declared, observed, new CaptureScope());
352
+ }
353
+ /** A RegExp tests the full URL, a path form ignores the origin; `{{token}}` segments compare structurally and declared query params are a subset. */
354
+ function urlMatches(declared, observed) {
355
+ if (declared instanceof RegExp) return declared.test(observed);
356
+ let url;
357
+ try {
358
+ url = new URL(observed, RELATIVE_BASE);
359
+ } catch {
360
+ return false;
361
+ }
362
+ const { base, query } = splitDeclared(declared);
363
+ const observedBase = base.startsWith("/") ? url.pathname : `${url.origin}${url.pathname}`;
364
+ const decodedBase = base.startsWith("/") ? safeDecode(url.pathname) : `${url.origin}${safeDecode(url.pathname)}`;
365
+ if (!valueMatches(base, observedBase) && !valueMatches(base, decodedBase)) return false;
366
+ for (const key of new Set(query.keys())) {
367
+ const observedValues = url.searchParams.getAll(key);
368
+ if (!query.getAll(key).every((value) => observedValues.some((actual) => valueMatches(value, actual)))) return false;
369
+ }
370
+ return true;
371
+ }
372
+ /** No narrower than `stubMatches`, which stays the authority on what matches. */
373
+ function routePatternOf(declared) {
374
+ if (declared instanceof RegExp) return declared;
375
+ const { base } = splitDeclared(declared);
376
+ if (base.startsWith("/")) return wholeUrl(String.raw`https?:\/\/[^/?#]+`, base);
377
+ if (hasPlaceholders(base)) return wholeUrl("", base);
378
+ return base;
379
+ }
380
+ function routeKeyOf({ method, url }) {
381
+ return url instanceof RegExp ? `${method} re:${url.source}` : `${method} str:${url}`;
382
+ }
383
+ function describeAbsent(absentHeaders) {
384
+ if (absentHeaders === void 0 || absentHeaders.length === 0) return "";
385
+ return ` (without header${absentHeaders.length > 1 ? "s" : ""} ${absentHeaders.join(", ")})`;
386
+ }
387
+ function describeRoute({ absentHeaders, method, url }) {
388
+ return `${method === "*" ? "ANY" : method} ${url instanceof RegExp ? String(url) : url}${describeAbsent(absentHeaders)}`;
389
+ }
390
+ //#endregion
391
+ //#region src/stubs/stub.ts
392
+ function defineStub(stub) {
393
+ return stub;
394
+ }
395
+ function flatten(items) {
396
+ const flat = [];
397
+ for (const item of items) if (Array.isArray(item)) flat.push(...flatten(item));
398
+ else if ("stubs" in item) flat.push(...item.stubs);
399
+ else flat.push(item);
400
+ return flat;
401
+ }
402
+ /** Compose stubs, lists and composites, order preserved; `.with()` derives a scenario. */
403
+ function defineStubs(...items) {
404
+ const stubs = Object.freeze(flatten(items));
405
+ return {
406
+ stubs,
407
+ with(...overrides) {
408
+ const added = flatten(overrides);
409
+ const claimed = new Set(added.map((stub) => routeKeyOf(stub.request)));
410
+ const kept = stubs.filter((stub) => !claimed.has(routeKeyOf(stub.request)));
411
+ return defineStubs([...added, ...kept]);
412
+ }
413
+ };
414
+ }
415
+ function stubsOf(input) {
416
+ return flatten([input]);
417
+ }
418
+ //#endregion
419
+ //#region src/stubs/intercept/declared.ts
420
+ function isRequest(value) {
421
+ return "adapter" in value;
422
+ }
423
+ /** A bare request is refused; the compiler already refuses the form, so only an untyped call reaches the refusal. */
424
+ function declared(call, requestOrStubs, maybeResponse) {
425
+ if (!isRequest(requestOrStubs)) return stubsOf(requestOrStubs);
426
+ if (maybeResponse === void 0) throw new Error(refusal({
427
+ fix: `pass a stub, defineStub({ request, response }), or the inline pair ${call}(request, response)`,
428
+ found: `${call}(request), a bare request with no response`,
429
+ read: "docs/reference/stubs.md#using-them-intercept",
430
+ why: "A stub is a request and its response: a request alone answers nothing."
431
+ }));
432
+ return [{
433
+ request: requestOrStubs,
434
+ response: maybeResponse
435
+ }];
436
+ }
437
+ //#endregion
438
+ //#region src/stubs/exchange.ts
439
+ /** Each adapter serialises an object as JSON, so a stream declared as a plain value would arrive as `{}`: the tag tells them apart. */
440
+ const STREAM_BODY = "@jterrazz/test:stream";
441
+ function isStreamBody(body) {
442
+ return typeof body === "object" && body !== null && "kind" in body && body.kind === "@jterrazz/test:stream";
443
+ }
444
+ //#endregion
445
+ export { Matcher as _, defineStubs as a, urlMatches as c, structuralEquals as d, structuralSubset as f, placeholderStringMatches as g, hasPlaceholders as h, defineStub as i, isPlainObject as l, textEquals as m, isStreamBody as n, describeRoute as o, textContains as p, declared as r, routePatternOf as s, STREAM_BODY as t, renderExpected as u, match as v, CaptureScope as y };
446
+
447
+ //# sourceMappingURL=shared-exchange.js.map