@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,22 @@
1
+ //#region src/chain/pause.ts
2
+ async function pause(ms) {
3
+ if (ms <= 0) return;
4
+ await new Promise((resolve) => {
5
+ setTimeout(resolve, ms);
6
+ });
7
+ }
8
+ /** Asks `probe` until it answers something other than `undefined`, which it hands back, or answers `undefined` once `timeout` has passed. Measured on `performance.now()`, so `clock.at()`, which fakes `Date`, cannot freeze it. */
9
+ async function untilDeadline(probe, { interval = 0, timeout, wait }) {
10
+ const deadline = performance.now() + timeout;
11
+ for (;;) {
12
+ const answer = await probe();
13
+ if (answer !== void 0) return answer;
14
+ const remaining = deadline - performance.now();
15
+ if (remaining <= 0) return;
16
+ await (wait === void 0 ? pause(interval) : wait(remaining));
17
+ }
18
+ }
19
+ //#endregion
20
+ export { untilDeadline as n, pause as t };
21
+
22
+ //# sourceMappingURL=shared-pause.js.map
@@ -0,0 +1,88 @@
1
+ import { n as installRootOf } from "./shared-resolve.js";
2
+ import { n as refusal } from "./shared-refusal.js";
3
+ import { existsSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ //#region src/services/peer.ts
6
+ const INSTALLERS = [
7
+ {
8
+ command: "bun add -d",
9
+ lockfile: "bun.lock",
10
+ name: "bun"
11
+ },
12
+ {
13
+ command: "bun add -d",
14
+ lockfile: "bun.lockb",
15
+ name: "bun"
16
+ },
17
+ {
18
+ command: "pnpm add -D",
19
+ lockfile: "pnpm-lock.yaml",
20
+ name: "pnpm"
21
+ },
22
+ {
23
+ command: "yarn add -D",
24
+ lockfile: "yarn.lock",
25
+ name: "yarn"
26
+ },
27
+ {
28
+ command: "npm install -D",
29
+ lockfile: "package-lock.json",
30
+ name: "npm"
31
+ }
32
+ ];
33
+ const DEFAULT_INSTALLER = {
34
+ command: "npm install -D",
35
+ name: "npm"
36
+ };
37
+ const BUILDERS = {
38
+ bun: (peer) => `bun pm trust ${peer}`,
39
+ npm: (peer) => `npm install-scripts approve ${peer} && npm rebuild ${peer}`,
40
+ pnpm: (peer) => `add "${peer}" to \`onlyBuiltDependencies\` in package.json (or \`only-built-dependencies\` in .npmrc), then \`pnpm rebuild ${peer}\``,
41
+ yarn: (peer) => `yarn rebuild ${peer}`
42
+ };
43
+ const UNBUILT = /could not locate the bindings file|\.node['"]?$|invalid elf header|was compiled against a different node/iu;
44
+ function isUnbuilt(error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ return UNBUILT.test(message);
47
+ }
48
+ const NATIVE_PEERS = /* @__PURE__ */ new Set(["better-sqlite3"]);
49
+ function installerAt(from) {
50
+ const root = installRootOf(from);
51
+ if (root === void 0) return DEFAULT_INSTALLER;
52
+ return INSTALLERS.find((candidate) => existsSync(resolve(root, candidate.lockfile))) ?? DEFAULT_INSTALLER;
53
+ }
54
+ function buildNote(peer, manager) {
55
+ if (!NATIVE_PEERS.has(peer) || manager !== "pnpm") return "";
56
+ return `; under pnpm the binding is not built unless the package is listed: add "${peer}" to \`onlyBuiltDependencies\` in package.json (or \`only-built-dependencies\` in .npmrc) and reinstall`;
57
+ }
58
+ /** A binding the package manager never built surfaces as a `bindings` stack trace; this turns it into a refusal naming the peer and the command. */
59
+ function requireBuiltPeer(peer, what, use, from = process.cwd()) {
60
+ try {
61
+ use();
62
+ } catch (error) {
63
+ if (!isUnbuilt(error)) throw error;
64
+ const { name } = installerAt(from);
65
+ throw new Error(refusal({
66
+ fix: BUILDERS[name](peer),
67
+ found: `${what} found \`${peer}\`, and its native binding is not built`,
68
+ read: "docs/start/set-up-a-project.md#what-to-install",
69
+ why: `A native peer is built by its install script, and ${name} runs a dependency's install script only once it is told to.`
70
+ }), { cause: error });
71
+ }
72
+ }
73
+ /** `load` is the caller's own `import('<peer>')`: the bundler and the resolver need to see the bare specifier. */
74
+ async function loadPeer(peer, what, load, from = process.cwd()) {
75
+ try {
76
+ return await load();
77
+ } catch (error) {
78
+ const { command, name } = installerAt(from);
79
+ throw new Error(refusal("seam-is-an-optional-peer", {
80
+ fix: `${command} ${peer}${buildNote(peer, name)}`,
81
+ found: `${what} requires \`${peer}\`, and it is not installed`
82
+ }), { cause: error });
83
+ }
84
+ }
85
+ //#endregion
86
+ export { requireBuiltPeer as n, loadPeer as t };
87
+
88
+ //# sourceMappingURL=shared-peer.js.map
@@ -0,0 +1,328 @@
1
+ import { n as refusal } from "./shared-refusal.js";
2
+ import { n as untilDeadline } from "./shared-pause.js";
3
+ import { d as evidenceFile } from "./shared-artifacts.js";
4
+ import { c as scopeChain } from "./shared-landmarks.js";
5
+ import { n as CANDIDATE_LIMITS, o as ambiguousTargetError, r as candidatesOf, s as asWritten, t as scenarioFailure } from "./shared-scenario-failure.js";
6
+ //#region src/specifications/website/browser/page-scripts.ts
7
+ function extractPage() {
8
+ const links = [...document.querySelectorAll("link")].map((link) => ({
9
+ href: link.href,
10
+ hreflang: link.hreflang || void 0,
11
+ rel: link.rel,
12
+ type: link.type || void 0
13
+ }));
14
+ const metas = [...document.querySelectorAll("meta")].map((meta) => ({
15
+ content: meta.content,
16
+ name: meta.name || void 0,
17
+ property: meta.getAttribute("property") ?? void 0
18
+ }));
19
+ const jsonLdBlocks = [...document.querySelectorAll("script[type=\"application/ld+json\"]")].map((script) => script.textContent ?? "");
20
+ return {
21
+ html: document.documentElement.outerHTML,
22
+ jsonLdBlocks,
23
+ links,
24
+ metas,
25
+ text: document.body?.innerText ?? "",
26
+ title: document.title
27
+ };
28
+ }
29
+ function seedStorage(state) {
30
+ if (state === "throws") {
31
+ for (const store of ["localStorage", "sessionStorage"]) Object.defineProperty(globalThis, store, {
32
+ configurable: true,
33
+ get: () => {
34
+ throw new DOMException("The operation is insecure.", "SecurityError");
35
+ }
36
+ });
37
+ return;
38
+ }
39
+ for (const [store, entries] of [["localStorage", state.local], ["sessionStorage", state.session]]) for (const [key, value] of Object.entries(entries ?? {})) try {
40
+ if (globalThis[store].getItem(key) === null) globalThis[store].setItem(key, value);
41
+ } catch {}
42
+ }
43
+ //#endregion
44
+ //#region src/specifications/website/browser/shared-browser.ts
45
+ /** Launched on the first ask, again once disconnected; a failed launch is not kept. */
46
+ function createSharedBrowser(launch) {
47
+ let shared;
48
+ const attempt = async () => {
49
+ try {
50
+ const browser = await launch();
51
+ browser.on("disconnected", () => {
52
+ shared = void 0;
53
+ });
54
+ return browser;
55
+ } catch (error) {
56
+ shared = void 0;
57
+ throw error;
58
+ }
59
+ };
60
+ return async () => {
61
+ shared ??= attempt();
62
+ return await shared;
63
+ };
64
+ }
65
+ //#endregion
66
+ //#region src/specifications/website/browser/website-visitor.ts
67
+ const VALUE_TIMEOUT_MS = 3e4;
68
+ const VALUE_POLL_MS = 100;
69
+ const FIELD_ROLES = [
70
+ "checkbox",
71
+ "combobox",
72
+ "radio",
73
+ "searchbox",
74
+ "slider",
75
+ "spinbutton",
76
+ "switch",
77
+ "textbox"
78
+ ];
79
+ function locate(root, target) {
80
+ const scope = target.scope ? locate(root, target.scope) : root;
81
+ const exact = target.exact ?? true;
82
+ const name = target.name ?? "";
83
+ if (target.kind === "field") {
84
+ let field = scope.getByLabel(name, { exact });
85
+ for (const role of FIELD_ROLES) field = field.or(scope.getByRole(role, {
86
+ exact,
87
+ name
88
+ }));
89
+ return field;
90
+ }
91
+ if (target.kind === "testId") return scope.getByTestId(name);
92
+ if (target.kind === "text") return scope.getByText(name, { exact });
93
+ return scope.getByRole(target.kind, target.name === void 0 ? { exact } : {
94
+ exact,
95
+ name: target.name
96
+ });
97
+ }
98
+ function isStrictViolation(error) {
99
+ return error instanceof Error && error.message.includes("strict mode violation");
100
+ }
101
+ async function act(page, target, action) {
102
+ try {
103
+ return await action(locate(page, target));
104
+ } catch (error) {
105
+ if (!isStrictViolation(error)) throw error;
106
+ const culprit = await findAmbiguousLevel(page, target);
107
+ const matches = await locate(page, culprit).evaluateAll(candidatesOf, CANDIDATE_LIMITS);
108
+ throw ambiguousTargetError({
109
+ target: culprit,
110
+ matches,
111
+ url: page.url()
112
+ });
113
+ }
114
+ }
115
+ async function findAmbiguousLevel(page, target) {
116
+ for (const level of scopeChain(target).slice(0, -1)) if (await locate(page, level).count() > 1) return level;
117
+ return target;
118
+ }
119
+ function stateOnly(page, locator, target) {
120
+ if (target.focused === true) return locator.and(page.locator(":focus"));
121
+ if (target.disabled !== void 0) return locator.and(page.locator(target.disabled ? ":disabled" : ":enabled"));
122
+ if (target.selected !== void 0) return locator.and(page.locator(target.selected ? ":checked" : ":not(:checked)"));
123
+ return null;
124
+ }
125
+ function byPresence(target) {
126
+ return target.kind === "option";
127
+ }
128
+ async function currentValue(locator) {
129
+ try {
130
+ return await locator.inputValue({ timeout: VALUE_POLL_MS });
131
+ } catch (error) {
132
+ if (isStrictViolation(error)) throw error;
133
+ return null;
134
+ }
135
+ }
136
+ async function waitForValue(locator, target, wanted, timeout = VALUE_TIMEOUT_MS) {
137
+ const value = target.value ?? "";
138
+ let actual = null;
139
+ if (await untilDeadline(async () => {
140
+ actual = await currentValue(locator);
141
+ return actual === value === wanted || void 0;
142
+ }, {
143
+ interval: VALUE_POLL_MS,
144
+ timeout
145
+ }) === void 0) throw new Error(refusal({
146
+ fix: "correct the value the scenario states, or what the page puts in the field",
147
+ found: `${asWritten(target)} ${wanted ? "never held" : "still holds"} ${JSON.stringify(value)}${actual === null ? " (it is not on the page)" : `, it holds ${JSON.stringify(actual)}`}`,
148
+ read: "docs/reference/visitor.md#see-and-gone-are-the-synchronization",
149
+ why: "A verb waits for what it states to hold, within its budget, and never sleeps."
150
+ }));
151
+ }
152
+ async function settle(page, locator, target, present) {
153
+ if (target.value !== void 0) {
154
+ await waitForValue(locator, target, present);
155
+ return;
156
+ }
157
+ const narrowed = stateOnly(page, locator, target);
158
+ if (narrowed !== null) {
159
+ await narrowed.waitFor({ state: present ? "attached" : "detached" });
160
+ return;
161
+ }
162
+ const shown = present ? "visible" : "hidden";
163
+ await locator.waitFor({ state: byPresence(target) ? present ? "attached" : "detached" : shown });
164
+ }
165
+ function createVisitor(page, baseUrl) {
166
+ return {
167
+ check: async (target) => {
168
+ await act(page, target, async (locator) => {
169
+ await locator.check();
170
+ });
171
+ },
172
+ click: async (target) => {
173
+ await act(page, target, async (locator) => {
174
+ await locator.click();
175
+ });
176
+ },
177
+ fill: async (target, value) => {
178
+ await act(page, target, async (locator) => {
179
+ await locator.fill(value);
180
+ });
181
+ },
182
+ goto: async (path) => {
183
+ await page.goto(`${baseUrl}${path}`, { waitUntil: "load" });
184
+ },
185
+ hover: async (target) => {
186
+ await act(page, target, async (locator) => {
187
+ await locator.hover();
188
+ });
189
+ },
190
+ press: async (key) => {
191
+ await page.keyboard.press(key);
192
+ },
193
+ gone: async (target) => {
194
+ await act(page, target, async (locator) => {
195
+ await settle(page, locator, target, false);
196
+ });
197
+ },
198
+ see: async (target) => {
199
+ await act(page, target, async (locator) => {
200
+ await settle(page, locator, target, true);
201
+ });
202
+ },
203
+ select: async (target, option) => {
204
+ await act(page, target, async (locator) => await locator.selectOption(option));
205
+ }
206
+ };
207
+ }
208
+ //#endregion
209
+ //#region src/specifications/website/browser/playwright.adapter.ts
210
+ const LAUNCH_OPTIONS = {
211
+ channel: "chromium-headless-shell",
212
+ headless: true
213
+ };
214
+ async function launchChromium() {
215
+ let chromium;
216
+ try {
217
+ ({chromium} = await import("playwright"));
218
+ } catch {
219
+ throw new Error(refusal("seam-is-an-optional-peer", {
220
+ fix: "npm install -D playwright && npx playwright install chromium",
221
+ found: ".visit() requires `playwright`, and it is not installed"
222
+ }));
223
+ }
224
+ return await chromium.launch(LAUNCH_OPTIONS);
225
+ }
226
+ /** invariant: with `'block'`, requests leaving the site are aborted; the stub backend stays reachable through `allowedOrigins`. */
227
+ async function blockExternal(context, options) {
228
+ const aborted = /* @__PURE__ */ new Set();
229
+ if (options.external !== "block") return aborted;
230
+ const allowed = new Set([options.baseUrl, ...options.allowedOrigins ?? []].map((at) => new URL(at).origin));
231
+ await context.route("**/*", (route) => {
232
+ const url = route.request().url();
233
+ if (allowed.has(new URL(url).origin)) route.continue();
234
+ else {
235
+ aborted.add(url);
236
+ route.abort();
237
+ }
238
+ });
239
+ return aborted;
240
+ }
241
+ function recordConsole(page, aborted) {
242
+ const consoleMessages = [];
243
+ page.on("console", (message) => {
244
+ if (message.type() === "error" && aborted.has(message.location().url)) return;
245
+ consoleMessages.push({
246
+ text: message.text(),
247
+ type: message.type()
248
+ });
249
+ });
250
+ page.on("pageerror", (error) => {
251
+ consoleMessages.push({
252
+ text: `Uncaught ${error.name}: ${error.message}`,
253
+ type: "error"
254
+ });
255
+ });
256
+ return consoleMessages;
257
+ }
258
+ const workerBrowsers = /* @__PURE__ */ new Map();
259
+ function workerBrowser(launch) {
260
+ let shared = workerBrowsers.get(launch);
261
+ if (shared === void 0) {
262
+ shared = createSharedBrowser(launch);
263
+ workerBrowsers.set(launch, shared);
264
+ }
265
+ return shared;
266
+ }
267
+ /** One browser per worker, launched lazily; each visit gets a fresh `BrowserContext`. */
268
+ var PlaywrightAdapter = class {
269
+ browser;
270
+ contexts = /* @__PURE__ */ new Set();
271
+ constructor(options = {}) {
272
+ this.browser = workerBrowser(options.launch ?? launchChromium);
273
+ }
274
+ async close() {
275
+ const open = [...this.contexts];
276
+ this.contexts.clear();
277
+ await Promise.all(open.map(async (context) => {
278
+ await context.close();
279
+ }));
280
+ }
281
+ async open(url, options) {
282
+ const context = await (await this.browser()).newContext({ extraHTTPHeaders: options.headers ?? {} });
283
+ this.contexts.add(context);
284
+ try {
285
+ const aborted = await blockExternal(context, options);
286
+ if (options.storage !== void 0) await context.addInitScript(seedStorage, options.storage);
287
+ const page = await context.newPage();
288
+ if (options.clock !== void 0) await page.clock.setFixedTime(new Date(options.clock));
289
+ const consoleMessages = recordConsole(page, aborted);
290
+ const response = await page.goto(url, { waitUntil: "load" });
291
+ if (options.scenario) try {
292
+ await options.scenario(createVisitor(page, options.baseUrl));
293
+ } catch (error) {
294
+ const evidence = await this.captureEvidence(page);
295
+ throw scenarioFailure(error, evidence, "visit");
296
+ }
297
+ const extraction = await page.evaluate(extractPage);
298
+ const tree = await page.locator("body").ariaSnapshot();
299
+ return {
300
+ consoleMessages,
301
+ status: response?.status() ?? 0,
302
+ tree,
303
+ url: page.url(),
304
+ ...extraction
305
+ };
306
+ } finally {
307
+ this.contexts.delete(context);
308
+ await context.close();
309
+ }
310
+ }
311
+ /** Screenshot the failing state into the evidence folder; never masks the original error. */
312
+ async captureEvidence(page) {
313
+ try {
314
+ const path = evidenceFile("website");
315
+ await page.screenshot({
316
+ fullPage: true,
317
+ path
318
+ });
319
+ return path;
320
+ } catch {
321
+ return null;
322
+ }
323
+ }
324
+ };
325
+ //#endregion
326
+ export { PlaywrightAdapter };
327
+
328
+ //# sourceMappingURL=shared-playwright.adapter.js.map
@@ -0,0 +1,13 @@
1
+ //#region src/chain/process-alive.ts
2
+ function processAlive(pid, probe = (target) => process.kill(target, 0)) {
3
+ try {
4
+ probe(pid);
5
+ return true;
6
+ } catch (error) {
7
+ return Reflect.get(new Object(error), "code") !== "ESRCH";
8
+ }
9
+ }
10
+ //#endregion
11
+ export { processAlive as t };
12
+
13
+ //# sourceMappingURL=shared-process-alive.js.map
@@ -0,0 +1,74 @@
1
+ import { t as ServedProcess } from "./shared-serve.js";
2
+ import { n as refusal } from "./shared-refusal.js";
3
+ //#region src/services/process/process.adapter.ts
4
+ async function nothingToIsolate() {}
5
+ var ProcessHandle = class {
6
+ /** Embedded: nothing to pull, nothing to run in a container. */
7
+ defaultImage = "";
8
+ defaultPort = 0;
9
+ environment = {};
10
+ type = "process";
11
+ serviceName = null;
12
+ connectionString = "";
13
+ started = false;
14
+ served = null;
15
+ options;
16
+ constructor(options) {
17
+ this.options = options;
18
+ }
19
+ buildConnectionString(host, port) {
20
+ return `http://${host}:${port}`;
21
+ }
22
+ createDatabaseAdapter() {
23
+ return null;
24
+ }
25
+ async healthcheck() {}
26
+ async initialize() {}
27
+ /** Isolation is the run's: one child per evaluation of the specification module, told apart by the minted id. */
28
+ isolation() {
29
+ return {
30
+ acquire: nothingToIsolate,
31
+ release: nothingToIsolate,
32
+ reset: nothingToIsolate
33
+ };
34
+ }
35
+ async reset() {}
36
+ /** Spawn it and wait until it answers; `services` is every sibling, already started. */
37
+ async spawnWith(root, services, runId) {
38
+ const stated = typeof this.options.env === "function" ? this.options.env(services) : this.options.env;
39
+ this.served = new ServedProcess(this.options, root, "process", {
40
+ ...stated,
41
+ TEST_RUN_ID: runId
42
+ });
43
+ this.connectionString = await this.served.start();
44
+ this.started = true;
45
+ }
46
+ /** Terminate the process group. Idempotent. */
47
+ async shutdown() {
48
+ await this.served?.stop();
49
+ this.served = null;
50
+ this.started = false;
51
+ }
52
+ /** What it was declared as: what a specification reads to start it in a slot of its own. */
53
+ get spec() {
54
+ return this.options;
55
+ }
56
+ /** The base URL it came up on, known only once it is started. */
57
+ get url() {
58
+ if (!this.started) throw new Error(refusal({
59
+ fix: "read it from the started services record (`env: (services) => ...` or `server: (services) => ...`), never at declaration time",
60
+ found: "process().url read before the process was ready",
61
+ read: "docs/reference/services.md#process-the-one-shape-an-external-process-takes",
62
+ why: "A process's URL is known once it is ready."
63
+ }));
64
+ return this.connectionString;
65
+ }
66
+ };
67
+ /** Declare an external process the framework owns for the life of the specification. */
68
+ function processService(options) {
69
+ return new ProcessHandle(options);
70
+ }
71
+ //#endregion
72
+ export { processService as n, ProcessHandle as t };
73
+
74
+ //# sourceMappingURL=shared-process.adapter.js.map
@@ -0,0 +1,13 @@
1
+ //#region lint/kit/project/record-key.ts
2
+ /** `analyticsDb` gives `analytics-db`. */
3
+ function toKebabCase(key) {
4
+ return key.replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower>-$<upper>").toLowerCase();
5
+ }
6
+ /** `analyticsDb` gives `ANALYTICS_DB`, `db-main` gives `DB_MAIN`. */
7
+ function toConstantCase(key) {
8
+ return key.replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower>_$<upper>").replaceAll(/[^A-Za-z0-9]/gu, "_").toUpperCase();
9
+ }
10
+ //#endregion
11
+ export { toKebabCase as n, toConstantCase as t };
12
+
13
+ //# sourceMappingURL=shared-record-key.js.map
@@ -0,0 +1,35 @@
1
+ import { t as CATALOGUE_CHAPTER } from "./shared-chapter.js";
2
+ import { defineInvariant, refusalBlock } from "@jterrazz/typescript/rules";
3
+ //#region src/chain/refusals/rows.ts
4
+ const REFUSAL_WHY = {
5
+ "database-named-only-when-several-at-runtime": "With two SQL databases or more, a seed or a table names the `database:` it reads, and with exactly one it names none.",
6
+ "route-written-as-template-at-runtime": "A request written as its route template is handed exactly the parameters the template names.",
7
+ "disk-named-by-relative-path-at-runtime": "A verb that reads the disk takes a `file()` or a `directory()`, whose path starts with `./` or `../` and ends in `/` exactly when it names a directory.",
8
+ "one-verb-per-meaning": "`toMatch` on a result the framework produced compares the WHOLE result, to a value written in place or to a `file()`/`directory()` golden.",
9
+ "network-strict-after-first-stub": "From its first stub, a chain's network is strict: every outgoing request matches a declared stub.",
10
+ "target-matches-one-element": "A verb acts on exactly ONE element: a target that matches several is refused, never settled by the first.",
11
+ "seam-is-an-optional-peer": "Every seam is an optional peer of @jterrazz/test, installed by the project that specifies through it.",
12
+ "no-landmark-on-mobile": "An iOS screen has no ARIA landmark: a mobile verb is scoped with `within(testId(...), ...)`."
13
+ };
14
+ `${REFUSAL_WHY["route-written-as-template-at-runtime"]}`, `${REFUSAL_WHY["database-named-only-when-several-at-runtime"]}`, `${REFUSAL_WHY["disk-named-by-relative-path-at-runtime"]}`, `${REFUSAL_WHY["network-strict-after-first-stub"]}`, `${REFUSAL_WHY["one-verb-per-meaning"]}`, `${REFUSAL_WHY["target-matches-one-element"]}`, `${REFUSAL_WHY["no-landmark-on-mobile"]}`, `${REFUSAL_WHY["seam-is-an-optional-peer"]}`;
15
+ //#endregion
16
+ //#region src/chain/refusals/refusal.ts
17
+ const invariant = defineInvariant({
18
+ bugOf: "the framework",
19
+ notOf: "the test",
20
+ package: "@jterrazz/test"
21
+ });
22
+ function refusal$1(rowOrLesson, teaching) {
23
+ if (typeof rowOrLesson !== "string") return refusalBlock(rowOrLesson);
24
+ if (teaching === void 0) throw invariant(`refusal('${rowOrLesson}') was handed no found and no fix for its case`);
25
+ return refusalBlock({
26
+ fix: teaching.fix,
27
+ found: teaching.found,
28
+ read: `${CATALOGUE_CHAPTER}#${rowOrLesson}`,
29
+ why: REFUSAL_WHY[rowOrLesson]
30
+ });
31
+ }
32
+ //#endregion
33
+ export { refusal$1 as n, invariant as t };
34
+
35
+ //# sourceMappingURL=shared-refusal.js.map
@@ -0,0 +1,24 @@
1
+ import { holdsManifest, nearestRoot } from "@jterrazz/typescript/rules";
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ //#region lint/kit/project/resolve.ts
5
+ const installRootOf = (from) => nearestRoot(from, (directory) => existsSync(join(directory, "package.json")));
6
+ function discoverRoot(startDir) {
7
+ return nearestRoot(startDir, holdsManifest) ?? startDir;
8
+ }
9
+ function resolveRoot(root, callerDir) {
10
+ if (root === void 0 || root === "") return discoverRoot(callerDir);
11
+ return resolve(callerDir, root);
12
+ }
13
+ /** What a child process prints as `$PWD` (macOS resolves the tmpdir symlink); the path itself when it cannot resolve. */
14
+ function safeRealpath(path) {
15
+ try {
16
+ return realpathSync(path);
17
+ } catch {
18
+ return path;
19
+ }
20
+ }
21
+ //#endregion
22
+ export { safeRealpath as i, installRootOf as n, resolveRoot as r, discoverRoot as t };
23
+
24
+ //# sourceMappingURL=shared-resolve.js.map