@jterrazz/test 15.3.0 → 16.1.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 (222) hide show
  1. package/README.md +120 -427
  2. package/dist/appium.adapter.js +71 -15
  3. package/dist/artifacts.js +25 -1735
  4. package/dist/browser/index.js +304 -233
  5. package/dist/checker.d.ts +1 -1
  6. package/dist/checker.js +1103 -97
  7. package/dist/fixtures.js +110 -0
  8. package/dist/index.d.ts +956 -971
  9. package/dist/index.js +2159 -2281
  10. package/dist/literate.js +1747 -0
  11. package/dist/match.js +625 -84
  12. package/dist/oxlint.cjs +3585 -1480
  13. package/dist/oxlint.d.cts +60 -48
  14. package/dist/oxlint.d.ts +60 -48
  15. package/dist/oxlint.js +3585 -1480
  16. package/dist/playwright.adapter.js +51 -8
  17. package/dist/{intercept.js → server.js} +2 -2
  18. package/dist/stream.js +6 -6
  19. package/dist/{ambiguity.js → substring-warning.js} +106 -7
  20. package/dist/vitest.d.ts +67 -48
  21. package/dist/vitest.js +229 -93
  22. package/docs/01-architecture.md +95 -0
  23. package/docs/02-developing.md +351 -0
  24. package/docs/03-testing.md +495 -0
  25. package/docs/04-operating.md +101 -0
  26. package/docs/05-module-tests.md +152 -0
  27. package/docs/06-integration.md +180 -0
  28. package/docs/07-component.md +246 -0
  29. package/docs/08-website.md +404 -0
  30. package/docs/09-mobile.md +270 -0
  31. package/docs/10-api.md +264 -0
  32. package/docs/11-jobs.md +157 -0
  33. package/docs/12-cli.md +677 -0
  34. package/docs/13-elements.md +242 -0
  35. package/docs/14-assertions.md +314 -0
  36. package/docs/15-tokens.md +253 -0
  37. package/docs/16-contracts.md +438 -0
  38. package/docs/17-services.md +188 -0
  39. package/docs/18-conventions.md +305 -0
  40. package/docs/19-linting.md +510 -0
  41. package/docs/README.md +36 -0
  42. package/docs/decisions/001-v9-conventions-on-three-channels.md +50 -0
  43. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +49 -0
  44. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +84 -0
  45. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +81 -0
  46. package/docs/decisions/005-the-doubles-ladder-is-closed.md +86 -0
  47. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +245 -0
  48. package/docs/decisions/007-compose-mode-leaves-the-package.md +53 -0
  49. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +75 -0
  50. package/docs/decisions/_template.md +22 -0
  51. package/docs/reference/classes/Matcher.md +120 -0
  52. package/docs/reference/classes/ProcessHandle.md +368 -0
  53. package/docs/reference/functions/defineContract.md +39 -0
  54. package/docs/reference/functions/defineContracts.md +36 -0
  55. package/docs/reference/functions/disabled.md +28 -0
  56. package/docs/reference/functions/enabled.md +20 -0
  57. package/docs/reference/functions/focused.md +26 -0
  58. package/docs/reference/functions/mockOf.md +79 -0
  59. package/docs/reference/functions/postgres.md +31 -0
  60. package/docs/reference/functions/process.md +33 -0
  61. package/docs/reference/functions/redis.md +28 -0
  62. package/docs/reference/functions/required.md +41 -0
  63. package/docs/reference/functions/selected.md +26 -0
  64. package/docs/reference/functions/sqlite.md +33 -0
  65. package/docs/reference/functions/testId.md +20 -0
  66. package/docs/reference/functions/text.md +40 -0
  67. package/docs/reference/functions/valued.md +28 -0
  68. package/docs/reference/functions/waitUntil.md +46 -0
  69. package/docs/reference/functions/within.md +29 -0
  70. package/docs/reference/index.md +196 -0
  71. package/docs/reference/interfaces/BaseResult.md +87 -0
  72. package/docs/reference/interfaces/CallResult.md +160 -0
  73. package/docs/reference/interfaces/CaptureScope.md +82 -0
  74. package/docs/reference/interfaces/CliResult.md +253 -0
  75. package/docs/reference/interfaces/ContainerAccessor.md +166 -0
  76. package/docs/reference/interfaces/DirectoryAccessor.md +33 -0
  77. package/docs/reference/interfaces/FetchResult.md +180 -0
  78. package/docs/reference/interfaces/FilesystemAccessor.md +46 -0
  79. package/docs/reference/interfaces/HttpResult.md +126 -0
  80. package/docs/reference/interfaces/JsonAccessor.md +46 -0
  81. package/docs/reference/interfaces/PageResult.md +356 -0
  82. package/docs/reference/interfaces/RenderResult.md +72 -0
  83. package/docs/reference/interfaces/RenderedText.md +110 -0
  84. package/docs/reference/interfaces/ResponseAccessor.md +47 -0
  85. package/docs/reference/interfaces/ScreenResult.md +128 -0
  86. package/docs/reference/interfaces/TableAccessor.md +26 -0
  87. package/docs/reference/interfaces/TextAccessor.md +96 -0
  88. package/docs/reference/type-aliases/ApiHandle.md +68 -0
  89. package/docs/reference/type-aliases/ApiSpecification.md +213 -0
  90. package/docs/reference/type-aliases/ApiSpecificationOptions.md +68 -0
  91. package/docs/reference/type-aliases/BrowserConsoleMessage.md +30 -0
  92. package/docs/reference/type-aliases/BrowserLinkElement.md +50 -0
  93. package/docs/reference/type-aliases/BrowserMetaElement.md +40 -0
  94. package/docs/reference/type-aliases/BrowserOpenOptions.md +87 -0
  95. package/docs/reference/type-aliases/BrowserPage.md +132 -0
  96. package/docs/reference/type-aliases/BrowserPort.md +51 -0
  97. package/docs/reference/type-aliases/CliEnv.md +11 -0
  98. package/docs/reference/type-aliases/CliHandle.md +80 -0
  99. package/docs/reference/type-aliases/CliInput.md +36 -0
  100. package/docs/reference/type-aliases/CliOutput.md +46 -0
  101. package/docs/reference/type-aliases/CliPort.md +58 -0
  102. package/docs/reference/type-aliases/CliSpecification.md +144 -0
  103. package/docs/reference/type-aliases/CliSpecificationOptions.md +134 -0
  104. package/docs/reference/type-aliases/ComponentChain.md +156 -0
  105. package/docs/reference/type-aliases/ComponentScenario.md +20 -0
  106. package/docs/reference/type-aliases/ComponentUi.md +47 -0
  107. package/docs/reference/type-aliases/ComponentVisitor.md +10 -0
  108. package/docs/reference/type-aliases/ContainerPort.md +113 -0
  109. package/docs/reference/type-aliases/Contract.md +68 -0
  110. package/docs/reference/type-aliases/ContractInput.md +13 -0
  111. package/docs/reference/type-aliases/ContractRequest.md +95 -0
  112. package/docs/reference/type-aliases/ContractResponder.md +23 -0
  113. package/docs/reference/type-aliases/ContractResponse.md +78 -0
  114. package/docs/reference/type-aliases/ContractResponseValue.md +13 -0
  115. package/docs/reference/type-aliases/Contracts.md +48 -0
  116. package/docs/reference/type-aliases/DatabaseKeys.md +16 -0
  117. package/docs/reference/type-aliases/DatabasePort.md +72 -0
  118. package/docs/reference/type-aliases/DeviceOpenOptions.md +46 -0
  119. package/docs/reference/type-aliases/DevicePort.md +51 -0
  120. package/docs/reference/type-aliases/DeviceScreen.md +32 -0
  121. package/docs/reference/type-aliases/DeviceTimeouts.md +39 -0
  122. package/docs/reference/type-aliases/DockerSpecConfig.md +43 -0
  123. package/docs/reference/type-aliases/DomMount.md +21 -0
  124. package/docs/reference/type-aliases/ElementKind.md +27 -0
  125. package/docs/reference/type-aliases/ElementMatch.md +74 -0
  126. package/docs/reference/type-aliases/ElementOptions.md +29 -0
  127. package/docs/reference/type-aliases/ElementRef.md +128 -0
  128. package/docs/reference/type-aliases/ExecOptions.md +37 -0
  129. package/docs/reference/type-aliases/FileAccessor.md +54 -0
  130. package/docs/reference/type-aliases/HonoApp.md +31 -0
  131. package/docs/reference/type-aliases/HttpContractFilter.md +49 -0
  132. package/docs/reference/type-aliases/HttpResponseInit.md +46 -0
  133. package/docs/reference/type-aliases/HttpStreamInit.md +58 -0
  134. package/docs/reference/type-aliases/IntegrationHandle.md +46 -0
  135. package/docs/reference/type-aliases/IntegrationSpecification.md +111 -0
  136. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +42 -0
  137. package/docs/reference/type-aliases/Intercept.md +19 -0
  138. package/docs/reference/type-aliases/InterceptOptions.md +28 -0
  139. package/docs/reference/type-aliases/InterceptScope.md +11 -0
  140. package/docs/reference/type-aliases/IsolationStrategy.md +75 -0
  141. package/docs/reference/type-aliases/JobHandle.md +34 -0
  142. package/docs/reference/type-aliases/JobsHandle.md +57 -0
  143. package/docs/reference/type-aliases/JobsResult.md +16 -0
  144. package/docs/reference/type-aliases/JobsSpecification.md +94 -0
  145. package/docs/reference/type-aliases/JobsSpecificationOptions.md +56 -0
  146. package/docs/reference/type-aliases/LandmarkKind.md +20 -0
  147. package/docs/reference/type-aliases/LiterateRunFlags.md +27 -0
  148. package/docs/reference/type-aliases/LiterateServeRegistration.md +70 -0
  149. package/docs/reference/type-aliases/MatchFixtureOptions.md +27 -0
  150. package/docs/reference/type-aliases/MatchableRequest.md +61 -0
  151. package/docs/reference/type-aliases/MatcherKind.md +34 -0
  152. package/docs/reference/type-aliases/MobileBackendOptions.md +28 -0
  153. package/docs/reference/type-aliases/MobileElementKind.md +14 -0
  154. package/docs/reference/type-aliases/MobileElementMatch.md +60 -0
  155. package/docs/reference/type-aliases/MobileElementRef.md +20 -0
  156. package/docs/reference/type-aliases/MobileHandle.md +65 -0
  157. package/docs/reference/type-aliases/MobileScenario.md +20 -0
  158. package/docs/reference/type-aliases/MobileSpecification.md +51 -0
  159. package/docs/reference/type-aliases/MobileSpecificationOptions.md +126 -0
  160. package/docs/reference/type-aliases/MobileVisitor.md +81 -0
  161. package/docs/reference/type-aliases/MockOfOptions.md +26 -0
  162. package/docs/reference/type-aliases/MockPort.md +10 -0
  163. package/docs/reference/type-aliases/PinnedClock.md +10 -0
  164. package/docs/reference/type-aliases/PostgresOptions.md +32 -0
  165. package/docs/reference/type-aliases/ProcessOptions.md +112 -0
  166. package/docs/reference/type-aliases/RedisOptions.md +20 -0
  167. package/docs/reference/type-aliases/RenderSubject.md +12 -0
  168. package/docs/reference/type-aliases/ScreenNode.md +67 -0
  169. package/docs/reference/type-aliases/ServerPort.md +36 -0
  170. package/docs/reference/type-aliases/ServerResponse.md +46 -0
  171. package/docs/reference/type-aliases/ServerSpec.md +21 -0
  172. package/docs/reference/type-aliases/ServiceHandle.md +216 -0
  173. package/docs/reference/type-aliases/ServiceRecord.md +12 -0
  174. package/docs/reference/type-aliases/SpecDocument.md +84 -0
  175. package/docs/reference/type-aliases/SpecEnvToken.md +21 -0
  176. package/docs/reference/type-aliases/SpecFileAssertion.md +27 -0
  177. package/docs/reference/type-aliases/SpecFixture.md +30 -0
  178. package/docs/reference/type-aliases/SpecKind.md +8 -0
  179. package/docs/reference/type-aliases/SpecRun.md +108 -0
  180. package/docs/reference/type-aliases/SpecServeEntry.md +40 -0
  181. package/docs/reference/type-aliases/SpecStream.md +32 -0
  182. package/docs/reference/type-aliases/SpecificationConfig.md +290 -0
  183. package/docs/reference/type-aliases/SqliteOptions.md +36 -0
  184. package/docs/reference/type-aliases/SseEvent.md +58 -0
  185. package/docs/reference/type-aliases/StreamBody.md +61 -0
  186. package/docs/reference/type-aliases/TextFilter.md +22 -0
  187. package/docs/reference/type-aliases/VisitScenario.md +20 -0
  188. package/docs/reference/type-aliases/Visitor.md +215 -0
  189. package/docs/reference/type-aliases/WaitUntilOptions.md +46 -0
  190. package/docs/reference/type-aliases/WebsiteBackendOptions.md +37 -0
  191. package/docs/reference/type-aliases/WebsiteHandle.md +51 -0
  192. package/docs/reference/type-aliases/WebsiteSpecification.md +118 -0
  193. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +40 -0
  194. package/docs/reference/variables/anthropic.md +19 -0
  195. package/docs/reference/variables/banner.md +10 -0
  196. package/docs/reference/variables/button.md +10 -0
  197. package/docs/reference/variables/clock.md +16 -0
  198. package/docs/reference/variables/complementary.md +10 -0
  199. package/docs/reference/variables/component.md +10 -0
  200. package/docs/reference/variables/content.md +10 -0
  201. package/docs/reference/variables/contentinfo.md +10 -0
  202. package/docs/reference/variables/dialog.md +10 -0
  203. package/docs/reference/variables/field.md +10 -0
  204. package/docs/reference/variables/form.md +10 -0
  205. package/docs/reference/variables/heading.md +10 -0
  206. package/docs/reference/variables/http.md +40 -0
  207. package/docs/reference/variables/intercept.md +11 -0
  208. package/docs/reference/variables/link.md +10 -0
  209. package/docs/reference/variables/listitem.md +10 -0
  210. package/docs/reference/variables/main.md +10 -0
  211. package/docs/reference/variables/match.md +8 -0
  212. package/docs/reference/variables/navigation.md +10 -0
  213. package/docs/reference/variables/openai.md +21 -0
  214. package/docs/reference/variables/option.md +17 -0
  215. package/docs/reference/variables/region.md +10 -0
  216. package/docs/reference/variables/row.md +10 -0
  217. package/docs/reference/variables/search.md +10 -0
  218. package/docs/reference/variables/specification.md +8 -0
  219. package/docs/reference/variables/status.md +10 -0
  220. package/docs/reference/variables/table.md +10 -0
  221. package/package.json +32 -11
  222. package/dist/spec-document.js +0 -651
package/docs/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @jterrazz/test — the corpus
2
+
3
+ The manual of this repository: what the framework is, how it is changed, what proves a change, how it is released, and one chapter per subject it holds. The vitrine is the root `README.md`; the brief a session opens first is the root `AGENTS.md`.
4
+
5
+ | Chapter | Holds |
6
+ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | [01 — Architecture](01-architecture.md) | The runner model behind the six constructors, the five source trees and their edges, the seven enforcement channels, the four entries, what the tarball publishes |
8
+ | [02 — Developing](02-developing.md) | The repository's own loop and what a change owes; then install, first specs, the vitest preset and its helpers |
9
+ | [03 — Testing](03-testing.md) | The vitest projects, the generated capability matrix, the layered reading, the coverage ratchet, the meta-test channel, what CI runs |
10
+ | [04 — Operating](04-operating.md) | The npm release; the peers a consumer brings — required, optional, the provider's exact pin, the native seams; Node 24; the ARIA dialect |
11
+ | [05 — Module tests](05-module-tests.md) | The majority kind: what a `*.test.ts` beside its module may and may not do — `mockOf`, `vi.fn`, `clock`, `intercept`, `vi.stubEnv`, `*.fixtures.ts`, `test.each` |
12
+ | [06 — Integration specs](06-integration.md) | `specification.integration()`: a module against real services or a golden — `.call()`, `value`/`error`, the `integration()` project |
13
+ | [07 — Component specs](07-component.md) | `component`: a rendered unit in Chromium — the chain, the visitor, `tree`/`html`, the `component()` project, the hook and DOM-function recipes |
14
+ | [08 — Website specs](08-website.md) | `specification.website()`: `.fetch()` / `.visit()`, visit scenarios, declared backends, the `head` golden |
15
+ | [09 — Mobile specs](09-mobile.md) | `specification.mobile()`: `.open()`, simulator resolution, open scenarios, the `screen` golden |
16
+ | [10 — API specs](10-api.md) | `specification.api()`: options, the in-process app, `.http` request files, inline actions, seeds, intercepts |
17
+ | [11 — Jobs specs](11-jobs.md) | `specification.jobs()`: in-process pipelines, `.trigger()`, provider error cases |
18
+ | [12 — CLI specs](12-cli.md) | `specification.cli()`: `.exec()`, `.env()`, fixtures and projects, `<case>.spec.yaml` documents, the three doors, services, Docker-aware mode |
19
+ | [13 — Elements](13-elements.md) | The one element vocabulary: descriptors, modifiers, verbs, `see()` semantics, W3 and its evidence, `within()`, exact names, `testId` |
20
+ | [14 — Assertions](14-assertions.md) | The reference: every matcher, grouped by subject, sync/async rules, `toMatch` resolution, diffs |
21
+ | [15 — Tokens](15-tokens.md) | The `{{token}}` grammar: all 21 tokens, `#ref` captures, `match.*`, and update mode end to end |
22
+ | [16 — Contracts](16-contracts.md) | `defineContract` / `defineContracts`, the facade layout, selection, provider builders, `intercept()` and its origin, streams, the browser transport |
23
+ | [17 — Services](17-services.md) | `postgres` / `redis` / `sqlite` / `process`, the services record, init scripts, per-worker isolation, the optional peer each one names |
24
+ | [18 — Conventions](18-conventions.md) | The constitution: the principles, the fork, the doubles ladder, the rule families, the backlog, what stays human, the naming recap, retro-propagation (K1) |
25
+ | [19 — Linting](19-linting.md) | The oxlint plugin (`@jterrazz/test/oxlint`), the conventions checker, and the GENERATED seven-channel rule catalogue |
26
+
27
+ The decisions this package alone took stand in [`decisions/`](decisions/), numbered in the order they were taken, the mold `_template.md` beside them. A decision that spans several repositories belongs to the corpus that spans them. The generated API reference is [`reference/`](reference/) — a projection of the source, never authored by hand.
28
+
29
+ ## How to read it
30
+
31
+ - **01 to 04 are the spine**, and they answer for the repository itself: what it is, how it is changed, what proves a change, how it ships.
32
+ - **05 to 12 are the kinds of test, in the order the fork asks them.** A module test needs no runner and has no folder (05); everything below it is an assembled product met through an entry. Read 02, then the one chapter matching what you specify.
33
+ - **13 to 17 are shared references.** The element vocabulary reaches every rendered surface; assertions and the token grammar reach every facet; contracts apply wherever a facet meets the network, services wherever it has a database. The kind chapters link into them and restate nothing.
34
+ - **18 and 19 cover enforcement.** 18 is the constitution — the principles and the criteria no machine settles. 19 documents the static plugin and the checker, and carries the generated catalogue, sourced from `src/lint/manifest.ts`. The chapters explain and illustrate; the constitution and the catalogue decide.
35
+
36
+ Every kind chapter (05 to 12) is written on the same seven sections — **What it specifies · The constructor · The chain · The result · Unique here · Pitfalls · Related** — so the reader who learned one facet already knows where to look in the next. All examples use the Given/Then comment convention the framework itself enforces (rule B4).
@@ -0,0 +1,50 @@
1
+ # ADR-001: v9 — conventions enforced on three channels
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-07-17
5
+
6
+ Retroactive record, written 2026-09-04 from the v9 design lock (2026-07-17)
7
+ and the `@jterrazz/test@9.0.0` ship (2026-07-18, after a multi-agent review
8
+ campaign). Moved into this repository 2026-09-06, from the OS wiki where it
9
+ was ADR-003 — this package alone can falsify it, and the normative spec was
10
+ always here.
11
+
12
+ ## Context
13
+
14
+ Testing conventions lived in prose and review habits; every consumer repo
15
+ drifted its own way, and partial assertions ("status is 200") hid silent
16
+ failures.
17
+
18
+ ## Decision
19
+
20
+ v9 is specification-first: three constructors (`specification.api/jobs/cli`),
21
+ one `{{token}}` grammar across every fixture kind, and golden files (`.http`
22
+ _requests/responses, full-output text snapshots) as the default assertion — a
23
+ partial probe must earn its place as a deliberate scalpel.
24
+
25
+ Crucially, every convention is enforced on one of THREE channels, or
26
+ documents why it cannot be:
27
+
28
+ 1. **Static** — oxlint rules living IN this package, so a rule versions with
29
+ the API it describes, wired by `@jterrazz/typescript` through composable
30
+ presets (`compose(node, testing)`).
31
+ 2. **Meta-tests** — executed truths: every token matcher tested both ways,
32
+ update-mode fixtures re-verified on the next run.
33
+ 3. **Runtime** — strict errors: an outbound request with no matching intercept
34
+ fails the spec.
35
+
36
+ Any newly discovered defect class must, in the same change, produce a rule on
37
+ one of the channels — the package's "rule K".
38
+
39
+ ## Consequences
40
+
41
+ - Product specs live under `specs/<facet>/<domain>/`; unit tests are siblings
42
+ of their module. A consumer's own conventions derive from this package,
43
+ except where its doctrine states a more specific shape — the first such case
44
+ is jterrazz-os's `docs/decisions/006-cli-specs-mirror-the-command-tree.md`.
45
+ - Migrations proved the value immediately: strict intercepts caught a real
46
+ production-class bug in a consumer (an undrained handler), and dead test
47
+ layers were deleted by the dead-fixture checker.
48
+ - The docs are code: each rule's normative text lives in its implementation's
49
+ metadata and the catalog is generated — hand-synced convention documents are
50
+ banned by construction.
@@ -0,0 +1,49 @@
1
+ # ADR-002: the statique channel holds only what upstream does not
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-09-15
5
+
6
+ ## Context
7
+
8
+ The statique channel grew by writing a rule for every mechanizable convention,
9
+ without asking whether oxlint already carried one. Four hygiene rules ended up
10
+ restating rules of oxlint's own `vitest` plugin — J1 (`.only`/`.skip`), J3 (an
11
+ assertion per test), J4 (unique literal titles), J5 (a lowercase title).
12
+
13
+ That cost nothing while the plugin was off. `@jterrazz/typescript` v10 turns it
14
+ on over the test globs in every profile, so a consumer composing
15
+ `compose(node, testing)` gets both layers: one violation reported twice, two
16
+ ids to suppress, and two normative sentences that have to be kept in step by
17
+ hand — the drift this package's docs-as-code inversion exists to prevent.
18
+
19
+ ## Decision
20
+
21
+ A convention an upstream oxlint rule already enforces belongs to that rule. The
22
+ statique channel holds only the residue: what no loaded plugin carries.
23
+
24
+ Equivalence is proved before a rule is deleted, on this package's own fixtures
25
+ and on both the installed oxlint and the version the preset pins — every
26
+ invalid sample of the rule's test must fire the upstream rule, and every valid
27
+ sample must stay silent. A rule that catches something upstream misses is kept
28
+ and narrowed to that residue.
29
+
30
+ The four were proved equivalent and deleted (`vitest/no-focused-tests` +
31
+ `vitest/no-disabled-tests`, `vitest/expect-expect`, `vitest/no-identical-title`,
32
+ `vitest/prefer-lowercase-title`). J2, the arbitrary-sleep ban, has no upstream
33
+ counterpart and stays.
34
+
35
+ ## Consequences
36
+
37
+ - Breaking for consumers: `jterrazz/j1-no-only-skip`,
38
+ `jterrazz/j3-no-expectless-test`, `jterrazz/j4-unique-test-names` and
39
+ `jterrazz/j5-lowercase-title` no longer exist, and a config still naming one
40
+ fails oxlint's unknown-rule check.
41
+ - The hygiene floor now reaches this package only through the base preset. A
42
+ repository that adopts the conventions without `@jterrazz/typescript` wires
43
+ `plugins: ['vitest']` itself — the catalogue chapter says which rules.
44
+ - One behaviour is configuration, not code: `prefer-lowercase-title` flags an
45
+ all-caps first word (`VALID_CATEGORIES`, `HTTP`, `DI`) that J5 exempted, and
46
+ `allowedPrefixes` restores the exemption.
47
+ - The rule of growth changes with it. A new mechanizable defect class (K1) is
48
+ now searched for upstream first, and a rule is written here only where the
49
+ search comes back empty.
@@ -0,0 +1,84 @@
1
+ # ADR-003: A rendered component is a facet, on Vitest Browser Mode
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-09-19
5
+
6
+ ## Context
7
+
8
+ The estate tested its UI in two dialects at once. A page spec said
9
+ `click(link('Articles'))` against a real Chromium and goldened what the page
10
+ MEANT; beside it, in the same repository, a component test said
11
+ `render / screen / fireEvent / waitFor` against `happy-dom` with a scripted
12
+ `fetch`, and goldened nothing. Same product, same reader, two vocabularies —
13
+ and the second proves the least of the two, because the thing it renders never
14
+ met a browser.
15
+
16
+ The corpus had sanctioned that split. `docs/18-conventions.md` and the agent
17
+ skill both said a component test "needs vitest alone", on the ground that the
18
+ framework is for a SURFACE and a component has none. The reading was wrong in
19
+ one word: a component has no surface it is SERVED through, but it needs
20
+ everything the framework owns — a real browser, a Vite pipeline, a network
21
+ double, a golden engine, an element vocabulary. Rule I2's own message had been
22
+ saying so all along: "a test needing more than its module is a specification".
23
+
24
+ What forced a decision now is that the estate has a UI to migrate (a console's
25
+ 42 tests, a browser extension's DOM renderer, a web app's string renderer) and
26
+ every one of them would otherwise be written a third time.
27
+
28
+ ## Decision
29
+
30
+ A rendered component is a FACET of `@jterrazz/test`, running in Vitest Browser
31
+ Mode on the `playwright` peer the website facet already drives.
32
+
33
+ It is the facet with **no constructor**. A component starts nothing — no
34
+ server, no database, no binary, no simulator — so there is no handle to
35
+ destructure and no `*.specification.ts` to hold it. The chain is exported
36
+ directly (`component.intercept(…).wrap(…).render(<X />, scenario)`), and what
37
+ every render of a project shares belongs to the PROJECT: `component()` in
38
+ `vitest.config.ts`.
39
+
40
+ Its test file sits **beside its subject**, as a module test does, and the
41
+ suffix states the kind: `<file>.test.ts` is a module, `<file>.test.tsx` is a
42
+ component. `unit()` excludes `.test.tsx`; `component()` collects exactly those.
43
+
44
+ The package keeps **one specifier**. A page cannot load the node entry, so
45
+ `exports["."]` gains a `browser` condition resolving to a second build of the
46
+ same surface, in which every node-only name is a stub that throws where it was
47
+ called. The split is the runtime's, never the facets'.
48
+
49
+ Weighed and rejected:
50
+
51
+ - **A Node-side gallery** — Playwright driven from the test process, one
52
+ browser context per render. Measured at ~15 s against 0.6–1.1 s per file for
53
+ Browser Mode, and it needs an undeclared `vite` to build what it serves.
54
+ - **Playwright Component Testing** — a second runner beside vitest, with its
55
+ own config, its own reporters and its own assertion library. The estate has
56
+ one runner on purpose.
57
+ - **`happy-dom` + Testing Library** — the status quo. Two vocabularies, and the
58
+ cheaper one is the one that proves least.
59
+ - **A second specifier, `@jterrazz/test/browser`** — considered and dropped: it
60
+ makes every test file remember which entry it may import, and needs a rule to
61
+ enforce the memory. One specifier and a condition costs nothing at the call
62
+ site.
63
+
64
+ ## Consequences
65
+
66
+ - `happy-dom`, `jsdom` and `@testing-library/*` leave the estate's test
67
+ vocabulary. Rules E5, E5b, F6 and G4 refuse them and name the move; E6
68
+ refuses a hand-rolled `browser:` project, because the provider pin, the
69
+ service worker, the JSX transform and the cold-cache pre-bundling are four
70
+ ways for a run to pass here and fail on the next machine.
71
+ - A component spec and a page spec now read alike: same descriptors, same
72
+ verbs, same W3 ambiguity refusal, same ARIA-tree golden in the same dialect.
73
+ The vocabulary gains what a rendered surface needs and a page lacked —
74
+ `gone`, `focused`, and five role descriptors.
75
+ - A consumer takes on optional peers it did not have (`@vitest/browser-playwright`,
76
+ `vitest-browser-react`, `vite`, `react`), and one hard constraint with them:
77
+ the provider peers vitest on an EXACT version, so the two move in one change.
78
+ - CI pays for a second Chromium project. `sequence.groupOrder` keeps it from
79
+ sharing a slot with the website project on a two-vCPU runner.
80
+ - React Native stays out: Browser Mode does not run it. `.astro` pages stay
81
+ website specs; only islands are components.
82
+ - The package's own suite grows a component tree that is NOT under `_fixtures/`
83
+ — ground is what a spec stands on from a distance, and here the spec stands
84
+ next to it.
@@ -0,0 +1,81 @@
1
+ # ADR-004: A module against real services, or against a golden, is the integration facet
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-09-19
5
+
6
+ ## Context
7
+
8
+ The fork the corpus drew had two rungs: a module alone, tested beside its code
9
+ with no runner, and the assembled product behind an entry — HTTP, a binary, a
10
+ served page, a screen — specified through `specification.*`. A whole kind of
11
+ test fell between them, and every repository invented its own answer for it.
12
+
13
+ A module that needs a real database is one. signews-api kept a
14
+ global-setup file that built a sqlite template by hand and a project named
15
+ "integrations" running plain vitest, because no constructor covered "this
16
+ repository function, against the real schema". package-broadcast and
17
+ package-analytics did the same with a root tests folder and `vi.mock`.
18
+
19
+ A module whose oracle is a GOLDEN is the other. package-attestation's byte
20
+ contracts and package-intelligence's fifty-three input/output pairs are
21
+ golden-file tests, and both reached for `toMatchSnapshot` — a second golden
22
+ mechanism, with no token grammar, no `{ frozen }`, and no `TEST_UPDATE=1`,
23
+ living beside the one the package owns. Rule D20 could ban the matcher only
24
+ once the tests it serves had somewhere to go.
25
+
26
+ Neither case needed a new idea. Both needed a constructor.
27
+
28
+ Three shapes were weighed and refused:
29
+
30
+ - **Widen `jobs`.** `trigger(name)` takes no input and a job is
31
+ `() => Promise<void>`, so the Given would have to leave the test and live in
32
+ the specification file. A spec whose input is elsewhere is not a spec.
33
+ - **Plain vitest under a folder named for it.** The shape every repository had
34
+ already reached, and the shape the evidence indicts: the services are
35
+ hand-started, nothing resets between tests, and the goldens are somebody's.
36
+ - **`sqlite()` outside a runner.** A second lifecycle path for services — two
37
+ answers to "when does this start and who stops it", which is the defect the
38
+ orchestrator exists to prevent.
39
+
40
+ ## Decision
41
+
42
+ `specification.integration({ services?, root? })` is the eighth kind of test
43
+ and the sixth constructor. Its subject is a MODULE; its terminal action is
44
+ `.call((services) => …)`, which hands the started record to the subject so it
45
+ is built with real connection strings; its result is `CallResult`.
46
+
47
+ `CallResult` has two readings and never both: `value` is what the call
48
+ returned — a `TextAccessor` for a string, a `JsonAccessor` for anything else —
49
+ and `error` is what it threw, as text, empty when it returned. Both are the
50
+ package's ordinary accessors, so `toMatch`, the `{{token}}` grammar,
51
+ `{ frozen }` and `TEST_UPDATE=1` reach them with no new mechanism.
52
+
53
+ The setups are the ones every facet already has: `.seed()`, `.intercept()`,
54
+ `.clock()`. Without `services` the constructor starts nothing: that is the
55
+ golden-only half, and it is the whole of what a fifty-three-row table needs —
56
+ one `.call()` inside `test.each`.
57
+
58
+ ## Consequences
59
+
60
+ - **A refusal is a reading.** No `try`/`catch` in a spec, and no "it should
61
+ throw" test that passes because nothing threw. A chain says "and it did not
62
+ refuse" with `await expect(result.error).toBeEmpty()`.
63
+ - **D20 gains its destination.** `toMatchSnapshot` can be banned because every
64
+ test it served now has a facet, a folder and a golden engine.
65
+ - **One more folder name is spoken for, and no unowned one is added.**
66
+ `specs/integration/` is a facet folder: singular, like `website/`, and
67
+ holding a `*.specification.ts` at its root. The package renamed its own
68
+ plural folder in the same change. What lived there — probes of the container
69
+ adapters themselves — became integration specs under
70
+ `specs/integration/<seam>/`: an adapter met against a real service IS a
71
+ module against a real service. Folder = constructor holds for the package
72
+ too, so no probe earns a folder under `specs/` that no constructor owns, and
73
+ what a probe cannot reach through the public entry is a module test beside
74
+ its module (rule F3).
75
+ - **An env-gated sub-suite stops being a project.** With `{ include, exclude }`
76
+ on the helper, package-attestation's five projects become `unit()` plus one
77
+ `integration()`.
78
+ - **A door closed.** A module test may not carry a golden (D20) and an
79
+ integration spec may not assert only on doubles: the ladder's rungs are now
80
+ distinguishable by where the test lives, which is what makes both
81
+ mechanizable.
@@ -0,0 +1,86 @@
1
+ # ADR-005: The doubles ladder is closed, and a ban an upstream rule can carry is an option, not a rule
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-09-19
5
+
6
+ ## Context
7
+
8
+ Two questions had no written answer, and the estate's tests answered them
9
+ differently in every repository.
10
+
11
+ **What may stand in for the real thing?** The measured failure mode is not
12
+ subtle: a survey of the workbench found seven files replacing `fetch` with
13
+ `vi.stubGlobal`, thirteen importing `vitest-mock-extended` directly, sixteen
14
+ calling `vi.mock` on a module, two declaring `msw` themselves, five using
15
+ `toMatchSnapshot`. Every one of them is a substitute chosen because nothing
16
+ said which substitute to reach for first. Agents make this worse rather than
17
+ better — the published measurements put a third of agent-written test commits
18
+ at "adds a mock", and nearly all of those at "mocks everything".
19
+
20
+ **Who owns a ban?** ADR-002 settled that a convention oxlint's own `vitest`
21
+ plugin already enforces belongs to that plugin, and four hygiene rules left the
22
+ `jterrazz/*` channel on that ground. It did not settle the case where the
23
+ upstream rule exists but is inert without OPTIONS —
24
+ `vitest/no-restricted-matchers` bans nothing until it is told which matchers,
25
+ `vitest/no-restricted-vi-methods` until it is told which methods. Those options
26
+ are a convention, and they had nowhere to live.
27
+
28
+ Two shapes were weighed and refused. Writing a `jterrazz/*` rule for each ban
29
+ duplicates an upstream rule that already does the work, which is exactly what
30
+ ADR-002 forbids. Asking `compose()` to MERGE options across fragments is a
31
+ toolchain change with no other caller, and it would make two owners of one
32
+ rule's configuration.
33
+
34
+ ## Decision
35
+
36
+ **The ladder is closed and ordered.** An agent takes the first rung that fits:
37
+
38
+ 1. **The real thing**, in-process — a pure module, a Hono app handed to
39
+ `server`, a component mounted in a real Chromium.
40
+ 2. **A declared service** — `sqlite()`, `postgres()`, `redis()`, `process()`:
41
+ real, isolated per worker, reset per chain.
42
+ 3. **A contract** — `.intercept()` on a chain, `intercept()` in module scope;
43
+ msw's server, msw's worker, or the `node:http` stub, all over one queue;
44
+ strict from the first contract.
45
+ 4. **A typed port double** — `mockOf<Port>()`, or `vi.fn<Fn>()` for a
46
+ function-shaped dependency.
47
+
48
+ Nothing else is a rung. `vi.mock`, `vi.stubGlobal`, a raw `msw` import, `nock`,
49
+ `sinon`, `jest` and `mockdate` are not lower rungs; they are off the ladder,
50
+ and each has a named destination.
51
+
52
+ **A ban an upstream vitest rule can carry as an OPTION is set from the
53
+ `testing` fragment**, and the rulebook marks that rule `covered` so one owner
54
+ states it. A ban a CORE rule would carry — `no-restricted-imports`,
55
+ `no-restricted-globals` — stays a `jterrazz/*` rule, because an oxlint override
56
+ REPLACES a rule's options rather than merging them, and three toolchain owners
57
+ already set those two.
58
+
59
+ This extends ADR-002: the option mechanism is this record's, the principle it
60
+ serves is that one.
61
+
62
+ ## Consequences
63
+
64
+ - **The ladder is citable.** "Use a contract, not a stubbed `fetch`" is a rung
65
+ number, not an opinion, and each ban's message names the rung above it.
66
+ - **`mockOf` earns its place on rung four.** It pins
67
+ `vitest-mock-extended`'s range on the consumer's behalf, which is why the
68
+ consumer is forbidden to declare it. Its `{ deep: false }` form exists so
69
+ rung four does not push a test back down to a hand-written object.
70
+ - **A mandatory type argument is not expressible.** `mockOf<T extends object>()`
71
+ has no default, but TypeScript falls back to the CONSTRAINT where no
72
+ inference site says otherwise, so `mockOf()` compiles and answers for
73
+ `object`. The refusal lands where the double is used — it is assignable to
74
+ no port — rather than where it was made. Stated rather than worked around: a
75
+ cast or a never-typed overload would buy the earlier error with a worse
76
+ signature.
77
+ - **Three bans move upstream.** `no-restricted-matchers` (the snapshot
78
+ matchers), `no-restricted-vi-methods` (`stubGlobal`, the timer methods) and
79
+ `max-nested-describe` are configured, not reimplemented. They turn on in the
80
+ toolchain release that follows the package's next major.
81
+ - **`vi.mock` keeps one door.** A native module a consumer cannot inject is
82
+ allow-listed per specifier, with a reason, and the allow-list is the
83
+ repository's — not a silent exception.
84
+ - **A cost accepted.** Two channels now state bans: the fragment's options and
85
+ the `jterrazz/*` rules. The catalogue carries both, with `upstream` as a
86
+ channel value, so a reader still finds one list.