@jterrazz/test 16.1.1 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +2 -2980
  3. package/dist/browser/services.js +2 -0
  4. package/dist/browser/stubs.js +2 -0
  5. package/dist/checker.d.ts +6 -1
  6. package/dist/checker.js +4534 -2628
  7. package/dist/config.d.ts +40 -0
  8. package/dist/config.js +309 -0
  9. package/dist/in-place.global-setup.d.ts +11 -0
  10. package/dist/in-place.global-setup.js +21 -0
  11. package/dist/index.d.ts +1653 -3004
  12. package/dist/index.js +5226 -3845
  13. package/dist/mobile-session.global-setup.d.ts +14 -0
  14. package/dist/mobile-session.global-setup.js +31 -0
  15. package/dist/oxlint.cjs +6069 -5634
  16. package/dist/oxlint.d.cts +43 -7212
  17. package/dist/oxlint.d.ts +43 -7212
  18. package/dist/oxlint.js +6127 -5693
  19. package/dist/route-coverage.global-setup.d.ts +42 -0
  20. package/dist/route-coverage.global-setup.js +2 -0
  21. package/dist/services.d.ts +3 -0
  22. package/dist/services.global-setup.d.ts +43 -0
  23. package/dist/services.global-setup.js +2 -0
  24. package/dist/services.js +597 -0
  25. package/dist/shared-appium.adapter.js +428 -0
  26. package/dist/shared-artifacts.js +32 -0
  27. package/dist/shared-chapter.js +7 -0
  28. package/dist/shared-container.port.d.ts +31 -0
  29. package/dist/shared-derived-projects.js +58 -0
  30. package/dist/shared-device.port.d.ts +189 -0
  31. package/dist/shared-exchange.js +447 -0
  32. package/dist/shared-in-place.js +172 -0
  33. package/dist/shared-index.browser.js +3191 -0
  34. package/dist/shared-index.services.d.ts +346 -0
  35. package/dist/shared-index.stubs.d.ts +287 -0
  36. package/dist/shared-json-shape.js +17 -0
  37. package/dist/shared-landmarks.js +71 -0
  38. package/dist/shared-ledger.js +68 -0
  39. package/dist/shared-literate-plugin.js +183 -0
  40. package/dist/shared-manifest.js +1321 -0
  41. package/dist/shared-pause.js +11 -0
  42. package/dist/shared-peer.js +88 -0
  43. package/dist/shared-playwright.adapter.js +330 -0
  44. package/dist/shared-process.adapter.js +74 -0
  45. package/dist/shared-refusal.js +35 -0
  46. package/dist/shared-resolve.js +24 -0
  47. package/dist/shared-route-coverage.global-setup.js +187 -0
  48. package/dist/shared-scenario-failure.js +198 -0
  49. package/dist/shared-serve.js +365 -0
  50. package/dist/shared-service-images.js +10 -0
  51. package/dist/shared-services.global-setup.js +179 -0
  52. package/dist/shared-session-ledger.js +301 -0
  53. package/dist/shared-setup-entry.js +12 -0
  54. package/dist/shared-source-scan.js +133 -0
  55. package/dist/shared-spec-document.js +365 -0
  56. package/dist/shared-specifications.js +26 -0
  57. package/dist/shared-stream-body.js +130 -0
  58. package/dist/shared-stub-server.js +190 -0
  59. package/dist/shared-temp-root.global-setup.js +80 -0
  60. package/dist/shared-test-baseline.js +26 -0
  61. package/dist/shared-test-section.js +58 -0
  62. package/dist/shared-testcontainers.adapter.js +203 -0
  63. package/dist/shared-tokens.js +55 -0
  64. package/dist/shared-yaml-document.js +119 -0
  65. package/dist/stubs.d.ts +2 -0
  66. package/dist/stubs.js +657 -0
  67. package/dist/temp-root.global-setup.d.ts +7 -0
  68. package/dist/temp-root.global-setup.js +2 -0
  69. package/dist/vitest.d.ts +97 -163
  70. package/dist/vitest.js +373 -525
  71. package/docs/README.md +82 -36
  72. package/docs/concepts/architecture.md +118 -0
  73. package/docs/concepts/conventions.md +333 -0
  74. package/docs/guides/api-specs.md +374 -0
  75. package/docs/guides/cli-specs.md +564 -0
  76. package/docs/guides/component-specs.md +252 -0
  77. package/docs/guides/develop.md +94 -0
  78. package/docs/guides/integration-specs.md +181 -0
  79. package/docs/guides/job-specs.md +180 -0
  80. package/docs/guides/lint-specs.md +174 -0
  81. package/docs/guides/mobile-specs.md +319 -0
  82. package/docs/guides/module-tests.md +149 -0
  83. package/docs/guides/release.md +43 -0
  84. package/docs/guides/spec-documents.md +189 -0
  85. package/docs/guides/website-specs.md +413 -0
  86. package/docs/reference/assertions.md +345 -0
  87. package/docs/reference/glossary.md +20 -0
  88. package/docs/reference/linting.md +483 -0
  89. package/docs/reference/services.md +236 -0
  90. package/docs/reference/stubs.md +498 -0
  91. package/docs/reference/time-and-doubles.md +137 -0
  92. package/docs/reference/tokens.md +258 -0
  93. package/docs/reference/visitor.md +221 -0
  94. package/docs/start/getting-started.md +187 -0
  95. package/docs/start/set-up-a-project.md +214 -0
  96. package/package.json +50 -30
  97. package/schema/spec.schema.json +43 -17
  98. package/dist/appium.adapter.js +0 -489
  99. package/dist/artifacts.js +0 -32
  100. package/dist/fixtures.js +0 -110
  101. package/dist/literate.js +0 -1747
  102. package/dist/match.js +0 -804
  103. package/dist/playwright.adapter.js +0 -438
  104. package/dist/server.js +0 -160
  105. package/dist/stream.js +0 -321
  106. package/dist/substring-warning.js +0 -282
  107. package/docs/01-architecture.md +0 -95
  108. package/docs/02-developing.md +0 -351
  109. package/docs/03-testing.md +0 -495
  110. package/docs/04-operating.md +0 -101
  111. package/docs/05-module-tests.md +0 -152
  112. package/docs/06-integration.md +0 -180
  113. package/docs/07-component.md +0 -246
  114. package/docs/08-website.md +0 -404
  115. package/docs/09-mobile.md +0 -270
  116. package/docs/10-api.md +0 -264
  117. package/docs/11-jobs.md +0 -157
  118. package/docs/12-cli.md +0 -677
  119. package/docs/13-elements.md +0 -248
  120. package/docs/14-assertions.md +0 -314
  121. package/docs/15-tokens.md +0 -253
  122. package/docs/16-contracts.md +0 -438
  123. package/docs/17-services.md +0 -188
  124. package/docs/18-conventions.md +0 -332
  125. package/docs/19-linting.md +0 -512
  126. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  127. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  128. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  129. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  130. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  131. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  132. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  133. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  134. package/docs/decisions/_template.md +0 -22
  135. package/docs/reference/classes/Matcher.md +0 -120
  136. package/docs/reference/classes/ProcessHandle.md +0 -368
  137. package/docs/reference/functions/defineContract.md +0 -39
  138. package/docs/reference/functions/defineContracts.md +0 -36
  139. package/docs/reference/functions/disabled.md +0 -28
  140. package/docs/reference/functions/enabled.md +0 -20
  141. package/docs/reference/functions/focused.md +0 -26
  142. package/docs/reference/functions/mockOf.md +0 -79
  143. package/docs/reference/functions/postgres.md +0 -31
  144. package/docs/reference/functions/process.md +0 -33
  145. package/docs/reference/functions/redis.md +0 -28
  146. package/docs/reference/functions/required.md +0 -41
  147. package/docs/reference/functions/selected.md +0 -26
  148. package/docs/reference/functions/sqlite.md +0 -33
  149. package/docs/reference/functions/testId.md +0 -20
  150. package/docs/reference/functions/text.md +0 -40
  151. package/docs/reference/functions/valued.md +0 -28
  152. package/docs/reference/functions/waitUntil.md +0 -46
  153. package/docs/reference/functions/within.md +0 -29
  154. package/docs/reference/index.md +0 -196
  155. package/docs/reference/interfaces/BaseResult.md +0 -87
  156. package/docs/reference/interfaces/CallResult.md +0 -160
  157. package/docs/reference/interfaces/CaptureScope.md +0 -82
  158. package/docs/reference/interfaces/CliResult.md +0 -253
  159. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  160. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  161. package/docs/reference/interfaces/FetchResult.md +0 -180
  162. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  163. package/docs/reference/interfaces/HttpResult.md +0 -126
  164. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  165. package/docs/reference/interfaces/PageResult.md +0 -356
  166. package/docs/reference/interfaces/RenderResult.md +0 -72
  167. package/docs/reference/interfaces/RenderedText.md +0 -110
  168. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  169. package/docs/reference/interfaces/ScreenResult.md +0 -128
  170. package/docs/reference/interfaces/TableAccessor.md +0 -26
  171. package/docs/reference/interfaces/TextAccessor.md +0 -96
  172. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  173. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  174. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  175. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  176. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  177. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  178. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  179. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  180. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  181. package/docs/reference/type-aliases/CliEnv.md +0 -11
  182. package/docs/reference/type-aliases/CliHandle.md +0 -80
  183. package/docs/reference/type-aliases/CliInput.md +0 -36
  184. package/docs/reference/type-aliases/CliOutput.md +0 -46
  185. package/docs/reference/type-aliases/CliPort.md +0 -58
  186. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  187. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  188. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  189. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  190. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  191. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  192. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  193. package/docs/reference/type-aliases/Contract.md +0 -68
  194. package/docs/reference/type-aliases/ContractInput.md +0 -13
  195. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  196. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  197. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  198. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  199. package/docs/reference/type-aliases/Contracts.md +0 -48
  200. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  201. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  202. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  203. package/docs/reference/type-aliases/DevicePort.md +0 -51
  204. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  205. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  206. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  207. package/docs/reference/type-aliases/DomMount.md +0 -21
  208. package/docs/reference/type-aliases/ElementKind.md +0 -27
  209. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  210. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  211. package/docs/reference/type-aliases/ElementRef.md +0 -128
  212. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  213. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  214. package/docs/reference/type-aliases/HonoApp.md +0 -31
  215. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  216. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  217. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  218. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  219. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  220. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  221. package/docs/reference/type-aliases/Intercept.md +0 -19
  222. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  223. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  224. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  225. package/docs/reference/type-aliases/JobHandle.md +0 -34
  226. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  227. package/docs/reference/type-aliases/JobsResult.md +0 -16
  228. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  229. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  230. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  231. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  232. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  233. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  234. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  235. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  236. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  237. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  238. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  239. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  240. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  241. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  242. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  243. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  244. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  245. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  246. package/docs/reference/type-aliases/MockPort.md +0 -10
  247. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  248. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  249. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  250. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  251. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  252. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  253. package/docs/reference/type-aliases/ServerPort.md +0 -36
  254. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  255. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  256. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  257. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  258. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  259. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  260. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  261. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  262. package/docs/reference/type-aliases/SpecKind.md +0 -8
  263. package/docs/reference/type-aliases/SpecRun.md +0 -108
  264. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  265. package/docs/reference/type-aliases/SpecStream.md +0 -32
  266. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  267. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  268. package/docs/reference/type-aliases/SseEvent.md +0 -58
  269. package/docs/reference/type-aliases/StreamBody.md +0 -61
  270. package/docs/reference/type-aliases/TextFilter.md +0 -22
  271. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  272. package/docs/reference/type-aliases/Visitor.md +0 -215
  273. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  274. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  275. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  276. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  277. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  278. package/docs/reference/variables/anthropic.md +0 -19
  279. package/docs/reference/variables/banner.md +0 -10
  280. package/docs/reference/variables/button.md +0 -10
  281. package/docs/reference/variables/clock.md +0 -16
  282. package/docs/reference/variables/complementary.md +0 -10
  283. package/docs/reference/variables/component.md +0 -10
  284. package/docs/reference/variables/content.md +0 -10
  285. package/docs/reference/variables/contentinfo.md +0 -10
  286. package/docs/reference/variables/dialog.md +0 -10
  287. package/docs/reference/variables/field.md +0 -10
  288. package/docs/reference/variables/form.md +0 -10
  289. package/docs/reference/variables/heading.md +0 -10
  290. package/docs/reference/variables/http.md +0 -40
  291. package/docs/reference/variables/intercept.md +0 -11
  292. package/docs/reference/variables/link.md +0 -10
  293. package/docs/reference/variables/listitem.md +0 -10
  294. package/docs/reference/variables/main.md +0 -10
  295. package/docs/reference/variables/match.md +0 -8
  296. package/docs/reference/variables/navigation.md +0 -10
  297. package/docs/reference/variables/openai.md +0 -21
  298. package/docs/reference/variables/option.md +0 -17
  299. package/docs/reference/variables/region.md +0 -10
  300. package/docs/reference/variables/row.md +0 -10
  301. package/docs/reference/variables/search.md +0 -10
  302. package/docs/reference/variables/specification.md +0 -8
  303. package/docs/reference/variables/status.md +0 -10
  304. package/docs/reference/variables/table.md +0 -10
@@ -0,0 +1,187 @@
1
+ import { t as isRecord } from "./shared-json-shape.js";
2
+ import { n as refusal } from "./shared-refusal.js";
3
+ import { a as readLedgers, i as measure, n as clearLedgers, r as markPartial, s as shardLedgersDir, t as ROUTES_DIR, u as shouldUpdateSnapshots } from "./shared-ledger.js";
4
+ import { u as parseYamlSource } from "./shared-yaml-document.js";
5
+ import { r as testSettingsAt } from "./shared-test-section.js";
6
+ import { t as setupEntry } from "./shared-setup-entry.js";
7
+ import { n as heldUnreached, r as recordUnreached, t as ROUTES_LEDGER } from "./shared-test-baseline.js";
8
+ import { readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ //#region src/specifications/api/routes/methodless.ts
11
+ function shapeOf(path) {
12
+ return path.replaceAll(/:[^/]+/gu, ":");
13
+ }
14
+ function methodlessDrift(table, methodless) {
15
+ const named = new Set(methodless.map((path) => shapeOf(path)));
16
+ const any = [...new Map(table.filter((route) => route.method === "ALL").map((route) => [shapeOf(route.path), route.path]))];
17
+ const taken = new Set(any.map(([shape]) => shape));
18
+ const missed = any.filter(([shape]) => !named.has(shape)).map(([, path]) => ` ALL ${path}: the app takes it on any method, and the list misses it, so add '${path}' with why the app takes it on any method`);
19
+ const stale = methodless.filter((path) => !taken.has(shapeOf(path))).map((path) => table.some((route) => shapeOf(route.path) === shapeOf(path)) ? ` ${path}: the list names it, and the app takes it on its own methods only, so remove '${path}'` : ` ${path}: the list names it, and the app's table holds no such route, so remove '${path}'`);
20
+ return [...missed, ...stale];
21
+ }
22
+ function methodlessDrifted(drift) {
23
+ return {
24
+ fix: "state in `test: { methodless }` exactly the routes the app takes on any method, each with its reason",
25
+ found: `api routes: \`test: { methodless }\` in jterrazz.config.ts drifted from the app's table:\n${drift.join("\n")}`,
26
+ read: "docs/guides/api-specs.md#a-route-taken-on-any-method",
27
+ why: "`test: { methodless }` names the routes the app takes on any method, which the checker files by their path, whatever verb a spec sends."
28
+ };
29
+ }
30
+ //#endregion
31
+ //#region src/specifications/api/routes/routes.ts
32
+ const METHODS = /* @__PURE__ */ new Set([
33
+ "delete",
34
+ "get",
35
+ "head",
36
+ "options",
37
+ "patch",
38
+ "post",
39
+ "put",
40
+ "trace"
41
+ ]);
42
+ const BRACED_PARAMETER = /^\{[^}]+\}$/u;
43
+ function isParameter(segment) {
44
+ return segment.startsWith(":") || BRACED_PARAMETER.test(segment);
45
+ }
46
+ function takesSegment(declared, sent, template) {
47
+ if (sent === void 0) return false;
48
+ if (template && isParameter(sent)) return isParameter(declared);
49
+ return isParameter(declared) ? sent.length > 0 && !template : sent === declared;
50
+ }
51
+ /** A template request is matched by its template, any other by its path; `ALL` takes any method, a `*` tail the rest. */
52
+ function reachesRoute(route, sent) {
53
+ if (route.method !== "ALL" && route.method !== sent.method) return false;
54
+ const template = sent.template !== void 0;
55
+ const declared = route.path.split("/");
56
+ const segments = ((sent.template ?? sent.path).split("?")[0] ?? "").split("/");
57
+ if (declared.at(-1) === "*") return declared.slice(0, -1).every((segment, index) => takesSegment(segment, segments[index], template));
58
+ return declared.length === segments.length && declared.every((segment, index) => takesSegment(segment, segments[index], template));
59
+ }
60
+ function unreachedRoutes(declared, reached) {
61
+ const seen = /* @__PURE__ */ new Set();
62
+ return declared.filter((route) => {
63
+ const key = `${route.method} ${route.path}`;
64
+ if (seen.has(key) || reached.some((sent) => reachesRoute(route, sent))) return false;
65
+ seen.add(key);
66
+ return true;
67
+ });
68
+ }
69
+ function openApiRoutes(text, json) {
70
+ const parsed = json ? JSON.parse(text) : parseYamlSource(text).document.toJSON();
71
+ const paths = isRecord(parsed) ? parsed.paths : void 0;
72
+ if (!isRecord(paths)) return [];
73
+ return Object.entries(paths).flatMap(([path, item]) => isRecord(item) ? Object.keys(item).filter((method) => METHODS.has(method)).map((method) => ({
74
+ method: method.toUpperCase(),
75
+ path
76
+ })) : []);
77
+ }
78
+ async function declaredRoutes(root) {
79
+ const { routes } = await testSettingsAt(root);
80
+ if (routes === void 0) return;
81
+ return openApiRoutes(readFileSync(join(root, routes), "utf8"), routes.endsWith(".json"));
82
+ }
83
+ async function declaredMethodless(root) {
84
+ const { methodless } = await testSettingsAt(root);
85
+ return [...methodless];
86
+ }
87
+ //#endregion
88
+ //#region src/specifications/api/routes/route-judge.ts
89
+ const UNKNOWN_TABLE = {
90
+ fix: "hand `server` the Hono app itself (its `app.routes` is the table), or declare the routes as an OpenAPI document: `test: { routes: './openapi.yaml' }` in jterrazz.config.ts",
91
+ found: "api routes: an app specification.api() started exposes no route table",
92
+ read: "docs/guides/api-specs.md#an-app-that-is-not-hono",
93
+ why: "A route is judged reached against the table the app declares."
94
+ };
95
+ function spelled(route) {
96
+ return `${route.method} ${route.path}`;
97
+ }
98
+ const SHARD_UNJUDGED = "api routes: not judged, since a shard's ledgers are a part of the run's and the route ratchet is judged only over every shard's ledgers (docs/guides/api-specs.md)";
99
+ const PARTIAL_UNJUDGED = "api routes: not judged, since a partial run (--changed, a file filter or -t) reaches only some routes";
100
+ async function judgeRoutes(root, directory, run) {
101
+ return await judgeLedgers(root, readLedgers(directory), run);
102
+ }
103
+ function unjudgedRun({ failed, scope, update }) {
104
+ if (scope === "shard") return { line: SHARD_UNJUDGED };
105
+ if (scope === "partial") return { line: PARTIAL_UNJUDGED };
106
+ if (failed === 0) return;
107
+ const why = `${failed} file(s) of the run failed, and a failed file may not have sent its requests`;
108
+ return update ? { failure: {
109
+ fix: "make the run green, then record with TEST_UPDATE=1",
110
+ found: `api routes: not recorded: ${why}`,
111
+ read: "docs/guides/api-specs.md#when-a-run-is-not-judged",
112
+ why: "The route ratchet is recorded only from a run that is green and whole."
113
+ } } : { line: `api routes: not judged: ${why}` };
114
+ }
115
+ function ratchetMoved(missing, held, named) {
116
+ return missing > held ? {
117
+ fix: "send each from a spec, or record them with TEST_UPDATE=1",
118
+ found: `api routes: ${missing} declared route(s) no test reached, and the ratchet holds ${held}:\n${named}`,
119
+ read: "docs/guides/api-specs.md#the-ratchet",
120
+ why: "A route no test reached fails the run, beyond the count the ratchet holds."
121
+ } : {
122
+ fix: "the debt fell: record it with TEST_UPDATE=1",
123
+ found: `api routes: ${missing} declared route(s) no test reached, below the ${held} the ratchet holds`,
124
+ read: "docs/guides/api-specs.md#the-ratchet",
125
+ why: "The ratchet is exact in both directions."
126
+ };
127
+ }
128
+ async function judgeLedgers(root, ledgers, { failed, scope, update }) {
129
+ if (ledgers.length === 0) return {};
130
+ const unjudged = unjudgedRun({
131
+ failed,
132
+ scope,
133
+ update
134
+ });
135
+ if (unjudged !== void 0) return unjudged;
136
+ const run = measure(ledgers);
137
+ const table = await declaredRoutes(root) ?? run.declared;
138
+ if (table === void 0) return { failure: UNKNOWN_TABLE };
139
+ const drift = run.declared === void 0 ? [] : methodlessDrift(run.declared, await declaredMethodless(root));
140
+ if (drift.length > 0) return { failure: methodlessDrifted(drift) };
141
+ const declared = [...new Map(table.map((route) => [spelled(route), route])).values()];
142
+ const missing = unreachedRoutes(declared, run.reached);
143
+ const named = missing.map((route) => ` ${spelled(route)}`).join("\n");
144
+ if (update) {
145
+ recordUnreached(root, missing.length);
146
+ return { line: `api routes: ${missing.length} unreached recorded under ${ROUTES_LEDGER}` };
147
+ }
148
+ const held = heldUnreached(root);
149
+ if (missing.length !== held) return { failure: ratchetMoved(missing.length, held, named) };
150
+ return { line: missing.length === 0 ? `api routes: all ${declared.length} declared routes reached` : `api routes: ${missing.length} unreached, held by ${ROUTES_LEDGER}:\n${named}` };
151
+ }
152
+ //#endregion
153
+ //#region src/specifications/api/routes/route-coverage.global-setup.ts
154
+ async function scopeOf(project) {
155
+ if (project.vitest.config.shard !== void 0) return "shard";
156
+ if (project.vitest.config.testNamePattern !== void 0) return "partial";
157
+ const ran = new Set(project.vitest.state.getFiles().filter((file) => file.projectName === project.name).map((file) => file.filepath));
158
+ const { testFiles } = await project.globTestFiles();
159
+ return testFiles.every((file) => ran.has(file)) ? "whole" : "partial";
160
+ }
161
+ function routeCoverageSetup() {
162
+ return setupEntry(import.meta.dirname, "route-coverage");
163
+ }
164
+ function setupRoutes(project) {
165
+ const { root } = project.config;
166
+ const shard = project.vitest.config.shard;
167
+ const directory = join(root, shard === void 0 ? ROUTES_DIR : shardLedgersDir(shard));
168
+ clearLedgers(directory);
169
+ if (shard !== void 0 && project.vitest.config.testNamePattern !== void 0) markPartial(directory);
170
+ project.provide("routesLedger", directory);
171
+ return async () => {
172
+ const { failure, line } = await judgeRoutes(root, directory, {
173
+ failed: project.vitest.state.getFailedFilepaths().length,
174
+ scope: await scopeOf(project),
175
+ update: shouldUpdateSnapshots()
176
+ });
177
+ if (failure !== void 0) throw new Error(refusal(failure));
178
+ if (line !== void 0) process.stdout.write(`${line}\n`);
179
+ };
180
+ }
181
+ function setup(project) {
182
+ return setupRoutes(project);
183
+ }
184
+ //#endregion
185
+ export { setup as n, setupRoutes as r, routeCoverageSetup as t };
186
+
187
+ //# sourceMappingURL=shared-route-coverage.global-setup.js.map
@@ -0,0 +1,198 @@
1
+ import { n as refusal$1 } from "./shared-refusal.js";
2
+ import { lessonParts, refusalBlock } from "@jterrazz/typescript/rules";
3
+ //#region src/visitor/targets/as-written.ts
4
+ const FUNCTION_NAME_OF_KIND = {
5
+ banner: "banner",
6
+ button: "button",
7
+ complementary: "complementary",
8
+ contentinfo: "contentinfo",
9
+ dialog: "dialog",
10
+ field: "field",
11
+ form: "form",
12
+ heading: "heading",
13
+ link: "link",
14
+ listitem: "listitem",
15
+ main: "main",
16
+ navigation: "navigation",
17
+ option: "option",
18
+ region: "region",
19
+ row: "row",
20
+ search: "search",
21
+ status: "status",
22
+ table: "table",
23
+ testId: "testId",
24
+ text: "content"
25
+ };
26
+ /** Spoken in the vocabulary the author wrote, never playwright's: an option appears only when the author stated one. */
27
+ function asWritten(target) {
28
+ const bare = asWrittenUnscoped(target);
29
+ return target.scope ? `within(${asWritten(target.scope)}, ${bare})` : bare;
30
+ }
31
+ function asWrittenUnscoped(target) {
32
+ const args = [];
33
+ if (target.name !== void 0) args.push(JSON.stringify(target.name));
34
+ if (target.exact !== void 0) args.push(`{ exact: ${String(target.exact)} }`);
35
+ return `${FUNCTION_NAME_OF_KIND[target.kind]}(${args.join(", ")})`;
36
+ }
37
+ //#endregion
38
+ //#region src/visitor/matching/landmark-of-tag.ts
39
+ const LANDMARK_ELEMENTS = {
40
+ banner: "header",
41
+ complementary: "aside",
42
+ contentinfo: "footer",
43
+ form: "form",
44
+ main: "main",
45
+ navigation: "nav",
46
+ region: "section",
47
+ search: "search"
48
+ };
49
+ const LANDMARK_SELECTOR = Object.entries(LANDMARK_ELEMENTS).flatMap(([kind, tag]) => [tag, `[role="${kind}"]`]).join(", ");
50
+ function isLandmarkKind(value) {
51
+ return Object.hasOwn(LANDMARK_ELEMENTS, value);
52
+ }
53
+ function landmarkOfTagOrRole(value) {
54
+ const kind = Object.entries(LANDMARK_ELEMENTS).find(([, tag]) => tag === value)?.[0] ?? value;
55
+ return isLandmarkKind(kind) ? kind : void 0;
56
+ }
57
+ //#endregion
58
+ //#region src/visitor/matching/ambiguous.ts
59
+ function landmarkOf(match) {
60
+ const kind = landmarkOfTagOrRole(match.context ?? "");
61
+ if (kind !== "region") return kind === void 0 ? void 0 : `${FUNCTION_NAME_OF_KIND[kind]}()`;
62
+ return match.contextName === void 0 ? void 0 : `${FUNCTION_NAME_OF_KIND.region}(${JSON.stringify(match.contextName)})`;
63
+ }
64
+ function formatMatch(match, index) {
65
+ const attribute = match.detail === void 0 ? "" : ` ${quoteDetail(match)}`;
66
+ const matchedOnLabel = match.accessibleName === void 0 || match.accessibleName === "" ? "" : ` named ${JSON.stringify(match.accessibleName)}`;
67
+ const context = match.context === void 0 || match.context === "" ? "" : ` in <${match.context}>`;
68
+ return ` ${index + 1}. <${match.tag}${attribute}>${match.text}</${match.tag}>${matchedOnLabel}${context}`;
69
+ }
70
+ function quoteDetail(match) {
71
+ return `${match.tag === "a" ? "href" : "name"}=${JSON.stringify(match.detail)}`;
72
+ }
73
+ function landmarkCounts(matches) {
74
+ const counts = /* @__PURE__ */ new Map();
75
+ for (const match of matches) {
76
+ const landmark = landmarkOf(match);
77
+ if (landmark !== void 0) counts.set(landmark, (counts.get(landmark) ?? 0) + 1);
78
+ }
79
+ return [...counts].toSorted(([, a], [, b]) => a - b);
80
+ }
81
+ function formatFixes(target, matches) {
82
+ const fixes = [];
83
+ const landmarksThatCutTheSet = landmarkCounts(matches).filter(([, count]) => count < matches.length);
84
+ if (!target.scope) {
85
+ const [first, ...rest] = landmarksThatCutTheSet;
86
+ const alsoHere = rest.length > 0 ? ` [also here: ${rest.map(([name]) => name).join(", ")}]` : "";
87
+ fixes.push(first === void 0 ? `scope it within(<a container holding only this one>, ${asWrittenUnscoped(target)})` : `scope it within(${first[0]}, ${asWrittenUnscoped(target)}) [leaves ${first[1]} of ${matches.length}]${alsoHere}`);
88
+ }
89
+ if (target.exact === false) {
90
+ const remaining = matches.filter((match) => match.text === target.name).length;
91
+ if (remaining > 0 && remaining < matches.length) fixes.push(`exact name ${asWrittenUnscoped({
92
+ ...target,
93
+ exact: true
94
+ })} [leaves ${remaining} of ${matches.length}]`);
95
+ }
96
+ fixes.push("other element a heading(), button() or field() may name one thing where this does not");
97
+ return fixes;
98
+ }
99
+ function ambiguityError(options) {
100
+ const { target, fixes, lines, where } = options;
101
+ return new Error(refusal$1("target-matches-one-element", {
102
+ fix: ["designate one of them:", ...fixes.map((fix) => ` • ${fix}`)].join("\n"),
103
+ found: [`Ambiguous element: ${asWritten(target)} matched ${lines.length} elements on ${where}:`, ...lines].join("\n")
104
+ }));
105
+ }
106
+ function ambiguousTargetError(options) {
107
+ const { target, matches, url } = options;
108
+ return ambiguityError({
109
+ target,
110
+ fixes: formatFixes(target, matches),
111
+ lines: matches.map((match, index) => formatMatch(match, index)),
112
+ where: url
113
+ });
114
+ }
115
+ function shortened(text) {
116
+ return text.replaceAll(/\s+/gu, " ").trim().slice(0, 80);
117
+ }
118
+ const CANDIDATE_LIMITS = {
119
+ landmarks: LANDMARK_SELECTOR,
120
+ limit: 10,
121
+ textLength: 80
122
+ };
123
+ /** Serialized into the page by playwright: it reads nothing from module scope. */
124
+ function candidatesOf(nodes, { landmarks, limit, textLength }) {
125
+ const landmarkOf = (node) => {
126
+ const landmark = node.parentElement?.closest(landmarks);
127
+ if (landmark === null || landmark === void 0) return {
128
+ context: void 0,
129
+ contextName: void 0
130
+ };
131
+ const labelledBy = (landmark.getAttribute("aria-labelledby") ?? "").split(/\s+/u).filter((id) => id !== "").map((id) => document.querySelector(`#${CSS.escape(id)}`)?.textContent ?? "").join(" ");
132
+ const name = (landmark.getAttribute("aria-label") ?? labelledBy).replaceAll(/\s+/gu, " ").trim();
133
+ return {
134
+ context: landmark.getAttribute("role") ?? landmark.tagName.toLowerCase(),
135
+ contextName: name === "" ? void 0 : name
136
+ };
137
+ };
138
+ return nodes.slice(0, limit).map((node) => {
139
+ const detail = node.getAttribute("href") ?? node.getAttribute("name");
140
+ const text = (node.textContent ?? "").replaceAll(/\s+/gu, " ").trim().slice(0, textLength);
141
+ const label = node.getAttribute("aria-label")?.trim();
142
+ const { context, contextName } = landmarkOf(node);
143
+ return {
144
+ accessibleName: label === void 0 || label === "" || label === text ? void 0 : label,
145
+ context,
146
+ contextName,
147
+ detail: detail ?? void 0,
148
+ tag: node.tagName.toLowerCase(),
149
+ text
150
+ };
151
+ });
152
+ }
153
+ //#endregion
154
+ //#region src/chain/refusals/scenario-failure.ts
155
+ const SURFACE_OF = {
156
+ open: "screen",
157
+ visit: "page"
158
+ };
159
+ const CHAPTER_ANCHOR = /^docs\/[^#]+\.md#.+$/u;
160
+ const isAnchor = (read) => CHAPTER_ANCHOR.test(read);
161
+ function finishedLesson(error) {
162
+ if (!(error instanceof Error)) return;
163
+ const { fix = "", found = "", read, why = "" } = lessonParts(error.message) ?? {};
164
+ const anchor = String(read);
165
+ return [
166
+ fix,
167
+ found,
168
+ why
169
+ ].includes("") || !isAnchor(anchor) ? void 0 : {
170
+ fix,
171
+ found,
172
+ read: anchor,
173
+ why
174
+ };
175
+ }
176
+ function scenarioFailure(error, evidence, verb) {
177
+ const suffix = evidence === null ? "" : `\nEvidence: ${evidence}`;
178
+ const lesson = finishedLesson(error);
179
+ if (error instanceof Error && lesson !== void 0) {
180
+ error.message = refusalBlock({
181
+ ...lesson,
182
+ found: `${lesson.found}${suffix}`
183
+ });
184
+ return error;
185
+ }
186
+ const message = error instanceof Error ? error.message : String(error);
187
+ const surface = SURFACE_OF[verb];
188
+ return new Error(refusal$1({
189
+ fix: `correct the step that failed, or the ${surface} it drives`,
190
+ found: `the ${verb} scenario failed:\n ${message.replaceAll("\n", "\n ")}${suffix}`,
191
+ read: verb === "visit" ? "docs/guides/website-specs.md#visit-scenarios-the-when" : "docs/guides/mobile-specs.md#open-scenarios-the-when",
192
+ why: `${verb === "visit" ? "A" : "An"} ${verb} scenario drives the ${surface} step by step, and the first step that fails ends it.`
193
+ }), { cause: error });
194
+ }
195
+ //#endregion
196
+ export { ambiguityError as a, asWrittenUnscoped as c, shortened as i, CANDIDATE_LIMITS as n, ambiguousTargetError as o, candidatesOf as r, asWritten as s, scenarioFailure as t };
197
+
198
+ //# sourceMappingURL=shared-scenario-failure.js.map