@jterrazz/test 14.3.0 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/ambiguity.js +1 -1
- package/dist/appium.adapter.js +1 -1
- package/dist/checker.js +14 -14
- package/dist/oxlint.cjs +90 -281
- package/dist/oxlint.d.cts +2 -2
- package/dist/oxlint.d.ts +2 -2
- package/dist/oxlint.js +90 -281
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ runs:
|
|
|
87
87
|
Error: no my-cli.yaml in the current directory
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
Same engine as the chain, same `{{token}}` grammar, same `TEST_UPDATE=1` — which rewrites each run's exit code and streams, and nothing else. A JSON Schema ships at `@jterrazz/test/schema` so an editor validates as you type. Full grammar: [docs/
|
|
90
|
+
Same engine as the chain, same `{{token}}` grammar, same `TEST_UPDATE=1` — which rewrites each run's exit code and streams, and nothing else. A JSON Schema ships at `@jterrazz/test/schema` so an editor validates as you type. Full grammar: [docs/07-cli.md](docs/07-cli.md#spec-documents--casespecyaml).
|
|
91
91
|
|
|
92
92
|
### Website testing (browser)
|
|
93
93
|
|
|
@@ -210,7 +210,7 @@ A `JobHandle` is `{ name: string; execute: () => Promise<void> }`.
|
|
|
210
210
|
|
|
211
211
|
### `specification.cli(bin, { root?, services?, docker?, transform?, env?, serve? })`
|
|
212
212
|
|
|
213
|
-
Runs a command binary against fixture projects in fresh temp directories. `env` (named environment sets) and `serve` (named servers) are the registries a [`<case>.spec.yaml`](docs/
|
|
213
|
+
Runs a command binary against fixture projects in fresh temp directories. `env` (named environment sets) and `serve` (named servers) are the registries a [`<case>.spec.yaml`](docs/07-cli.md#spec-documents--casespecyaml) names by word. With `services`, connection URLs are injected into the child env automatically: `<KEY>_URL` per record key (CONSTANT_CASE at camelCase boundaries — `analyticsDb` → `ANALYTICS_DB_URL`), plus `DATABASE_URL` (exactly one SQL database) and `REDIS_URL` (exactly one redis). `.env()` overrides; `null` unsets.
|
|
214
214
|
|
|
215
215
|
```typescript
|
|
216
216
|
export const { cli, cleanup } = await specification.cli('my-migrate-tool', {
|
|
@@ -239,7 +239,7 @@ const page = await website.visit('/', async (visitor) => {
|
|
|
239
239
|
});
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
-
The handle destructures to `{ website, cleanup, url }` — no `docker`, no `orchestrator`. `.visit()` needs playwright (`npm install -D playwright && npx playwright install chromium`) — an optional peer dependency, only loaded when a spec actually renders a page. Full reference: [docs/
|
|
242
|
+
The handle destructures to `{ website, cleanup, url }` — no `docker`, no `orchestrator`. `.visit()` needs playwright (`npm install -D playwright && npx playwright install chromium`) — an optional peer dependency, only loaded when a spec actually renders a page. Full reference: [docs/14-website.md](docs/14-website.md).
|
|
243
243
|
|
|
244
244
|
### `specification.mobile({ app, device, backend?, root? })`
|
|
245
245
|
|
|
@@ -258,7 +258,7 @@ const result = await mobile.open('news://events', async (visitor) => {
|
|
|
258
258
|
});
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
The handle destructures to `{ mobile, cleanup, udid }` (plus `backendUrl` with `backend: { port? }` — a declared stub backend whose URL the CALLER wires into its own bundler env; the framework never touches Metro). The element vocabulary is the website facet's, unchanged — `button`, `field`, `content`, `testId`, `within` — landmarks excepted (an iOS screen has no ARIA regions; they refuse at runtime). Requires the app installed on the simulator plus the optional peers: `npm install -D appium webdriverio && npx appium driver install xcuitest`. Full reference: [docs/
|
|
261
|
+
The handle destructures to `{ mobile, cleanup, udid }` (plus `backendUrl` with `backend: { port? }` — a declared stub backend whose URL the CALLER wires into its own bundler env; the framework never touches Metro). The element vocabulary is the website facet's, unchanged — `button`, `field`, `content`, `testId`, `within` — landmarks excepted (an iOS screen has no ARIA regions; they refuse at runtime). Requires the app installed on the simulator plus the optional peers: `npm install -D appium webdriverio && npx appium driver install xcuitest`. Full reference: [docs/15-mobile.md](docs/15-mobile.md).
|
|
262
262
|
|
|
263
263
|
### Root auto-discovery
|
|
264
264
|
|
|
@@ -284,7 +284,7 @@ export default defineSpecConfig({
|
|
|
284
284
|
| `test.testTimeout` / `hookTimeout` | `30_000` |
|
|
285
285
|
| `test.exclude` | vitest's defaults **+** `**/_fixtures/**` |
|
|
286
286
|
|
|
287
|
-
Nothing else: `fileParallelism`, `reporters`, `environment` and every `include` stay yours. Inline `projects` inherit the same defaults (vitest gives a project nothing from the root), arrays are concatenated rather than replaced, and scalars you state win. Full walkthrough, including migrating a hand-rolled config: [docs/
|
|
287
|
+
Nothing else: `fileParallelism`, `reporters`, `environment` and every `include` stay yours. Inline `projects` inherit the same defaults (vitest gives a project nothing from the root), arrays are concatenated rather than replaced, and scalars you state win. Full walkthrough, including migrating a hand-rolled config: [docs/02-developing.md](docs/02-developing.md#vitest-config-the-preset).
|
|
288
288
|
|
|
289
289
|
Every artefact a run produces lands under `.artifacts/<tool>/` — one `.gitignore` line, one `rm -rf`. Per-run scratch (a CLI spec's temp cwd, a browser profile) stays in the OS temp dir.
|
|
290
290
|
|
|
@@ -378,7 +378,7 @@ Location: /orders/{{uuid#order}}
|
|
|
378
378
|
}
|
|
379
379
|
```
|
|
380
380
|
|
|
381
|
-
See [docs/
|
|
381
|
+
See [docs/09-tokens.md](docs/09-tokens.md) for the canonical accepted form of every token.
|
|
382
382
|
|
|
383
383
|
## Contracts
|
|
384
384
|
|
|
@@ -413,7 +413,7 @@ export const withRatesDown = () =>
|
|
|
413
413
|
const result = await jobs.intercept(pipeline).trigger('nightly-report');
|
|
414
414
|
```
|
|
415
415
|
|
|
416
|
-
Selection is first-match, one queue for every facet: `times` bounds how often a contract serves (omitted = unlimited, so retries and re-renders replay it), `required: true` fails the chain if it was never requested. Provider string filters are **exact** — the loose forms are explicit (`RegExp`, `match.includes('…')`). Failure simulation: `openai.error(429)`, `anthropic.timeout()`, `openai.malformed('not json')`. MSW ships as a direct dependency — no separate install. Full chapter: [docs/
|
|
416
|
+
Selection is first-match, one queue for every facet: `times` bounds how often a contract serves (omitted = unlimited, so retries and re-renders replay it), `required: true` fails the chain if it was never requested. Provider string filters are **exact** — the loose forms are explicit (`RegExp`, `match.includes('…')`). Failure simulation: `openai.error(429)`, `anthropic.timeout()`, `openai.malformed('not json')`. MSW ships as a direct dependency — no separate install. Full chapter: [docs/10-contracts.md](docs/10-contracts.md).
|
|
417
417
|
|
|
418
418
|
## Docker-aware CLIs
|
|
419
419
|
|
|
@@ -442,7 +442,7 @@ test('deploy spawns a labelled container', async () => {
|
|
|
442
442
|
|
|
443
443
|
`docker/compose.test.yaml` is the single source of truth for test infrastructure; `docker/<service>/init.sql` runs when the corresponding service starts. Parallel isolation is automatic per vitest worker: postgres clones a schema, redis assigns a database index, sqlite copies the template file, compose mode gets a dedicated project.
|
|
444
444
|
|
|
445
|
-
`sqlite()` caches its schema template inside the project — `.artifacts/vitest/sqlite/template-<key>.sqlite` — so two checkouts never share one, and workers racing for a cold cache wait for the one that is building rather than all building at once. Details: [docs/
|
|
445
|
+
`sqlite()` caches its schema template inside the project — `.artifacts/vitest/sqlite/template-<key>.sqlite` — so two checkouts never share one, and workers racing for a cold cache wait for the one that is building rather than all building at once. Details: [docs/11-services.md](docs/11-services.md#where-the-template-lives).
|
|
446
446
|
|
|
447
447
|
## Mocking utilities
|
|
448
448
|
|
|
@@ -457,7 +457,7 @@ import { mockOf, mockOfDate } from '@jterrazz/test';
|
|
|
457
457
|
|
|
458
458
|
## Conventions
|
|
459
459
|
|
|
460
|
-
Normative rules live in the constitution ([docs/
|
|
460
|
+
Normative rules live in the constitution ([docs/12-conventions.md](docs/12-conventions.md)); the generated per-rule catalogue is [docs/13-linting.md](docs/13-linting.md). A facet (`specs/<facet>/`) carries its runner(s) at its root and holds domain folders; the folder follows the assets:
|
|
461
461
|
|
|
462
462
|
```
|
|
463
463
|
specs/<facet>/ # api | jobs | cli | integrations | lint
|
|
@@ -477,7 +477,7 @@ Every test contains `// Given -` and `// Then -` comments (always both; `// When
|
|
|
477
477
|
|
|
478
478
|
### Convention enforcement — the shipped lint plugin
|
|
479
479
|
|
|
480
|
-
These conventions are not just prose: the package ships an oxlint plugin (`@jterrazz/test/oxlint`) with ~40 AST rules, plus a `jterrazz-test-check` binary (the conventions checker) that reads the data fixtures and cross-file relationships oxlint cannot. Wire the plugin into your `oxlint.config.ts` and run `jterrazz-test-check specs` in CI — the full four-channel catalogue (each rule, its channel and rationale) is generated into [docs/
|
|
480
|
+
These conventions are not just prose: the package ships an oxlint plugin (`@jterrazz/test/oxlint`) with ~40 AST rules, plus a `jterrazz-test-check` binary (the conventions checker) that reads the data fixtures and cross-file relationships oxlint cannot. Wire the plugin into your `oxlint.config.ts` and run `jterrazz-test-check specs` in CI — the full four-channel catalogue (each rule, its channel and rationale) is generated into [docs/13-linting.md](docs/13-linting.md).
|
|
481
481
|
|
|
482
482
|
## Requirements
|
|
483
483
|
|
package/dist/ambiguity.js
CHANGED
|
@@ -119,7 +119,7 @@ function describeAmbiguity(options) {
|
|
|
119
119
|
"Disambiguate with one of:",
|
|
120
120
|
...fixes,
|
|
121
121
|
"",
|
|
122
|
-
"Docs: docs/
|
|
122
|
+
"Docs: docs/14-website.md#designating-exactly-one-element (CONVENTIONS W3)"
|
|
123
123
|
].join("\n");
|
|
124
124
|
}
|
|
125
125
|
//#endregion
|
package/dist/appium.adapter.js
CHANGED
|
@@ -71,7 +71,7 @@ function describeMobileAmbiguity(options) {
|
|
|
71
71
|
"Disambiguate with one of:",
|
|
72
72
|
...fixes,
|
|
73
73
|
"",
|
|
74
|
-
"Docs: docs/
|
|
74
|
+
"Docs: docs/15-mobile.md#designating-exactly-one-element (CONVENTIONS W3)"
|
|
75
75
|
].join("\n");
|
|
76
76
|
}
|
|
77
77
|
//#endregion
|
package/dist/checker.js
CHANGED
|
@@ -415,7 +415,7 @@ function checkDockerRunnerAwaitUsing(rootDir) {
|
|
|
415
415
|
violations.push({
|
|
416
416
|
file: rel,
|
|
417
417
|
line,
|
|
418
|
-
message: `${rel}:${line}: result of docker-aware runner "${runner}" must be bound with \`await using\` so its containers are disposed (B5 — see docs/
|
|
418
|
+
message: `${rel}:${line}: result of docker-aware runner "${runner}" must be bound with \`await using\` so its containers are disposed (B5 — see docs/13-linting.md)`,
|
|
419
419
|
severity: "error"
|
|
420
420
|
});
|
|
421
421
|
}
|
|
@@ -471,13 +471,13 @@ function checkDatabaseProperty(rootDir) {
|
|
|
471
471
|
if (count >= 2 && !hasDatabase) violations.push({
|
|
472
472
|
file: rel,
|
|
473
473
|
line,
|
|
474
|
-
message: `${rel}:${line}: .${verb}() must pass { database } — ${count} SQL databases are declared (A7 — see docs/
|
|
474
|
+
message: `${rel}:${line}: .${verb}() must pass { database } — ${count} SQL databases are declared (A7 — see docs/13-linting.md)`,
|
|
475
475
|
severity: "error"
|
|
476
476
|
});
|
|
477
477
|
else if (count === 1 && hasDatabase) violations.push({
|
|
478
478
|
file: rel,
|
|
479
479
|
line,
|
|
480
|
-
message: `${rel}:${line}: .${verb}() must not pass { database } — a single SQL database is declared (A7 — see docs/
|
|
480
|
+
message: `${rel}:${line}: .${verb}() must not pass { database } — a single SQL database is declared (A7 — see docs/13-linting.md)`,
|
|
481
481
|
severity: "error"
|
|
482
482
|
});
|
|
483
483
|
}
|
|
@@ -525,7 +525,7 @@ function checkDeadFixtures(rootDir) {
|
|
|
525
525
|
violations.push({
|
|
526
526
|
file: rel,
|
|
527
527
|
line: 1,
|
|
528
|
-
message: `${rel}: domain directory has ground subdirs (${groundSubdirs.join(", ")}) but no *.test.ts or *.spec.yaml (C9 — see docs/
|
|
528
|
+
message: `${rel}: domain directory has ground subdirs (${groundSubdirs.join(", ")}) but no *.test.ts or *.spec.yaml (C9 — see docs/13-linting.md)`,
|
|
529
529
|
severity: "error"
|
|
530
530
|
});
|
|
531
531
|
continue;
|
|
@@ -551,7 +551,7 @@ function checkDeadFixtures(rootDir) {
|
|
|
551
551
|
violations.push({
|
|
552
552
|
file: relEntry,
|
|
553
553
|
line: 1,
|
|
554
|
-
message: `${relEntry}: dead fixture — no test literal in ${feature} references ${sub}/${entry.name} (C9 — see docs/
|
|
554
|
+
message: `${relEntry}: dead fixture — no test literal in ${feature} references ${sub}/${entry.name} (C9 — see docs/13-linting.md)`,
|
|
555
555
|
severity
|
|
556
556
|
});
|
|
557
557
|
}
|
|
@@ -576,7 +576,7 @@ function checkPoolFixtures(rootDir) {
|
|
|
576
576
|
violations.push({
|
|
577
577
|
file: relEntry,
|
|
578
578
|
line: 1,
|
|
579
|
-
message: `${relEntry}: dead pool fixture — no spec under ${relative(rootDir, specsRoot)} references $FIXTURES/${entry.name} (C9 — see docs/
|
|
579
|
+
message: `${relEntry}: dead pool fixture — no spec under ${relative(rootDir, specsRoot)} references $FIXTURES/${entry.name} (C9 — see docs/13-linting.md)`,
|
|
580
580
|
severity: "error"
|
|
581
581
|
});
|
|
582
582
|
}
|
|
@@ -678,7 +678,7 @@ function checkPoolFixtureSharing(rootDir) {
|
|
|
678
678
|
return {
|
|
679
679
|
file: relEntry,
|
|
680
680
|
line: 1,
|
|
681
|
-
message: `${relEntry}: only ${relLeaf} reaches for this pool fixture — the pool is what SEVERAL leaves share; move it to ${relLeaf}/${GROUND_FIXTURES}/${entry} and reference it as '${entry}' (C14 — see docs/
|
|
681
|
+
message: `${relEntry}: only ${relLeaf} reaches for this pool fixture — the pool is what SEVERAL leaves share; move it to ${relLeaf}/${GROUND_FIXTURES}/${entry} and reference it as '${entry}' (C14 — see docs/13-linting.md, or run the checker with --fix)`,
|
|
682
682
|
severity: "error"
|
|
683
683
|
};
|
|
684
684
|
});
|
|
@@ -731,7 +731,7 @@ function checkLocalFixtureReach(rootDir) {
|
|
|
731
731
|
violations.push({
|
|
732
732
|
file: rel,
|
|
733
733
|
line,
|
|
734
|
-
message: `${rel}:${line}: fixture "${path}" reaches outside ${relative(rootDir, ground)} — a leaf's ground sits beside it; ground several leaves share goes to the $FIXTURES pool at ${relative(rootDir, pool)} (C15 — see docs/
|
|
734
|
+
message: `${rel}:${line}: fixture "${path}" reaches outside ${relative(rootDir, ground)} — a leaf's ground sits beside it; ground several leaves share goes to the $FIXTURES pool at ${relative(rootDir, pool)} (C15 — see docs/13-linting.md)`,
|
|
735
735
|
severity: "error"
|
|
736
736
|
});
|
|
737
737
|
}
|
|
@@ -1152,7 +1152,7 @@ function checkSpecConventions(text, rel, path) {
|
|
|
1152
1152
|
].map(({ line, message, rule, severity }) => ({
|
|
1153
1153
|
file: rel,
|
|
1154
1154
|
line,
|
|
1155
|
-
message: `${rel}:${line}: ${message} (${rule} — see docs/
|
|
1155
|
+
message: `${rel}:${line}: ${message} (${rule} — see docs/13-linting.md)`,
|
|
1156
1156
|
severity
|
|
1157
1157
|
}));
|
|
1158
1158
|
}
|
|
@@ -1183,7 +1183,7 @@ function checkSpecDescriptionsUnique(rootDir) {
|
|
|
1183
1183
|
violations.push({
|
|
1184
1184
|
file: rel,
|
|
1185
1185
|
line: document.descriptionLine,
|
|
1186
|
-
message: `${rel}:${document.descriptionLine}: "${document.description}" is already the description of ${first} — two documents of one directory cannot share a title (j4-spec-description-unique — see docs/
|
|
1186
|
+
message: `${rel}:${document.descriptionLine}: "${document.description}" is already the description of ${first} — two documents of one directory cannot share a title (j4-spec-description-unique — see docs/13-linting.md)`,
|
|
1187
1187
|
severity: "error"
|
|
1188
1188
|
});
|
|
1189
1189
|
}
|
|
@@ -1345,7 +1345,7 @@ function checkSpecFile(text, rel) {
|
|
|
1345
1345
|
return [{
|
|
1346
1346
|
file: rel,
|
|
1347
1347
|
line: error.line,
|
|
1348
|
-
message: `${error.message} (D4b — see docs/
|
|
1348
|
+
message: `${error.message} (D4b — see docs/13-linting.md)`,
|
|
1349
1349
|
severity: "error"
|
|
1350
1350
|
}];
|
|
1351
1351
|
}
|
|
@@ -1401,13 +1401,13 @@ function checkConventionFiles(rootDir) {
|
|
|
1401
1401
|
if (depth1 && !REQUEST_LINE.test(firstLine(text))) violations.push({
|
|
1402
1402
|
file: rel,
|
|
1403
1403
|
line: 1,
|
|
1404
|
-
message: `${rel}:1: a _requests/*.http file must start with a request line "METHOD /path" (D4b — see docs/
|
|
1404
|
+
message: `${rel}:1: a _requests/*.http file must start with a request line "METHOD /path" (D4b — see docs/13-linting.md)`,
|
|
1405
1405
|
severity: "error"
|
|
1406
1406
|
});
|
|
1407
1407
|
for (const { line, token } of findKnownTokens(text)) violations.push({
|
|
1408
1408
|
file: rel,
|
|
1409
1409
|
line,
|
|
1410
|
-
message: `${rel}:${line}: token ${token} in a _requests/ file — requests are inputs, never matched; tokens are not validated here (D10 — see docs/
|
|
1410
|
+
message: `${rel}:${line}: token ${token} in a _requests/ file — requests are inputs, never matched; tokens are not validated here (D10 — see docs/13-linting.md)`,
|
|
1411
1411
|
severity: "warn",
|
|
1412
1412
|
token
|
|
1413
1413
|
});
|
|
@@ -1418,7 +1418,7 @@ function checkConventionFiles(rootDir) {
|
|
|
1418
1418
|
if (depth1 && entry.name.endsWith(".http") && !STATUS_LINE.test(firstLine(text))) violations.push({
|
|
1419
1419
|
file: rel,
|
|
1420
1420
|
line: 1,
|
|
1421
|
-
message: `${rel}:1: an _expected/*.http file must start with a status line "HTTP/1.1 <status>" (D4b — see docs/
|
|
1421
|
+
message: `${rel}:1: an _expected/*.http file must start with a status line "HTTP/1.1 <status>" (D4b — see docs/13-linting.md)`,
|
|
1422
1422
|
severity: "error"
|
|
1423
1423
|
});
|
|
1424
1424
|
for (const { line, token } of findUnknownTokens(text)) violations.push({
|