@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.
- package/README.md +120 -427
- package/dist/appium.adapter.js +71 -15
- package/dist/artifacts.js +25 -1735
- package/dist/browser/index.js +304 -233
- package/dist/checker.d.ts +1 -1
- package/dist/checker.js +1103 -97
- package/dist/fixtures.js +110 -0
- package/dist/index.d.ts +956 -971
- package/dist/index.js +2159 -2281
- package/dist/literate.js +1747 -0
- package/dist/match.js +625 -84
- package/dist/oxlint.cjs +3585 -1480
- package/dist/oxlint.d.cts +60 -48
- package/dist/oxlint.d.ts +60 -48
- package/dist/oxlint.js +3585 -1480
- package/dist/playwright.adapter.js +51 -8
- package/dist/{intercept.js → server.js} +2 -2
- package/dist/stream.js +6 -6
- package/dist/{ambiguity.js → substring-warning.js} +106 -7
- package/dist/vitest.d.ts +67 -48
- package/dist/vitest.js +229 -93
- package/docs/01-architecture.md +95 -0
- package/docs/02-developing.md +351 -0
- package/docs/03-testing.md +495 -0
- package/docs/04-operating.md +101 -0
- package/docs/05-module-tests.md +152 -0
- package/docs/06-integration.md +180 -0
- package/docs/07-component.md +246 -0
- package/docs/08-website.md +404 -0
- package/docs/09-mobile.md +270 -0
- package/docs/10-api.md +264 -0
- package/docs/11-jobs.md +157 -0
- package/docs/12-cli.md +677 -0
- package/docs/13-elements.md +242 -0
- package/docs/14-assertions.md +314 -0
- package/docs/15-tokens.md +253 -0
- package/docs/16-contracts.md +438 -0
- package/docs/17-services.md +188 -0
- package/docs/18-conventions.md +305 -0
- package/docs/19-linting.md +510 -0
- package/docs/README.md +36 -0
- package/docs/decisions/001-v9-conventions-on-three-channels.md +50 -0
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +49 -0
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +84 -0
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +81 -0
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +86 -0
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +245 -0
- package/docs/decisions/007-compose-mode-leaves-the-package.md +53 -0
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +75 -0
- package/docs/decisions/_template.md +22 -0
- package/docs/reference/classes/Matcher.md +120 -0
- package/docs/reference/classes/ProcessHandle.md +368 -0
- package/docs/reference/functions/defineContract.md +39 -0
- package/docs/reference/functions/defineContracts.md +36 -0
- package/docs/reference/functions/disabled.md +28 -0
- package/docs/reference/functions/enabled.md +20 -0
- package/docs/reference/functions/focused.md +26 -0
- package/docs/reference/functions/mockOf.md +79 -0
- package/docs/reference/functions/postgres.md +31 -0
- package/docs/reference/functions/process.md +33 -0
- package/docs/reference/functions/redis.md +28 -0
- package/docs/reference/functions/required.md +41 -0
- package/docs/reference/functions/selected.md +26 -0
- package/docs/reference/functions/sqlite.md +33 -0
- package/docs/reference/functions/testId.md +20 -0
- package/docs/reference/functions/text.md +40 -0
- package/docs/reference/functions/valued.md +28 -0
- package/docs/reference/functions/waitUntil.md +46 -0
- package/docs/reference/functions/within.md +29 -0
- package/docs/reference/index.md +196 -0
- package/docs/reference/interfaces/BaseResult.md +87 -0
- package/docs/reference/interfaces/CallResult.md +160 -0
- package/docs/reference/interfaces/CaptureScope.md +82 -0
- package/docs/reference/interfaces/CliResult.md +253 -0
- package/docs/reference/interfaces/ContainerAccessor.md +166 -0
- package/docs/reference/interfaces/DirectoryAccessor.md +33 -0
- package/docs/reference/interfaces/FetchResult.md +180 -0
- package/docs/reference/interfaces/FilesystemAccessor.md +46 -0
- package/docs/reference/interfaces/HttpResult.md +126 -0
- package/docs/reference/interfaces/JsonAccessor.md +46 -0
- package/docs/reference/interfaces/PageResult.md +356 -0
- package/docs/reference/interfaces/RenderResult.md +72 -0
- package/docs/reference/interfaces/RenderedText.md +110 -0
- package/docs/reference/interfaces/ResponseAccessor.md +47 -0
- package/docs/reference/interfaces/ScreenResult.md +128 -0
- package/docs/reference/interfaces/TableAccessor.md +26 -0
- package/docs/reference/interfaces/TextAccessor.md +96 -0
- package/docs/reference/type-aliases/ApiHandle.md +68 -0
- package/docs/reference/type-aliases/ApiSpecification.md +213 -0
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +68 -0
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +30 -0
- package/docs/reference/type-aliases/BrowserLinkElement.md +50 -0
- package/docs/reference/type-aliases/BrowserMetaElement.md +40 -0
- package/docs/reference/type-aliases/BrowserOpenOptions.md +87 -0
- package/docs/reference/type-aliases/BrowserPage.md +132 -0
- package/docs/reference/type-aliases/BrowserPort.md +51 -0
- package/docs/reference/type-aliases/CliEnv.md +11 -0
- package/docs/reference/type-aliases/CliHandle.md +80 -0
- package/docs/reference/type-aliases/CliInput.md +36 -0
- package/docs/reference/type-aliases/CliOutput.md +46 -0
- package/docs/reference/type-aliases/CliPort.md +58 -0
- package/docs/reference/type-aliases/CliSpecification.md +144 -0
- package/docs/reference/type-aliases/CliSpecificationOptions.md +134 -0
- package/docs/reference/type-aliases/ComponentChain.md +156 -0
- package/docs/reference/type-aliases/ComponentScenario.md +20 -0
- package/docs/reference/type-aliases/ComponentUi.md +47 -0
- package/docs/reference/type-aliases/ComponentVisitor.md +10 -0
- package/docs/reference/type-aliases/ContainerPort.md +113 -0
- package/docs/reference/type-aliases/Contract.md +68 -0
- package/docs/reference/type-aliases/ContractInput.md +13 -0
- package/docs/reference/type-aliases/ContractRequest.md +95 -0
- package/docs/reference/type-aliases/ContractResponder.md +23 -0
- package/docs/reference/type-aliases/ContractResponse.md +78 -0
- package/docs/reference/type-aliases/ContractResponseValue.md +13 -0
- package/docs/reference/type-aliases/Contracts.md +48 -0
- package/docs/reference/type-aliases/DatabaseKeys.md +16 -0
- package/docs/reference/type-aliases/DatabasePort.md +72 -0
- package/docs/reference/type-aliases/DeviceOpenOptions.md +46 -0
- package/docs/reference/type-aliases/DevicePort.md +51 -0
- package/docs/reference/type-aliases/DeviceScreen.md +32 -0
- package/docs/reference/type-aliases/DeviceTimeouts.md +39 -0
- package/docs/reference/type-aliases/DockerSpecConfig.md +43 -0
- package/docs/reference/type-aliases/DomMount.md +21 -0
- package/docs/reference/type-aliases/ElementKind.md +27 -0
- package/docs/reference/type-aliases/ElementMatch.md +74 -0
- package/docs/reference/type-aliases/ElementOptions.md +29 -0
- package/docs/reference/type-aliases/ElementRef.md +128 -0
- package/docs/reference/type-aliases/ExecOptions.md +37 -0
- package/docs/reference/type-aliases/FileAccessor.md +54 -0
- package/docs/reference/type-aliases/HonoApp.md +31 -0
- package/docs/reference/type-aliases/HttpContractFilter.md +49 -0
- package/docs/reference/type-aliases/HttpResponseInit.md +46 -0
- package/docs/reference/type-aliases/HttpStreamInit.md +58 -0
- package/docs/reference/type-aliases/IntegrationHandle.md +46 -0
- package/docs/reference/type-aliases/IntegrationSpecification.md +111 -0
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +42 -0
- package/docs/reference/type-aliases/Intercept.md +19 -0
- package/docs/reference/type-aliases/InterceptOptions.md +28 -0
- package/docs/reference/type-aliases/InterceptScope.md +11 -0
- package/docs/reference/type-aliases/IsolationStrategy.md +75 -0
- package/docs/reference/type-aliases/JobHandle.md +34 -0
- package/docs/reference/type-aliases/JobsHandle.md +57 -0
- package/docs/reference/type-aliases/JobsResult.md +16 -0
- package/docs/reference/type-aliases/JobsSpecification.md +94 -0
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +56 -0
- package/docs/reference/type-aliases/LandmarkKind.md +20 -0
- package/docs/reference/type-aliases/LiterateRunFlags.md +27 -0
- package/docs/reference/type-aliases/LiterateServeRegistration.md +70 -0
- package/docs/reference/type-aliases/MatchFixtureOptions.md +27 -0
- package/docs/reference/type-aliases/MatchableRequest.md +61 -0
- package/docs/reference/type-aliases/MatcherKind.md +34 -0
- package/docs/reference/type-aliases/MobileBackendOptions.md +28 -0
- package/docs/reference/type-aliases/MobileElementKind.md +14 -0
- package/docs/reference/type-aliases/MobileElementMatch.md +60 -0
- package/docs/reference/type-aliases/MobileElementRef.md +20 -0
- package/docs/reference/type-aliases/MobileHandle.md +65 -0
- package/docs/reference/type-aliases/MobileScenario.md +20 -0
- package/docs/reference/type-aliases/MobileSpecification.md +51 -0
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +126 -0
- package/docs/reference/type-aliases/MobileVisitor.md +81 -0
- package/docs/reference/type-aliases/MockOfOptions.md +26 -0
- package/docs/reference/type-aliases/MockPort.md +10 -0
- package/docs/reference/type-aliases/PinnedClock.md +10 -0
- package/docs/reference/type-aliases/PostgresOptions.md +32 -0
- package/docs/reference/type-aliases/ProcessOptions.md +112 -0
- package/docs/reference/type-aliases/RedisOptions.md +20 -0
- package/docs/reference/type-aliases/RenderSubject.md +12 -0
- package/docs/reference/type-aliases/ScreenNode.md +67 -0
- package/docs/reference/type-aliases/ServerPort.md +36 -0
- package/docs/reference/type-aliases/ServerResponse.md +46 -0
- package/docs/reference/type-aliases/ServerSpec.md +21 -0
- package/docs/reference/type-aliases/ServiceHandle.md +216 -0
- package/docs/reference/type-aliases/ServiceRecord.md +12 -0
- package/docs/reference/type-aliases/SpecDocument.md +84 -0
- package/docs/reference/type-aliases/SpecEnvToken.md +21 -0
- package/docs/reference/type-aliases/SpecFileAssertion.md +27 -0
- package/docs/reference/type-aliases/SpecFixture.md +30 -0
- package/docs/reference/type-aliases/SpecKind.md +8 -0
- package/docs/reference/type-aliases/SpecRun.md +108 -0
- package/docs/reference/type-aliases/SpecServeEntry.md +40 -0
- package/docs/reference/type-aliases/SpecStream.md +32 -0
- package/docs/reference/type-aliases/SpecificationConfig.md +290 -0
- package/docs/reference/type-aliases/SqliteOptions.md +36 -0
- package/docs/reference/type-aliases/SseEvent.md +58 -0
- package/docs/reference/type-aliases/StreamBody.md +61 -0
- package/docs/reference/type-aliases/TextFilter.md +22 -0
- package/docs/reference/type-aliases/VisitScenario.md +20 -0
- package/docs/reference/type-aliases/Visitor.md +215 -0
- package/docs/reference/type-aliases/WaitUntilOptions.md +46 -0
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +37 -0
- package/docs/reference/type-aliases/WebsiteHandle.md +51 -0
- package/docs/reference/type-aliases/WebsiteSpecification.md +118 -0
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +40 -0
- package/docs/reference/variables/anthropic.md +19 -0
- package/docs/reference/variables/banner.md +10 -0
- package/docs/reference/variables/button.md +10 -0
- package/docs/reference/variables/clock.md +16 -0
- package/docs/reference/variables/complementary.md +10 -0
- package/docs/reference/variables/component.md +10 -0
- package/docs/reference/variables/content.md +10 -0
- package/docs/reference/variables/contentinfo.md +10 -0
- package/docs/reference/variables/dialog.md +10 -0
- package/docs/reference/variables/field.md +10 -0
- package/docs/reference/variables/form.md +10 -0
- package/docs/reference/variables/heading.md +10 -0
- package/docs/reference/variables/http.md +40 -0
- package/docs/reference/variables/intercept.md +11 -0
- package/docs/reference/variables/link.md +10 -0
- package/docs/reference/variables/listitem.md +10 -0
- package/docs/reference/variables/main.md +10 -0
- package/docs/reference/variables/match.md +8 -0
- package/docs/reference/variables/navigation.md +10 -0
- package/docs/reference/variables/openai.md +21 -0
- package/docs/reference/variables/option.md +17 -0
- package/docs/reference/variables/region.md +10 -0
- package/docs/reference/variables/row.md +10 -0
- package/docs/reference/variables/search.md +10 -0
- package/docs/reference/variables/specification.md +8 -0
- package/docs/reference/variables/status.md +10 -0
- package/docs/reference/variables/table.md +10 -0
- package/package.json +32 -11
- package/dist/spec-document.js +0 -651
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# 03 — Testing
|
|
2
|
+
|
|
3
|
+
What proves a change here: this package specifies itself with itself. The suites under `specs/` are written with `@jterrazz/test` against fixture apps, the module tests sit beside the modules they cover, and a family of meta-tests runs the framework on its own output. This chapter says which suite answers for what, and what a change owes each of them.
|
|
4
|
+
|
|
5
|
+
**The suffix says the kind.** `.test.ts` is the UNIT's word and sits beside the module it covers; `.test.tsx` is the same law for a rendered unit; `.spec.ts` is the ASSEMBLED product's word and lives under `specs/<facet>/`; a literate document stays `<case>.spec.yaml`. C12 holds both directions and its `--fix` renames with `git mv`, so a reader can tell what a file proves without opening it.
|
|
6
|
+
|
|
7
|
+
| Ground | Where | Proves |
|
|
8
|
+
| --------------- | ----------------------------------------- | ---------------------------------------------------------------------- |
|
|
9
|
+
| Module tests | `src/**/<file>.test.ts` | One module's behaviour, beside it (rule I2) |
|
|
10
|
+
| Component tests | `specs/component-app/<file>.test.tsx` | A rendered unit, beside it, in a real Chromium ([07](07-component.md)) |
|
|
11
|
+
| Product specs | `specs/<facet>/<domain>/<aspect>.spec.ts` | The framework's own facets, through the public surface |
|
|
12
|
+
| Spec documents | `specs/cli/literate/*.spec.yaml` | The document format, collected as test files by `literate()` |
|
|
13
|
+
| Meta-tests | `src/lint/*.test.ts` | The framework applied to itself and to its own projections |
|
|
14
|
+
|
|
15
|
+
## The seven projects
|
|
16
|
+
|
|
17
|
+
`test.projects` in `vitest.config.ts` declares seven, and which one you can run is decided by what is installed and running on the machine.
|
|
18
|
+
|
|
19
|
+
| Project | Collects | Needs |
|
|
20
|
+
| ------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
21
|
+
| `unit` | `src/**/*.test.ts` + `specs/lint/**/*.test.ts`, built by `unit()` | Nothing — the lint specs need `npm run build` first |
|
|
22
|
+
| `cli` | `specs/cli/**`, built by `cli()` — its documents included, through `literate()` | Nothing — the Docker specs self-skip |
|
|
23
|
+
| `api` | `specs/api/**/*.spec.ts`, built by `api()` (node mode, in-process Hono) | Docker |
|
|
24
|
+
| `jobs` | `specs/jobs/**`, built by `jobs()` | Docker |
|
|
25
|
+
| `integration` | `specs/integration/**`, built by `integration({ serial: true })` — the container seams included | Docker |
|
|
26
|
+
| `website` | `specs/website/**`, built by `website()` | playwright + `npx playwright install chromium`; no Docker |
|
|
27
|
+
| `component` | `specs/component-app/**/*.test.tsx`, built by `component()` | the same chromium; no Docker. Runs in its own group, after `website` |
|
|
28
|
+
|
|
29
|
+
Every project the package runs comes from a helper, so `--project api` means the same tree here as in any consumer — `unit()` included, with the globs it takes naming the one specs tree that is no facet's. A member without `"type": "module"` writes its config as `vitest.config.mts`: the helpers ship ESM only, a CJS config would have to `require()` them, and `roleOf` already reads `.mts` as the config role — so the rules that judge a config still find it.
|
|
30
|
+
|
|
31
|
+
Why a file is reached by its ROLE and named by its suffix, and what was weighed against it: [ADR-006](decisions/006-the-catalogue-reaches-a-file-by-its-role.md).
|
|
32
|
+
|
|
33
|
+
**`specs/lint/` is a repository suite, and it keeps `.test.ts`.** A repository suite covers a TREE rather than one assembled product: these files run the plugin and the checker over fixture PROJECTS, one per rule, and what they specify is a convention over a repository's shape. Its first level is not one of the six facets, so C12's rename clause never reaches it, C1's declared depth is what judges it, and `unit()` is what collects it — the shape the constitution calls [the third door](18-conventions.md#the-third-door-a-repository-suite).
|
|
34
|
+
|
|
35
|
+
**The `unit` project's budget is the whole suite's floor.** It collects the module tests and the lint suite and runs with no service, no browser and no container, so it is the one project a developer runs on every save: the same tree takes between nine and eleven seconds on consecutive runs of a developer machine, and **the budget it may not pass is twelve**. The budget is set above the range and not at the best measurement, because a budget set at the best measurement is a budget that fails on noise. The meta-test that proves every rule's reach (`k4-reach-per-path`) runs inside it on ONE oxlint run for the same reason.
|
|
36
|
+
|
|
37
|
+
Every FACET folder under `specs/` belongs to a constructor, and the one folder that is not a facet says so in its name: `specs/component-app/` is the rendered subject this package ships, and its component tests sit beside its components because that is what a rendered unit's test does (A2, I2). A probe of a container seam — the postgres and redis handles — is a module against a real service, so it is an integration spec: `specs/integration/<seam>/`, on `specification.integration({ services })` and its one terminal action. What the seam answers BEFORE it reaches a container, and the one adapter the public entry does not publish (`TestcontainersAdapter`), are module tests beside their modules under `src/seams/` — a spec reaches the framework through its public entry, and a probe that cannot is telling you where it belongs (rule F3).
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm test # every project — Docker and chromium both required
|
|
41
|
+
npx vitest --run --project unit # the loop: no infrastructure, after npm run build
|
|
42
|
+
npx vitest --run --project website # after npx playwright install chromium
|
|
43
|
+
npx vitest --run --project component # the same chromium, a mounted unit at a time
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
There is no mobile tree under `specs/`, and that is a hole this chapter states rather than hides: an iOS simulator is not something CI provisions, so the mobile facet is proven by module tests under `src/facets/mobile/` — the simulator resolution, the page-source projection, the ambiguity messages — and by nothing end-to-end.
|
|
47
|
+
|
|
48
|
+
A second hole is the RENDERER's, and it is not this repository's to close: a React Native screen never reaches the real Chromium the component facet mounts in, so jest stays on that surface until the `react-native-web` spike says otherwise — [07 — Component specs](07-component.md) lists it with the other three shapes the facet does not take.
|
|
49
|
+
|
|
50
|
+
## How a spec tree is laid out
|
|
51
|
+
|
|
52
|
+
The layout is the one the conventions enforce on every consumer (rule C1), and this repository is its first consumer. A facet carries its runners at its ROOT and its tests one level down, in domain folders:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
specs/
|
|
56
|
+
├── api/
|
|
57
|
+
│ ├── api.specification.ts # runners at the facet root
|
|
58
|
+
│ ├── intercepts.specification.ts
|
|
59
|
+
│ └── requests/ # a domain
|
|
60
|
+
│ ├── requests.spec.ts
|
|
61
|
+
│ ├── _requests/ # ground: complete requests, *.http
|
|
62
|
+
│ └── _expected/ # ground: every expected fixture, flat
|
|
63
|
+
└── _fixtures/ # the SHARED pool, reached as $FIXTURES/…
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**A domain shared by two facets carries the same name in both.** Six
|
|
67
|
+
capabilities belong to no facet in particular — a runner's `lifecycle`, the
|
|
68
|
+
declared network (`intercepts`), the ground a chain loads (`seeding`), what a
|
|
69
|
+
result answers (`assertions`), the `{{token}}` engine (`tokens`) and the pinned
|
|
70
|
+
calendar (`clock`) — and a facet that gives one of them a FOLDER calls it by
|
|
71
|
+
that name, so `specs/api/clock/` and `specs/website/clock/` are the same
|
|
72
|
+
question asked of two constructors. Everything else is one facet's own, and a
|
|
73
|
+
name that is one facet's own is never another's: two names for one capability
|
|
74
|
+
is the drift the vocabulary exists to stop.
|
|
75
|
+
|
|
76
|
+
It says nothing about where a METHOD may appear. `.seed()` is used inside jobs'
|
|
77
|
+
`triggering/` scenarios and integration's `call/` ones, because seeding is the
|
|
78
|
+
Given there and not the subject; a capability earns a folder when it is what
|
|
79
|
+
the specs in it are ABOUT.
|
|
80
|
+
|
|
81
|
+
The vocabulary is declared in `src/lint/domains.ts` and `domains.test.ts` holds
|
|
82
|
+
the tree equal to it in both directions — a folder nothing declares fails, and
|
|
83
|
+
a declared name nothing carries fails too.
|
|
84
|
+
|
|
85
|
+
<!-- GENERATED:domains — do not edit by hand; run `npm run docs`. Source: src/lint/domains.ts -->
|
|
86
|
+
|
|
87
|
+
| Facet | Shared domains it carries | Its own |
|
|
88
|
+
| ------------- | ---------------------------------------------------------------- | ----------------------------------------------------- |
|
|
89
|
+
| `api` | `assertions/`, `clock/`, `intercepts/`, `lifecycle/`, `seeding/` | `initiation-errors/`, `requests/`, `responses/` |
|
|
90
|
+
| `cli` | `assertions/`, `seeding/`, `tokens/` | `directory/`, `docker/`, `env/`, `exec/`, `literate/` |
|
|
91
|
+
| `integration` | `clock/`, `intercepts/` | `call/`, `golden/`, `postgres/`, `redis/` |
|
|
92
|
+
| `jobs` | — | `triggering/` |
|
|
93
|
+
| `website` | `assertions/`, `clock/` | `console/`, `fetch/`, `services/`, `visit/` |
|
|
94
|
+
|
|
95
|
+
<!-- /GENERATED:domains -->
|
|
96
|
+
|
|
97
|
+
A test at a facet root is forbidden and a `*.specification.ts` inside a domain is forbidden; a leading underscore means ground, never a domain. Which of the two legal shapes a given tree takes — its own domain, or sibling tests in a named group folder — is decided by the assets, and that judgement is the process channel's ([18 — Conventions](18-conventions.md)).
|
|
98
|
+
|
|
99
|
+
The fixture apps the specs drive live in the pool: `app` and `website-app` for the served facets, `cli-app`, `docker-cli`, `checker-cli` and `lint-cli` for the command facets, the `broken-*` trees for the infrastructure failure paths, and `lint-violations/` — a violation/compliant twin per lint rule.
|
|
100
|
+
|
|
101
|
+
**The component tree is the one exception, and the exception is the rule it dogfoods.** A rendered unit's test sits BESIDE the unit, so `specs/component-app/` holds the fixture app and its specs as neighbours — a table with a contract, a form carrying every verb, a modal hook with its Host in the test, a routed link, a vanilla-DOM list, a clock stamp, a noisy panel, a responsive note, a control that refuses input and a pair of links whose names overlap. It is not under `_fixtures/`: ground is what a spec stands on from a distance, and here the spec stands next to it. The exception is HELD by rules, not granted by omission: I2 requires every one of those `.test.tsx` files to have the `.tsx` (or the `.ts` of a hook) of the same basename beside it, and C1's row says a `.test.tsx` is out of its reach because a rendered unit never lives in a facet/domain tree.
|
|
102
|
+
|
|
103
|
+
## The layered reading
|
|
104
|
+
|
|
105
|
+
The package proves itself in five layers, and they are meant to be read from the inside out: a module against its own exports, a rule against its own fixtures, a facet against its own tree, the built binary against a fixture project, and the CORPUS against what a meta-test says it must keep true. Each layer judges something the one below it cannot — and the meta-tests are counted apart rather than folded into the module tests, because what they judge is the one thing a test beside a module cannot see.
|
|
106
|
+
|
|
107
|
+
<!-- GENERATED:layers — do not edit by hand; run `npm run docs`. Source: src/lint/matrix.ts -->
|
|
108
|
+
|
|
109
|
+
| Layer | Where | What it judges | Files |
|
|
110
|
+
| ----------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------ | ----- |
|
|
111
|
+
| Module tests | `src/**/*.test.ts` beside the module | one module, through its own exports, with nothing started | 52 |
|
|
112
|
+
| Rule tests | `src/lint/rules/<facet>/<rule>.test.ts` beside the rule | one rule: what it flags, what it leaves alone, and the message it prints | 56 |
|
|
113
|
+
| The package’s own specs | `specs/<facet>/` | the framework's own facets, each met through its constructor | 65 |
|
|
114
|
+
| The lint suite | `specs/lint/**` | the built binary end to end, over fixture projects | 88 |
|
|
115
|
+
| Meta-tests | `src/lint/*.test.ts` | the corpus itself: the catalogue, the matrix, the cards, the floor | 18 |
|
|
116
|
+
|
|
117
|
+
<!-- /GENERATED:layers -->
|
|
118
|
+
|
|
119
|
+
The counts are generated: a layer that stops growing while the surface does is visible here before it is visible in a bug.
|
|
120
|
+
|
|
121
|
+
## The lint suite is end-to-end
|
|
122
|
+
|
|
123
|
+
`specs/lint/**` runs the REAL oxlint binary and the real checker over the fixture projects and goldens their output, grouped by convention family (`runners/`, `chains/`, `files/`, `assertions/`, `imports/`, `architecture/`, `hygiene/`, `website/`, `checker/`, `meta/`). Because it loads `dist/oxlint.js`, `npm run build` must precede it — the same ordering `npm run lint` depends on.
|
|
124
|
+
|
|
125
|
+
Its goldens are full snapshots, not greps: `specs/lint/checker/_expected/*.txt` holds the exact lines the checker prints, including the chapter each message points a consumer at. A message that changes its wording moves its golden with it, in the same commit.
|
|
126
|
+
|
|
127
|
+
## Coverage is a ratchet, not a threshold
|
|
128
|
+
|
|
129
|
+
`npx vitest --run --coverage` turns on v8 — the runtime's own counter, so there
|
|
130
|
+
is no instrumentation pass and the numbers do not move when the bundler does —
|
|
131
|
+
and writes three reports where the preset sends them ([02 — Developing §
|
|
132
|
+
Artefacts live under `.artifacts/`](02-developing.md#artefacts-live-under-artifacts)):
|
|
133
|
+
`text` for the person who ran it, `html` for the one chasing a line, and
|
|
134
|
+
`json-summary`, which is the one a machine reads.
|
|
135
|
+
|
|
136
|
+
`npm run coverage` records where the suite stands in `coverage.baseline.json`;
|
|
137
|
+
`npm run coverage:check` refuses a run that fell below it. Neither runs the
|
|
138
|
+
suite: both read the report of the LAST `--coverage` run, which is why
|
|
139
|
+
`make test` runs the suite WITH coverage (`npm test -- --coverage`) before the
|
|
140
|
+
gate — a plain `npm test` would leave it judging an older run, or, on a fresh
|
|
141
|
+
clone where `.artifacts/` does not exist yet, nothing at all. That target is
|
|
142
|
+
the one CI calls ([What CI runs](#what-ci-runs)), so the floor is judged on
|
|
143
|
+
every push. It only ever rises: lowering it is an edit to a committed file,
|
|
144
|
+
with a commit body saying why.
|
|
145
|
+
|
|
146
|
+
### One floor, over the whole suite
|
|
147
|
+
|
|
148
|
+
The baseline is keyed by scope and the CLI
|
|
149
|
+
takes `--scope`, so a project can keep its own; this repository records `all`
|
|
150
|
+
and nothing else, because the projects overlap — the browser projects and the
|
|
151
|
+
node ones both execute `model/`, and four floors summing to more than the tree
|
|
152
|
+
would ratchet on which project happened to run. A scope missing any of the four
|
|
153
|
+
metrics is refused rather than read as a zero, since a floor of zero forbids
|
|
154
|
+
nothing.
|
|
155
|
+
|
|
156
|
+
### Why a ratchet and not a threshold
|
|
157
|
+
|
|
158
|
+
A fixed threshold would be a number somebody picked. Set it where the project
|
|
159
|
+
stands and it forbids nothing; set it where the project should be and every run
|
|
160
|
+
is red until it gets there, which is how a gate becomes something people pass
|
|
161
|
+
with `--no-verify`. A ratchet asks the only question a gate can answer
|
|
162
|
+
honestly: is this change worse than the last one?
|
|
163
|
+
|
|
164
|
+
### What the report excludes
|
|
165
|
+
|
|
166
|
+
What the report EXCLUDES is the shape of the answer, not a way to raise the
|
|
167
|
+
number: `node_modules/` (Browser Mode instruments the page's bundle, and
|
|
168
|
+
react-dom alone is 19 000 statements), `specs/` and `_fixtures/` (a spec is a
|
|
169
|
+
test and the app it drives is ground), the type-only and config files.
|
|
170
|
+
|
|
171
|
+
## Modules with no sibling test
|
|
172
|
+
|
|
173
|
+
I2 says a module test lives beside its module. It never said every module has
|
|
174
|
+
one, and it cannot: a barrel executes nothing, a `*.port.ts` declares a shape,
|
|
175
|
+
and a facet's constructor is proven by the tree that constructs it. What was
|
|
176
|
+
missing is the LIST of the ones that do not, and why — a module with real
|
|
177
|
+
behaviour, no sibling test and nobody able to say what covers it is the silence
|
|
178
|
+
this section closes.
|
|
179
|
+
|
|
180
|
+
`src/lint/siblings.ts` declares the kinds, each naming the PATHS it claims —
|
|
181
|
+
never a prefix, because a kind claiming `src/seams/` would claim every module
|
|
182
|
+
written there next year and K7 would be a poster. **K7** fails on a module that
|
|
183
|
+
matches none of them, which is what makes a new, unclaimed module fail the
|
|
184
|
+
suite on the day it lands.
|
|
185
|
+
|
|
186
|
+
<!-- GENERATED:siblings — do not edit by hand; run `npm run docs`. Source: src/lint/siblings.ts -->
|
|
187
|
+
|
|
188
|
+
Today **81 modules** have no sibling test, and every one of them is claimed:
|
|
189
|
+
|
|
190
|
+
| Kind | Proven instead by |
|
|
191
|
+
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
192
|
+
| composition root or barrel (4) | it wires and re-exports; what it publishes is held by `package-exports.test.ts` and by every spec that imports the entry |
|
|
193
|
+
| type-only declaration (11) | it declares a shape and executes nothing; the compiler is its test, and `type-channel.test-d.ts` holds what the compiler must refuse |
|
|
194
|
+
| constant data (3) | it is a table, not a behaviour; every reader of it asserts on it (`preset.test.ts` for the artefact paths, `plugin.test.ts` for the manifest) |
|
|
195
|
+
| the facet file set, as a re-export (12) | the six node facets share one builder, so these name it rather than implement it — `builder.test.ts` and `facet-matrix.test.ts` hold the thing they name |
|
|
196
|
+
| a facet's vitest project (7) | `projects.test.ts` builds every one of them and asserts the project it produces |
|
|
197
|
+
| a mobile module that needs a device (2) | the mobile facet has no tree under `specs/` and cannot have one here (M1); what a device is NOT needed for — resolving the simulator, projecting the page source, wording the ambiguity — has its own sibling test, and these two are the rest |
|
|
198
|
+
| a facet module (8) | the facet is proven end to end by its own tree under `specs/<facet>/`, which is what the constructor exists to make possible |
|
|
199
|
+
| a result accessor (7) | an accessor is what a terminal action hands back: it is exercised by every spec that asserts on a result, and `result.test.ts` holds the base |
|
|
200
|
+
| a seam adapter (13) | a seam is proven through the facet that drives it — a probe that could only reach it directly is an integration spec under `specs/integration/<seam>/` (chapter 03) |
|
|
201
|
+
| a bundled CLI entry (3) | it is argument parsing over a module that has its own tests, and `specs/lint/` runs the built binary end to end |
|
|
202
|
+
| a lint-layer module (4) | the lint layer is proven by the rule tests beside each rule and by `specs/lint/`, which runs the real oxlint binary and the real checker over fixture projects |
|
|
203
|
+
| a chain or runner internal (7) | it is reached through the chain, so its proof is the facet specs that drive the chain — a direct test would assert on a seam nothing else speaks to |
|
|
204
|
+
|
|
205
|
+
<!-- /GENERATED:siblings -->
|
|
206
|
+
|
|
207
|
+
A new module that matches no kind fails the meta-test, which is the moment to
|
|
208
|
+
write the test — not six months later, when a coverage number moved.
|
|
209
|
+
|
|
210
|
+
## The capability matrix
|
|
211
|
+
|
|
212
|
+
The catalogue in [19 — Linting](19-linting.md) answers "which conventions are
|
|
213
|
+
mechanized". This answers the other half: for each thing the framework can DO,
|
|
214
|
+
which facets declare it, and how many of the package's own test files exercise
|
|
215
|
+
it there.
|
|
216
|
+
|
|
217
|
+
It is generated by `npm run docs` from the facet declaration
|
|
218
|
+
(`src/lint/facet-matrix.ts`), the `{{token}}` list the runner and the checker
|
|
219
|
+
already share, and a scan of the trees each column owns. The same table is the
|
|
220
|
+
skill's `references/matrix.md`.
|
|
221
|
+
|
|
222
|
+
<!-- GENERATED:matrix — do not edit by hand; run `npm run docs`. Source: src/lint/matrix.ts -->
|
|
223
|
+
|
|
224
|
+
What the framework can do, and how many of this package’s own test FILES carry the literal that exercises it, per facet (comments stripped, so a sentence about a capability never counts as a test of it). A blank (`—`) is a capability the facet does not declare; a `0` is a declared capability nothing here exercises, and every one of them is named under **Exemptions** with the reason it is accepted. `matrix.test.ts` fails on a `0` that is not.
|
|
225
|
+
|
|
226
|
+
### Constructor option
|
|
227
|
+
|
|
228
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
229
|
+
| ----------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
230
|
+
| `services` | 2 | 1 | 1 | 2 | 1 | 0 | — | — |
|
|
231
|
+
| `root` | 2 | 1 | 0 | 2 | 0 | 0 | 0 | — |
|
|
232
|
+
| `server` | 4 | — | — | — | 3 | — | — | — |
|
|
233
|
+
| `jobs` | — | 2 | — | — | — | — | — | — |
|
|
234
|
+
| `url` | — | — | — | — | 0 | — | — | — |
|
|
235
|
+
| `backend` | — | — | — | — | 0 | 0 | — | — |
|
|
236
|
+
| `external` | — | — | — | — | 0 | — | — | — |
|
|
237
|
+
| `device` | — | — | — | — | — | 0 | — | — |
|
|
238
|
+
| `app` | — | — | — | — | — | 0 | — | — |
|
|
239
|
+
| `timeouts` | — | — | — | — | — | 0 | — | — |
|
|
240
|
+
| `defaults` | — | — | 1 | — | — | — | — | — |
|
|
241
|
+
| `env` | — | — | 4 | — | — | — | — | — |
|
|
242
|
+
| `docker` | — | — | 1 | — | — | — | — | — |
|
|
243
|
+
| `serve` | — | — | 4 | — | — | — | — | — |
|
|
244
|
+
| `transform` | — | — | 2 | — | 1 | — | — | — |
|
|
245
|
+
| `wrap` | — | — | — | — | — | — | 1 | — |
|
|
246
|
+
| `vite` | — | — | — | — | — | — | 1 | — |
|
|
247
|
+
| `clock` | — | — | — | — | — | — | 0 | — |
|
|
248
|
+
| `locale` | — | — | — | — | — | — | 0 | — |
|
|
249
|
+
| `timezone` | — | — | — | — | — | — | 0 | — |
|
|
250
|
+
| `viewport` | — | — | — | — | — | — | 0 | — |
|
|
251
|
+
|
|
252
|
+
### Setup
|
|
253
|
+
|
|
254
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
255
|
+
| -------------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
256
|
+
| `.clock()` | 1 | 0 | — | 1 | 1 | — | 1 | — |
|
|
257
|
+
| `.intercept()` | 3 | 1 | — | 1 | 0 | 0 | 4 | — |
|
|
258
|
+
| `.seed()` | 4 | 1 | 1 | 2 | — | — | — | — |
|
|
259
|
+
| `.headers()` | 1 | — | — | — | 1 | — | — | — |
|
|
260
|
+
| `.fixture()` | — | — | 6 | — | — | — | — | — |
|
|
261
|
+
| `.env()` | — | — | 1 | — | — | — | — | — |
|
|
262
|
+
| `.wrap()` | — | — | — | — | — | — | 2 | — |
|
|
263
|
+
| `.viewport()` | — | — | — | — | — | — | 1 | — |
|
|
264
|
+
|
|
265
|
+
### Terminal action
|
|
266
|
+
|
|
267
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
268
|
+
| ------------ | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
269
|
+
| `.get()` | 9 | — | — | — | — | — | — | — |
|
|
270
|
+
| `.post()` | 1 | — | — | — | — | — | — | — |
|
|
271
|
+
| `.put()` | 1 | — | — | — | — | — | — | — |
|
|
272
|
+
| `.delete()` | 1 | — | — | — | — | — | — | — |
|
|
273
|
+
| `.request()` | 1 | — | — | — | — | — | — | — |
|
|
274
|
+
| `.trigger()` | — | 1 | — | — | — | — | — | — |
|
|
275
|
+
| `.call()` | — | — | — | 6 | — | — | — | — |
|
|
276
|
+
| `.exec()` | — | — | 7 | — | — | — | — | — |
|
|
277
|
+
| `.run()` | — | — | 2 | — | — | — | — | — |
|
|
278
|
+
| `.visit()` | — | — | — | — | 10 | — | — | — |
|
|
279
|
+
| `.fetch()` | — | — | — | — | 2 | — | — | — |
|
|
280
|
+
| `.open()` | — | — | — | — | — | 0 | — | — |
|
|
281
|
+
| `.render()` | — | — | — | — | — | — | 14 | — |
|
|
282
|
+
|
|
283
|
+
### Verb
|
|
284
|
+
|
|
285
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
286
|
+
| ---------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
287
|
+
| `see` | — | — | — | — | 5 | 0 | 14 | — |
|
|
288
|
+
| `fill` | — | — | — | — | 3 | 0 | 2 | — |
|
|
289
|
+
| `click` | — | — | — | — | 3 | — | 4 | — |
|
|
290
|
+
| `gone` | — | — | — | — | 2 | — | 4 | — |
|
|
291
|
+
| `press` | — | — | — | — | 0 | — | 2 | — |
|
|
292
|
+
| `hover` | — | — | — | — | 0 | — | 1 | — |
|
|
293
|
+
| `check` | — | — | — | — | 0 | — | 2 | — |
|
|
294
|
+
| `select` | — | — | — | — | 1 | — | 2 | — |
|
|
295
|
+
| `goto` | — | — | — | — | 0 | — | — | — |
|
|
296
|
+
| `tap` | — | — | — | — | — | 0 | — | — |
|
|
297
|
+
| `rerender` | — | — | — | — | — | — | 1 | — |
|
|
298
|
+
| `unmount` | — | — | — | — | — | — | 1 | — |
|
|
299
|
+
|
|
300
|
+
### Descriptor
|
|
301
|
+
|
|
302
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
303
|
+
| --------------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
304
|
+
| `button` | — | — | — | — | 3 | 0 | 4 | — |
|
|
305
|
+
| `field` | — | — | — | — | 3 | 0 | 3 | — |
|
|
306
|
+
| `content` | — | — | — | — | 3 | 0 | 10 | — |
|
|
307
|
+
| `testId` | — | — | — | — | 0 | 0 | 1 | — |
|
|
308
|
+
| `heading` | — | — | — | — | 0 | — | 1 | — |
|
|
309
|
+
| `link` | — | — | — | — | 2 | — | 2 | — |
|
|
310
|
+
| `dialog` | — | — | — | — | 0 | — | 1 | — |
|
|
311
|
+
| `status` | — | — | — | — | 0 | — | 2 | — |
|
|
312
|
+
| `table` | — | — | — | — | 0 | — | 1 | — |
|
|
313
|
+
| `row` | — | — | — | — | 0 | — | 1 | — |
|
|
314
|
+
| `listitem` | — | — | — | — | 0 | — | 1 | — |
|
|
315
|
+
| `option` | — | — | — | — | 1 | — | 1 | — |
|
|
316
|
+
| `banner` | — | — | — | — | 0 | — | 0 | — |
|
|
317
|
+
| `complementary` | — | — | — | — | 0 | — | 0 | — |
|
|
318
|
+
| `contentinfo` | — | — | — | — | 1 | — | 0 | — |
|
|
319
|
+
| `form` | — | — | — | — | 0 | — | 0 | — |
|
|
320
|
+
| `main` | — | — | — | — | 1 | — | 1 | — |
|
|
321
|
+
| `navigation` | — | — | — | — | 1 | — | 2 | — |
|
|
322
|
+
| `region` | — | — | — | — | 1 | — | 1 | — |
|
|
323
|
+
| `search` | — | — | — | — | 0 | — | 0 | — |
|
|
324
|
+
| `within` | — | — | — | — | 2 | — | 5 | — |
|
|
325
|
+
| `focused` | — | — | — | — | 1 | — | 1 | — |
|
|
326
|
+
| `selected` | — | — | — | — | 1 | — | 1 | — |
|
|
327
|
+
| `valued` | — | — | — | — | 1 | — | 2 | — |
|
|
328
|
+
| `disabled` | — | — | — | — | 1 | — | 1 | — |
|
|
329
|
+
| `enabled` | — | — | — | — | 1 | — | 1 | — |
|
|
330
|
+
|
|
331
|
+
### Result accessor
|
|
332
|
+
|
|
333
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
334
|
+
| --------------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
335
|
+
| `.response` | 7 | — | — | — | — | — | — | — |
|
|
336
|
+
| `.status` | 3 | — | — | — | 2 | — | — | — |
|
|
337
|
+
| `.table()` | 4 | 1 | 1 | 1 | — | — | — | — |
|
|
338
|
+
| `.file()` | 0 | — | 5 | 0 | — | — | — | — |
|
|
339
|
+
| `.directory()` | 0 | — | 2 | 0 | — | — | — | — |
|
|
340
|
+
| `.stdout` | — | — | 5 | — | — | — | — | — |
|
|
341
|
+
| `.stderr` | — | — | 2 | — | — | — | — | — |
|
|
342
|
+
| `.exitCode` | — | — | 8 | — | — | — | — | — |
|
|
343
|
+
| `.filesystem` | — | — | 1 | — | — | — | — | — |
|
|
344
|
+
| `.container()` | — | — | 1 | — | — | — | — | — |
|
|
345
|
+
| `.containerIds` | — | — | 1 | — | — | — | — | — |
|
|
346
|
+
| `.value` | — | — | — | 6 | — | — | — | — |
|
|
347
|
+
| `.error` | — | — | — | 5 | — | — | — | — |
|
|
348
|
+
| `.tree` | — | — | — | — | 2 | — | 3 | — |
|
|
349
|
+
| `.content` | — | — | — | — | 4 | 0 | 11 | — |
|
|
350
|
+
| `.console` | — | — | — | — | 1 | — | 3 | — |
|
|
351
|
+
| `.errors` | — | — | — | — | 4 | — | 5 | — |
|
|
352
|
+
| `.html` | — | — | — | — | 0 | — | 5 | — |
|
|
353
|
+
| `.title` | — | — | — | — | 0 | — | — | — |
|
|
354
|
+
| `.url` | — | — | — | — | 2 | — | — | — |
|
|
355
|
+
| `.head` | — | — | — | — | 3 | — | — | — |
|
|
356
|
+
| `.jsonLd` | — | — | — | — | 1 | — | — | — |
|
|
357
|
+
| `.canonical` | — | — | — | — | 1 | — | — | — |
|
|
358
|
+
| `.alternates` | — | — | — | — | 1 | — | — | — |
|
|
359
|
+
| `.links` | — | — | — | — | 0 | — | — | — |
|
|
360
|
+
| `.meta()` | — | — | — | — | 2 | — | — | — |
|
|
361
|
+
| `.body` | — | — | — | — | 1 | — | — | — |
|
|
362
|
+
| `.json` | — | — | 2 | — | 1 | — | — | — |
|
|
363
|
+
| `.headers` | — | — | — | — | 1 | — | — | — |
|
|
364
|
+
| `.location` | — | — | — | — | 1 | — | — | — |
|
|
365
|
+
| `.screen` | — | — | — | — | — | 0 | — | — |
|
|
366
|
+
|
|
367
|
+
### Golden
|
|
368
|
+
|
|
369
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
370
|
+
| ------------------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
371
|
+
| `toMatch('<name>')` | 3 | 0 | 6 | 5 | 5 | — | 1 | 6 |
|
|
372
|
+
| `toMatchRows()` | 4 | 1 | 1 | 1 | — | — | 0 | 4 |
|
|
373
|
+
| `.http exchange` | 3 | — | — | — | — | — | — | 7 |
|
|
374
|
+
| `.aria.yaml tree` | — | — | — | — | 1 | — | 1 | — |
|
|
375
|
+
| `.json body` | 0 | — | 2 | 5 | 2 | — | — | 10 |
|
|
376
|
+
| `.txt stream` | 1 | — | 8 | 2 | 2 | — | — | 12 |
|
|
377
|
+
| `{ frozen }` | 2 | — | 5 | — | — | — | 1 | 3 |
|
|
378
|
+
|
|
379
|
+
### Time & doubles
|
|
380
|
+
|
|
381
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
382
|
+
| ------------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
383
|
+
| `clock()` | — | — | — | — | — | — | — | 4 |
|
|
384
|
+
| `intercept()` | — | — | — | — | — | — | — | 1 |
|
|
385
|
+
| `mockOf()` | — | — | — | — | — | — | — | 6 |
|
|
386
|
+
| `match.*` | — | — | — | — | — | — | — | 22 |
|
|
387
|
+
|
|
388
|
+
### Token
|
|
389
|
+
|
|
390
|
+
| Capability | api | jobs | cli | integration | website | mobile | component | module |
|
|
391
|
+
| -------------- | --- | ---- | --- | ----------- | ------- | ------ | --------- | ------ |
|
|
392
|
+
| `{{any}}` | 0 | — | 2 | 0 | 0 | — | — | 1 |
|
|
393
|
+
| `{{base64}}` | 0 | — | 0 | 0 | 0 | — | — | 2 |
|
|
394
|
+
| `{{date}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
395
|
+
| `{{duration}}` | 0 | — | 1 | 0 | 0 | — | — | 2 |
|
|
396
|
+
| `{{email}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
397
|
+
| `{{float}}` | 0 | — | 0 | 0 | 0 | — | — | 2 |
|
|
398
|
+
| `{{hex}}` | 0 | — | 0 | 0 | 0 | — | — | 2 |
|
|
399
|
+
| `{{int}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
400
|
+
| `{{ip}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
401
|
+
| `{{iso8601}}` | 0 | — | 0 | 0 | 0 | — | — | 2 |
|
|
402
|
+
| `{{number}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
403
|
+
| `{{path}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
404
|
+
| `{{port}}` | 0 | — | 0 | 0 | 0 | — | — | 2 |
|
|
405
|
+
| `{{semver}}` | 0 | — | 2 | 0 | 0 | — | — | 1 |
|
|
406
|
+
| `{{sha}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
407
|
+
| `{{string}}` | 0 | — | 1 | 0 | 0 | — | — | 1 |
|
|
408
|
+
| `{{time}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
409
|
+
| `{{ulid}}` | 0 | — | 0 | 0 | 0 | — | — | 1 |
|
|
410
|
+
| `{{url}}` | 0 | — | 2 | 0 | 0 | — | — | 1 |
|
|
411
|
+
| `{{uuid}}` | 0 | — | 0 | 0 | 0 | — | — | 3 |
|
|
412
|
+
| `{{workdir}}` | 0 | — | 5 | 0 | 0 | — | — | 1 |
|
|
413
|
+
|
|
414
|
+
### Exemptions
|
|
415
|
+
|
|
416
|
+
- no mobile tree: an iOS simulator is not something CI provisions (M1) — `services`·mobile, `root`·mobile, `backend`·mobile, `device`·mobile, `app`·mobile, `timeouts`·mobile, `.intercept()`·mobile, `.open()`·mobile, `see`·mobile, `fill`·mobile, `tap`·mobile, `button`·mobile, `field`·mobile, `content`·mobile, `testId`·mobile, `.content`·mobile, `.screen`·mobile
|
|
417
|
+
- the surface ships and this package does not specify it here yet — `root`·cli, `root`·website, `root`·component, `backend`·website, `external`·website, `clock`·component, `locale`·component, `timezone`·component, `viewport`·component, `.clock()`·jobs, `.intercept()`·website, `press`·website, `hover`·website, `check`·website, `goto`·website, `testId`·website, `heading`·website, `dialog`·website, `status`·website, `table`·website, `row`·website, `listitem`·website, `banner`·website, `banner`·component, `complementary`·website, `complementary`·component, `contentinfo`·component, `form`·website, `form`·component, `search`·website, `search`·component, `.file()`·api, `.file()`·integration, `.directory()`·api, `.directory()`·integration, `.html`·website, `.title`·website, `.links`·website, `toMatchRows()`·component, `.json body`·api
|
|
418
|
+
- the package serves its own fixture site, so `server` is what it proves; `url` targets an already-running deployment, which is a consumer shape (A11 states the XOR) — `url`·website
|
|
419
|
+
- a job's oracle is the table it wrote (`toMatchRows`): its result carries no file and no directory — `toMatch('<name>')`·jobs
|
|
420
|
+
- the engine owns the family (proven in the module column); a facet uses the ones its goldens need — `{{any}}`·api, `{{any}}`·integration, `{{any}}`·website, `{{base64}}`·api, `{{base64}}`·cli, `{{base64}}`·integration, `{{base64}}`·website, `{{date}}`·api, `{{date}}`·cli, `{{date}}`·integration, `{{date}}`·website, `{{duration}}`·api, `{{duration}}`·integration, `{{duration}}`·website, `{{email}}`·api, `{{email}}`·cli, `{{email}}`·integration, `{{email}}`·website, `{{float}}`·api, `{{float}}`·cli, `{{float}}`·integration, `{{float}}`·website, `{{hex}}`·api, `{{hex}}`·cli, `{{hex}}`·integration, `{{hex}}`·website, `{{int}}`·api, `{{int}}`·cli, `{{int}}`·integration, `{{int}}`·website, `{{ip}}`·api, `{{ip}}`·cli, `{{ip}}`·integration, `{{ip}}`·website, `{{iso8601}}`·api, `{{iso8601}}`·cli, `{{iso8601}}`·integration, `{{iso8601}}`·website, `{{number}}`·api, `{{number}}`·cli, `{{number}}`·integration, `{{number}}`·website, `{{path}}`·api, `{{path}}`·cli, `{{path}}`·integration, `{{path}}`·website, `{{port}}`·api, `{{port}}`·cli, `{{port}}`·integration, `{{port}}`·website, `{{semver}}`·api, `{{semver}}`·integration, `{{semver}}`·website, `{{sha}}`·api, `{{sha}}`·cli, `{{sha}}`·integration, `{{sha}}`·website, `{{string}}`·api, `{{string}}`·integration, `{{string}}`·website, `{{time}}`·api, `{{time}}`·cli, `{{time}}`·integration, `{{time}}`·website, `{{ulid}}`·api, `{{ulid}}`·cli, `{{ulid}}`·integration, `{{ulid}}`·website, `{{url}}`·api, `{{url}}`·integration, `{{url}}`·website, `{{uuid}}`·api, `{{uuid}}`·cli, `{{uuid}}`·integration, `{{uuid}}`·website, `{{workdir}}`·api, `{{workdir}}`·integration, `{{workdir}}`·website
|
|
421
|
+
|
|
422
|
+
<!-- /GENERATED:matrix -->
|
|
423
|
+
|
|
424
|
+
## The meta-test channel
|
|
425
|
+
|
|
426
|
+
Several truths about this package cannot be asserted from outside it, so they are asserted by running it on itself. Each of these exists because a defect class was found once and made unrepeatable (rule K1).
|
|
427
|
+
|
|
428
|
+
The table is GENERATED from the files, because the channel grows faster than a hand-kept list: what a row holds is the file's own `describe` titles, so a meta-test that lands is a row the next `npm run docs` writes.
|
|
429
|
+
|
|
430
|
+
<!-- GENERATED:meta-tests — do not edit by hand; run `npm run docs`. Source: src/lint/meta-tests.ts -->
|
|
431
|
+
|
|
432
|
+
**19 files** are the channel — every `*.test.ts` directly under `src/lint/`, plus the suite that needs the real binary. What each one holds is its own `describe` titles, read from the file:
|
|
433
|
+
|
|
434
|
+
| Meta-test | Holds | Tests |
|
|
435
|
+
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
436
|
+
| `src/lint/ast.test.ts` | specsAnchor | 7 |
|
|
437
|
+
| `src/lint/cards.test.ts` | signature cards — generation freshness (meta-test) · signature cards — completeness against the facet declaration (meta-test) · the skill routes to the cards (meta-test) · signature cards — what a card says | 15 |
|
|
438
|
+
| `src/lint/checker-crossfile.test.ts` | checker suppression — suppressedLines (checker-disable comments) | 9 |
|
|
439
|
+
| `src/lint/checker-member.test.ts` | the member pass — what a workspace member owes | 17 |
|
|
440
|
+
| `src/lint/checker-spec.test.ts` | fixSpecDocument — key order and block scalars | 5 |
|
|
441
|
+
| `src/lint/checker.test.ts` | conventions checker — findUnknownTokens (D4) · conventions checker — findKnownTokens (D10) · conventions checker — checkConventionFiles (D4) | 8 |
|
|
442
|
+
| `src/lint/coverage.test.ts` | the coverage ratchet | 8 |
|
|
443
|
+
| `src/lint/docs-typecheck.test.ts` | docs-typecheck — block selection · docs-typecheck — the published samples typecheck · docs-typecheck — the corpus does not await a sync matcher · docs-typecheck — harness | 6 |
|
|
444
|
+
| `src/lint/domains.test.ts` | the spec tree’s domain vocabulary | 4 |
|
|
445
|
+
| `src/lint/env-allowlist.test.ts` | framework env reads — e1-env-allowlist (meta-test) | 4 |
|
|
446
|
+
| `src/lint/facet-matrix.test.ts` | facet capability declaration (K1 guard) | 6 |
|
|
447
|
+
| `src/lint/matrix.test.ts` | capability matrix (meta-test K6) | 5 |
|
|
448
|
+
| `src/lint/meta-tests.test.ts` | the meta-test channel, read off its own files | 3 |
|
|
449
|
+
| `src/lint/oxlint-pin.test.ts` | the oxlint pin — the toolchain owns it and this manifest mirrors it | 2 |
|
|
450
|
+
| `src/lint/package-exports.test.ts` | package-exports — F1 reads the published contract · package-exports — the root has two runtimes and one type surface · package-exports — the tarball carries what a message points at | 8 |
|
|
451
|
+
| `src/lint/plugin.test.ts` | the composable fragment — what a consumer wires · the upstream options this vocabulary owns (ADR-005) · conventions catalogue — generation freshness (meta-test) · conventions catalogue — completeness (meta-test) · testing fragment — standalone oxlint config · conventions catalogue — the channels answer for themselves (meta-test) · conventions catalogue — E2E inventory (meta-test) · the corpus’ own coordinates (meta-test K8) · the corpus never offers an option the rulebook refuses | 34 |
|
|
452
|
+
| `src/lint/role.test.ts` | roleOf — the kind a path states · isTestRole — the three kinds that DECLARE tests · isTestFile — what the test conventions reach | 14 |
|
|
453
|
+
| `src/lint/siblings.test.ts` | modules with no sibling test (meta-test K7) | 4 |
|
|
454
|
+
| `specs/lint/meta/k4-reach-per-path.test.ts` | lint — k4-reach-per-path (meta-test) | 4 |
|
|
455
|
+
|
|
456
|
+
<!-- /GENERATED:meta-tests -->
|
|
457
|
+
|
|
458
|
+
One proof of the channel declares no test and appears in no row: `src/type-channel.test-d.ts` states what the COMPILER refuses — A8, A11, B1, B3, D1, M2, W6 — as an `@ts-expect-error` per row, and `typescript check` fails the day the line it marks starts compiling. It runs under the typechecker the whole repository already runs, never under a vitest project.
|
|
459
|
+
|
|
460
|
+
`src/lint/plugin.test.ts` also holds the seven-channel contract: every row of
|
|
461
|
+
every channel carries the proof that channel can give — a rule file and a
|
|
462
|
+
fixture pair, an assertion on a resolved upstream option, a bundled checker
|
|
463
|
+
pass, a `// RUNTIME <ID>` marker above the spec that drives the refusal, a line
|
|
464
|
+
of the type-channel file, a named meta-test. A row that loses its proof fails
|
|
465
|
+
that test rather than going quietly stale.
|
|
466
|
+
|
|
467
|
+
The freshness meta-test is the reason a documentation change can turn the suite red: edit the generated catalogue by hand and it fails, correctly. Regenerate instead — the gesture is [02 — Developing](02-developing.md)'s.
|
|
468
|
+
|
|
469
|
+
## Goldens and update mode
|
|
470
|
+
|
|
471
|
+
A fixture this repository compares against is regenerated, never hand-tuned, and the gesture here is the `unit` project:
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
TEST_UPDATE=1 npx vitest --run --project unit # or: npx vitest --run -u
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
What update mode writes, what it preserves and the discipline it asks for are [15 — Tokens § Update mode](15-tokens.md#update-mode-tokens-are-preserved)'s, whole.
|
|
478
|
+
|
|
479
|
+
## What CI runs
|
|
480
|
+
|
|
481
|
+
The workflow is `.github/workflows/validate.yaml`, on every push to `main` and every pull request, delegating to the estate's shared `validate.yaml`. It restores `.artifacts/`, then runs `make build`, `make lint` and `make test` in that order, with chromium provisioned because the website specs drive a real browser. The same three targets are what a local run owes before a push; the ordering is not decorative, since lint loads what build produced.
|
|
482
|
+
|
|
483
|
+
**`make test` carries the floor.** It runs the suite WITH coverage and then `npm run coverage:check`, so the ratchet is judged on every push rather than trusted to whoever remembered to type `make check` — a gate CI never runs is a courtesy, not a gate. `make check` is still the whole verdict locally: build, lint, then that same target.
|
|
484
|
+
|
|
485
|
+
## Pitfalls
|
|
486
|
+
|
|
487
|
+
- **Running the lint specs on a stale `dist/`.** `specs/lint/**` and `oxlint.config.ts` both load `dist/oxlint.js`. Without `npm run build`, the suite judges the previous build's rules and reports a green that means nothing.
|
|
488
|
+
- **Hand-editing a golden under `specs/lint/checker/_expected/`.** Those are full-output snapshots of a real binary. Change the message in the code and regenerate with `TEST_UPDATE=1`; a hand-tuned golden asserts your typing, not the checker's output.
|
|
489
|
+
- **Expecting `npm test` to pass with Docker stopped.** Only `unit` and `cli` are infrastructure-free. The Docker-backed tests self-skip inside them, but `api`, `jobs` and `integration` fail honestly.
|
|
490
|
+
- **Adding a test at a facet root.** `specs/<facet>/<aspect>.spec.ts` is refused by `c1-domain-structure` in this repository's default depth — the runner lives at the root, the tests live one level down.
|
|
491
|
+
- **Writing a facet spec with the unit’s suffix.** `c12-spec-file-name` refuses it and `jterrazz-test-check specs --fix` renames it with `git mv` — and names every `include`/`exclude` entry of the member the rename broke — the globs whose prefix covers a file it moved and the literals naming one — since an entry the rename leaves behind collects nothing and the run stays green with fewer files. The reverse — a `.spec.ts` with no `specs/` ancestor — is the member pass's finding.
|
|
492
|
+
|
|
493
|
+
## Related
|
|
494
|
+
|
|
495
|
+
[01 — Architecture](01-architecture.md) · [02 — Developing](02-developing.md) · [14 — Assertions](14-assertions.md) · [18 — Conventions](18-conventions.md) · [19 — Linting](19-linting.md)
|