@jterrazz/test 12.1.0 → 12.2.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 +18 -2
- package/dist/checker.js +73 -7
- package/dist/index.d.ts +179 -16
- package/dist/index.js +60 -1141
- package/dist/literate.js +1734 -0
- package/dist/match.js +193 -1
- package/dist/oxlint.cjs +96 -13
- package/dist/oxlint.js +96 -13
- package/dist/queue.js +3 -295
- package/dist/vitest.d.ts +41 -0
- package/dist/vitest.js +68 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -76,6 +76,21 @@ test('builds the project', async () => {
|
|
|
76
76
|
});
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
A CLI session can also BE the spec file. A literate `<case>.cli` states one scenario — the narrative in its header, the session in its blocks — and runs either as a test file of its own (the `literate()` vite plugin) or through `cli.run('case.cli')`:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
test: refuses to build without a manifest
|
|
83
|
+
given: an empty working directory
|
|
84
|
+
then: the error names the file it wanted
|
|
85
|
+
|
|
86
|
+
$ build
|
|
87
|
+
exit: 1
|
|
88
|
+
--- stderr
|
|
89
|
+
Error: no my-cli.yaml in the current directory
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Same engine as the chain, same `{{token}}` grammar, same `TEST_UPDATE=1` — which rewrites only what follows each `$`, never the header. Full grammar: [docs/04-cli.md](docs/04-cli.md#literate-specs--casecli).
|
|
93
|
+
|
|
79
94
|
### Website testing (browser)
|
|
80
95
|
|
|
81
96
|
```typescript
|
|
@@ -193,9 +208,9 @@ const result = await jobs.seed('pending.sql').trigger('nightly-report');
|
|
|
193
208
|
|
|
194
209
|
A `JobHandle` is `{ name: string; execute: () => Promise<void> }`.
|
|
195
210
|
|
|
196
|
-
### `specification.cli(bin, { root?, services?, docker?, transform? })`
|
|
211
|
+
### `specification.cli(bin, { root?, services?, docker?, transform?, env?, serve? })`
|
|
197
212
|
|
|
198
|
-
Runs a command binary against fixture projects in fresh temp directories. 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.
|
|
213
|
+
Runs a command binary against fixture projects in fresh temp directories. `env` (named environment sets) and `serve` (named servers) are the registries a [literate `<case>.cli`](docs/04-cli.md#literate-specs--casecli) 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.
|
|
199
214
|
|
|
200
215
|
```typescript
|
|
201
216
|
export const { cli, cleanup } = await specification.cli('my-migrate-tool', {
|
|
@@ -275,6 +290,7 @@ When `root` is absent, the framework walks up from the specification file to the
|
|
|
275
290
|
| `.exec("args")` | cli | `CliResult` | Run the command |
|
|
276
291
|
| `.exec(["build", "start"])` | cli | `CliResult` | Sequence in the same cwd; stops on first non-zero exit |
|
|
277
292
|
| `.exec("dev", { waitFor, timeout? })` | cli | `CliResult` | Long-running: resolves at the pattern, killed at `timeout` (default 10 s) |
|
|
293
|
+
| `.run("case.cli")` | cli | `CliResult` | Run a literate `<case>.cli` — its header and EVERY block asserted; the last one returns |
|
|
278
294
|
| `.fetch(path)` | website | `FetchResult` | One raw HTTP exchange — redirects surface as 3xx, never followed |
|
|
279
295
|
| `.visit(path, scenario?)` | website | `PageResult` | Render the page in a shared chromium; with a scenario, the capture is the final state |
|
|
280
296
|
| `.open(deepLink?, scenario?)` | mobile | `ScreenResult` | Relaunch the app fresh on the simulator; with a scenario, the capture is the final state |
|
package/dist/checker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as TOKEN_KINDS } from "./match.js";
|
|
2
|
+
import { a as LiterateSyntaxError, o as parseLiterateFile, r as TOKEN_KINDS } from "./match.js";
|
|
3
3
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
//#region src/lint/checker-crossfile.ts
|
|
@@ -17,7 +17,9 @@ import { basename, dirname, join, relative, resolve } from "node:path";
|
|
|
17
17
|
*
|
|
18
18
|
* - **C9 dead fixtures** — per feature dir, a fixture file no test literal
|
|
19
19
|
* references is dead weight; a feature dir with conventional subdirs but no
|
|
20
|
-
* `<feature>.test.ts` is an orphan.
|
|
20
|
+
* `<feature>.test.ts` is an orphan. A literate `<case>.cli` counts as a test
|
|
21
|
+
* AND as a referrer: the paths on its `fixture:` lines keep those trees alive
|
|
22
|
+
* exactly as a `.fixture('…')` literal does.
|
|
21
23
|
* - **B5 await-using (inference)** — the docker-aware runners of a spec file
|
|
22
24
|
* are derived from its `docker:` option, then every `<runner>….exec()` bound
|
|
23
25
|
* without `await using` in an importing test is flagged. The primary B5
|
|
@@ -160,6 +162,23 @@ function blankComments(text) {
|
|
|
160
162
|
function readSource(path) {
|
|
161
163
|
return blankComments(readText(path));
|
|
162
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* The fixture paths a literate `<case>.cli` names — its referrer literals. The
|
|
167
|
+
* runner's own parser reads them, so the two channels cannot disagree about
|
|
168
|
+
* what the file says; an unparseable file vouches for nothing (the D4b/B4 pass
|
|
169
|
+
* is what reports it).
|
|
170
|
+
*/
|
|
171
|
+
function collectLiterateLiterals(path) {
|
|
172
|
+
try {
|
|
173
|
+
return new Set(parseLiterateFile(readText(path), path).header.fixtures);
|
|
174
|
+
} catch {
|
|
175
|
+
return /* @__PURE__ */ new Set();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Is this a file a domain's assets may be referenced from? */
|
|
179
|
+
function isReferrer(name) {
|
|
180
|
+
return name.endsWith(".test.ts") || name.endsWith(".cli");
|
|
181
|
+
}
|
|
163
182
|
function isDir(path) {
|
|
164
183
|
try {
|
|
165
184
|
return statSync(path).isDirectory();
|
|
@@ -478,13 +497,13 @@ function checkDeadFixtures(rootDir) {
|
|
|
478
497
|
const convSubdirs = entries.filter((entry) => entry.isDirectory() && CONVENTIONAL_SUBDIRS.has(entry.name) && isConventionalSubdir(join(dir, entry.name))).map((entry) => entry.name);
|
|
479
498
|
if (convSubdirs.length === 0) continue;
|
|
480
499
|
const feature = basename(dir);
|
|
481
|
-
const testFiles = entries.filter((entry) => entry.isFile() && entry.name
|
|
500
|
+
const testFiles = entries.filter((entry) => entry.isFile() && isReferrer(entry.name)).map((entry) => join(dir, entry.name));
|
|
482
501
|
const rel = relative(rootDir, dir);
|
|
483
502
|
if (testFiles.length === 0) {
|
|
484
503
|
violations.push({
|
|
485
504
|
file: rel,
|
|
486
505
|
line: 1,
|
|
487
|
-
message: `${rel}: domain directory has conventional subdirs (${convSubdirs.join(", ")}) but no *.test.ts (C9 — see docs/10-linting.md)`,
|
|
506
|
+
message: `${rel}: domain directory has conventional subdirs (${convSubdirs.join(", ")}) but no *.test.ts or *.cli (C9 — see docs/10-linting.md)`,
|
|
488
507
|
severity: "error"
|
|
489
508
|
});
|
|
490
509
|
continue;
|
|
@@ -492,6 +511,10 @@ function checkDeadFixtures(rootDir) {
|
|
|
492
511
|
const literals = /* @__PURE__ */ new Set();
|
|
493
512
|
let downgrade = false;
|
|
494
513
|
for (const testFile of testFiles) {
|
|
514
|
+
if (testFile.endsWith(".cli")) {
|
|
515
|
+
for (const literal of collectLiterateLiterals(testFile)) literals.add(literal);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
495
518
|
const text = readSource(testFile);
|
|
496
519
|
for (const literal of collectLiterals(text)) literals.add(literal);
|
|
497
520
|
downgrade ||= hasNonLiteralFixtureArg(text);
|
|
@@ -521,9 +544,10 @@ function checkPoolFixtures(rootDir) {
|
|
|
521
544
|
for (const pool of findPools(rootDir)) {
|
|
522
545
|
const specsRoot = dirname(pool);
|
|
523
546
|
const literals = /* @__PURE__ */ new Set();
|
|
524
|
-
for (const file of listFiles(specsRoot, (path) => path.endsWith(".ts"))) {
|
|
547
|
+
for (const file of listFiles(specsRoot, (path) => path.endsWith(".ts") || path.endsWith(".cli"))) {
|
|
525
548
|
if (file.startsWith(`${pool}/`) || file.startsWith(`${pool}\\`)) continue;
|
|
526
|
-
|
|
549
|
+
const found = file.endsWith(".cli") ? collectLiterateLiterals(file) : collectLiterals(readSource(file));
|
|
550
|
+
for (const literal of found) literals.add(literal);
|
|
527
551
|
}
|
|
528
552
|
for (const entry of readdirSync(pool, { withFileTypes: true })) if (![...literals].some((literal) => literal.includes(entry.name))) {
|
|
529
553
|
const relEntry = relative(rootDir, join(pool, entry.name));
|
|
@@ -551,7 +575,10 @@ function checkPoolFixtures(rootDir) {
|
|
|
551
575
|
* - the **HTTP first-line grammar** of depth-1 `requests/*.http` (a request line)
|
|
552
576
|
* and `expected/*.http` (a status line) (D4b);
|
|
553
577
|
* - **tokens leaking into `requests/`** — requests are inputs, never matched, so
|
|
554
|
-
* a `{{token}}` there is almost always a mistake (D10, warning)
|
|
578
|
+
* a `{{token}}` there is almost always a mistake (D10, warning);
|
|
579
|
+
* - the **literate `.cli` grammar** wherever a scenario file sits: its narrative
|
|
580
|
+
* (B4) and its shape (D4b), read through the runner's own parser, plus the
|
|
581
|
+
* token vocabulary of its blocks (D4).
|
|
555
582
|
*
|
|
556
583
|
* It shares TOKEN_KINDS with the runtime matcher so the channels cannot drift.
|
|
557
584
|
*/
|
|
@@ -632,6 +659,40 @@ function firstLine(text) {
|
|
|
632
659
|
const REQUEST_LINE = /^(?<method>GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS) \/\S*/;
|
|
633
660
|
const STATUS_LINE = /^HTTP\/\d(?:\.\d)? \d{3}\b/;
|
|
634
661
|
/**
|
|
662
|
+
* Check one literate `<case>.cli` file — B4's narrative and D4b's shape
|
|
663
|
+
* through the SAME parser the runner uses (one grammar, no drift), then D4's
|
|
664
|
+
* token vocabulary over its BODY only: a `{{token}}` in the header is text,
|
|
665
|
+
* never a placeholder.
|
|
666
|
+
*
|
|
667
|
+
* Precision over recall, as everywhere in this channel: the parser refuses at
|
|
668
|
+
* the first defect, so one file reports one grammar violation per run.
|
|
669
|
+
*/
|
|
670
|
+
function checkLiterateFile(text, rel) {
|
|
671
|
+
const violations = [];
|
|
672
|
+
let bodyStart = 0;
|
|
673
|
+
try {
|
|
674
|
+
bodyStart = parseLiterateFile(text, rel).headerText.split("\n").length - 1;
|
|
675
|
+
} catch (error) {
|
|
676
|
+
if (!(error instanceof LiterateSyntaxError)) throw error;
|
|
677
|
+
const rule = error.defect === "narrative" ? "B4" : "D4b";
|
|
678
|
+
return [{
|
|
679
|
+
file: rel,
|
|
680
|
+
line: error.line,
|
|
681
|
+
message: `${error.message} (${rule} — see docs/10-linting.md)`,
|
|
682
|
+
severity: "error"
|
|
683
|
+
}];
|
|
684
|
+
}
|
|
685
|
+
const body = text.split("\n").slice(bodyStart).join("\n");
|
|
686
|
+
for (const { line, token } of findUnknownTokens(body)) violations.push({
|
|
687
|
+
file: rel,
|
|
688
|
+
line: line + bodyStart,
|
|
689
|
+
message: `${rel}:${line + bodyStart}: unknown token ${token} — the D4 vocabulary is frozen (known: ${[...TOKEN_KINDS].join(", ")})`,
|
|
690
|
+
severity: "error",
|
|
691
|
+
token
|
|
692
|
+
});
|
|
693
|
+
return violations;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
635
696
|
* Walk `rootDir` and check every fixture file. Paths in the result are relative
|
|
636
697
|
* to `rootDir`. Errors fail the checker; warnings are advisory.
|
|
637
698
|
*/
|
|
@@ -653,6 +714,11 @@ function checkConventionFiles(rootDir) {
|
|
|
653
714
|
visit(path, next);
|
|
654
715
|
continue;
|
|
655
716
|
}
|
|
717
|
+
if (entry.name.endsWith(".cli")) {
|
|
718
|
+
const text = decodeText(path);
|
|
719
|
+
if (text !== null) violations.push(...checkLiterateFile(text, rel));
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
656
722
|
if (inside === null) continue;
|
|
657
723
|
const depth1 = dir.endsWith(`/${inside}`) || dir.endsWith(`\\${inside}`);
|
|
658
724
|
if (inside === "requests") {
|
package/dist/index.d.ts
CHANGED
|
@@ -926,6 +926,48 @@ declare class HttpResult extends BaseResult {
|
|
|
926
926
|
get status(): number;
|
|
927
927
|
}
|
|
928
928
|
//#endregion
|
|
929
|
+
//#region src/core/specification/cli/literate.d.ts
|
|
930
|
+
/**
|
|
931
|
+
* Running a literate `<case>.cli` spec: one scenario, one workdir, one set of
|
|
932
|
+
* servers, every `$` block executed and asserted.
|
|
933
|
+
*
|
|
934
|
+
* The engine composes pieces that already exist rather than adding a second of
|
|
935
|
+
* anything — {@link copyPlan} for `fixture:`, the runner's own command adapter
|
|
936
|
+
* for each block, {@link ServeAdapter} for `serve:`, the `{{token}}` engine for
|
|
937
|
+
* the comparison, and {@link mergeTextPreservingPlaceholders} for the rewrite.
|
|
938
|
+
* What is new here is only the ORCHESTRATION the chain cannot express: several
|
|
939
|
+
* commands sharing one working directory, each asserted, none stopping the run.
|
|
940
|
+
*/
|
|
941
|
+
/**
|
|
942
|
+
* A server a literate header may start by name (`serve: mcp KEY=value`),
|
|
943
|
+
* registered once per app in the `serve` option of `specification.cli()`.
|
|
944
|
+
*/
|
|
945
|
+
interface LiterateServeRegistration {
|
|
946
|
+
/** Shell command that starts the server, run from the project root. */
|
|
947
|
+
command: string;
|
|
948
|
+
/** The variable the resolved URL is bound to in every block's child env. */
|
|
949
|
+
env: string;
|
|
950
|
+
/**
|
|
951
|
+
* Matched against the server's output; the FIRST capture group is the port
|
|
952
|
+
* it chose — named (`(?<port>\d+)`) or not, it is group 1 either way.
|
|
953
|
+
*/
|
|
954
|
+
ready: RegExp;
|
|
955
|
+
/** Builds the URL bound to {@link env} from the announced port. */
|
|
956
|
+
url: (port: number) => string;
|
|
957
|
+
}
|
|
958
|
+
/** Per-call options for {@link runLiterateSpec} / `cli.run()`. */
|
|
959
|
+
interface LiterateRunFlags {
|
|
960
|
+
/**
|
|
961
|
+
* Opt this file OUT of the update-mode rewrite. A frozen file is NEVER
|
|
962
|
+
* written under `TEST_UPDATE=1`: its mismatch still throws its diff. That
|
|
963
|
+
* is what makes a DELIBERATELY-WRONG `.cli` — one whose failure rendering
|
|
964
|
+
* is the subject of a negative test — survive an update run instead of
|
|
965
|
+
* being silently corrected into a passing file. The `.cli` mirror of
|
|
966
|
+
* `toMatch(name, { frozen: true })`.
|
|
967
|
+
*/
|
|
968
|
+
frozen?: boolean;
|
|
969
|
+
}
|
|
970
|
+
//#endregion
|
|
929
971
|
//#region src/core/specification/mobile/result.d.ts
|
|
930
972
|
/** Result from a `.open()` action — the screen as the device saw it, final state. */
|
|
931
973
|
declare class ScreenResult extends BaseResult {
|
|
@@ -1135,13 +1177,25 @@ interface SpecificationConfig {
|
|
|
1135
1177
|
* facet factories auto-populate this when `dockerConfig` is present.
|
|
1136
1178
|
*/
|
|
1137
1179
|
dockerTestRunId?: string;
|
|
1180
|
+
/**
|
|
1181
|
+
* Named environment SETS a literate `.cli` header may name by bare word
|
|
1182
|
+
* (`env: frozen`). Declared once per app in `specification.cli()`.
|
|
1183
|
+
*/
|
|
1184
|
+
envSets?: Record<string, CliEnv>;
|
|
1138
1185
|
/**
|
|
1139
1186
|
* When set, `.intercept()` is unavailable on this runner and throws this
|
|
1140
1187
|
* reason immediately (compose mode — MSW is in-process, CONVENTIONS I3).
|
|
1141
1188
|
*/
|
|
1142
1189
|
interceptDisabledReason?: string;
|
|
1143
1190
|
jobs?: JobHandle[];
|
|
1191
|
+
/** The project root — the working directory a literate `serve:` command runs from. */
|
|
1192
|
+
root?: string;
|
|
1144
1193
|
server?: ServerPort;
|
|
1194
|
+
/**
|
|
1195
|
+
* Named servers a literate `.cli` header may start (`serve: mcp KEY=value`).
|
|
1196
|
+
* Declared once per app in `specification.cli()`.
|
|
1197
|
+
*/
|
|
1198
|
+
serveRegistry?: Record<string, LiterateServeRegistration>;
|
|
1145
1199
|
/**
|
|
1146
1200
|
* The declared services record. In cli mode, drives the automatic
|
|
1147
1201
|
* connection-URL injection into the child env (CONVENTIONS B6):
|
|
@@ -1230,6 +1284,14 @@ interface CliSpecification<DatabaseKey extends string = string> {
|
|
|
1230
1284
|
* timeout.
|
|
1231
1285
|
*/
|
|
1232
1286
|
exec: (args?: string | string[], options?: ExecOptions) => Promise<CliResult>;
|
|
1287
|
+
/**
|
|
1288
|
+
* Run a literate `<case>.cli` spec — its header (fixtures, env sets,
|
|
1289
|
+
* servers) and every `$` block, each asserted — and resolve with the LAST
|
|
1290
|
+
* block's result, so a `.test.ts` can add an assertion the file cannot
|
|
1291
|
+
* express (a directory golden, a grep). The path is relative to the test
|
|
1292
|
+
* file's own directory, where the `.cli` lives.
|
|
1293
|
+
*/
|
|
1294
|
+
run: (file: string, options?: LiterateRunFlags) => Promise<CliResult>;
|
|
1233
1295
|
}
|
|
1234
1296
|
/**
|
|
1235
1297
|
* The `website` facet — page chain entry handed out by
|
|
@@ -1561,6 +1623,20 @@ interface CliSpecificationOptions<Services extends ServiceRecord = ServiceRecord
|
|
|
1561
1623
|
* containers get force-removed at scope exit (CONVENTIONS B5).
|
|
1562
1624
|
*/
|
|
1563
1625
|
docker?: DockerSpecConfig;
|
|
1626
|
+
/**
|
|
1627
|
+
* Named environment SETS for literate `.cli` specs. A header line
|
|
1628
|
+
* `env: frozen` applies the whole `frozen` record; `$WORKDIR` expands and
|
|
1629
|
+
* `null` unsets, exactly as in `.env()`. Declared once per app so a
|
|
1630
|
+
* scenario file states WHICH ground it stands on, not how to build it.
|
|
1631
|
+
*/
|
|
1632
|
+
env?: Record<string, CliEnv>;
|
|
1633
|
+
/**
|
|
1634
|
+
* Named servers a literate `.cli` header may start (`serve: mcp KEY=value`).
|
|
1635
|
+
* Each entry names the shell `command`, the `ready` regex whose capture
|
|
1636
|
+
* group holds the port the server announces, the `url(port)` builder, and
|
|
1637
|
+
* the `env` variable that URL is bound to in every block's child.
|
|
1638
|
+
*/
|
|
1639
|
+
serve?: Record<string, LiterateServeRegistration>;
|
|
1564
1640
|
/**
|
|
1565
1641
|
* Project-root override (CONVENTIONS A9) — the single meaning of `root`:
|
|
1566
1642
|
* it anchors compose detection and local-bin resolution for the tested
|
|
@@ -1699,14 +1775,24 @@ interface MobileHandle {
|
|
|
1699
1775
|
declare function startMobile(options: MobileSpecificationOptions): Promise<MobileHandle>;
|
|
1700
1776
|
//#endregion
|
|
1701
1777
|
//#region src/core/specification/website/serve.adapter.d.ts
|
|
1702
|
-
/** Options for
|
|
1778
|
+
/** Options for a local server started by the framework. */
|
|
1703
1779
|
interface ServeOptions {
|
|
1704
1780
|
/** Shell command that starts the site. Receives the chosen port as `PORT`. */
|
|
1705
1781
|
command: string;
|
|
1706
1782
|
/** Fixed port. Default: an OS-assigned free port, injected as `PORT`. */
|
|
1707
1783
|
port?: number;
|
|
1708
|
-
/**
|
|
1709
|
-
|
|
1784
|
+
/**
|
|
1785
|
+
* How readiness is observed. Two forms:
|
|
1786
|
+
*
|
|
1787
|
+
* - a **path** (or nothing) — the framework picks the port, injects it as
|
|
1788
|
+
* `PORT`, and polls `<baseUrl><path>` until it answers any HTTP status
|
|
1789
|
+
* (default `/`). This is `specification.website()`'s form.
|
|
1790
|
+
* - a **regex over the child's output** with ONE capture group holding the
|
|
1791
|
+
* port the server chose itself. Nothing is injected as `PORT`: the server
|
|
1792
|
+
* announces its port and the framework reads it from the banner. This is
|
|
1793
|
+
* the form a literate `serve:` registration uses.
|
|
1794
|
+
*/
|
|
1795
|
+
ready?: RegExp | string;
|
|
1710
1796
|
/** Readiness budget in milliseconds. Default 30 000. */
|
|
1711
1797
|
timeout?: number;
|
|
1712
1798
|
}
|
|
@@ -1840,6 +1926,85 @@ declare const specification: {
|
|
|
1840
1926
|
website: typeof startWebsite;
|
|
1841
1927
|
};
|
|
1842
1928
|
//#endregion
|
|
1929
|
+
//#region src/core/literate/literate-file.d.ts
|
|
1930
|
+
/**
|
|
1931
|
+
* Parser / serializer for `<case>.cli` literate spec files.
|
|
1932
|
+
*
|
|
1933
|
+
* A literate spec is ONE scenario in one file: a header of `key: value` lines
|
|
1934
|
+
* (the narrative and the ground the run stands on), a blank line, then one or
|
|
1935
|
+
* more `$ <argv>` blocks holding the expected exit code and streams.
|
|
1936
|
+
*
|
|
1937
|
+
* test: refuses to guess when it is run outside the checkout
|
|
1938
|
+
* given: a workdir with no home/ + apps/ pair anywhere above it
|
|
1939
|
+
* then: the error names where the command has to be run
|
|
1940
|
+
* fixture: $FIXTURES/repositories-stub/
|
|
1941
|
+
* env: frozen TERRA_ORIGIN=http://127.0.0.1:9
|
|
1942
|
+
* serve: mcp MCP_STUB_WITHHOLD=get-article
|
|
1943
|
+
*
|
|
1944
|
+
* $ terra git repositories
|
|
1945
|
+
* exit: 1
|
|
1946
|
+
* --- stderr
|
|
1947
|
+
* Error: no directory with home/ and apps/ above the current directory
|
|
1948
|
+
*
|
|
1949
|
+
* The shape mirrors the `.http` grammar (a start section, then a body split at
|
|
1950
|
+
* the first blank line) so a reader who knows one knows the other. The parser
|
|
1951
|
+
* is pure text: it resolves nothing, runs nothing, and knows no filesystem.
|
|
1952
|
+
*/
|
|
1953
|
+
/** One `KEY=value` (or a bare word naming a registered env set) from an `env:` line. */
|
|
1954
|
+
type LiterateEnvToken = {
|
|
1955
|
+
key: string;
|
|
1956
|
+
kind: 'pair';
|
|
1957
|
+
value: string;
|
|
1958
|
+
} | {
|
|
1959
|
+
kind: 'set';
|
|
1960
|
+
name: string;
|
|
1961
|
+
};
|
|
1962
|
+
/** One `serve:` line — a registered server name plus the extra env it is started with. */
|
|
1963
|
+
interface LiterateServeEntry {
|
|
1964
|
+
env: Record<string, string>;
|
|
1965
|
+
line: number;
|
|
1966
|
+
name: string;
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* The header of a literate spec — everything before the first blank line.
|
|
1970
|
+
*
|
|
1971
|
+
* The field names ARE the grammar keys, `then:` included: a reader must be able
|
|
1972
|
+
* to move between the file and this type without a translation table. The
|
|
1973
|
+
* thenable hazard the linter guards is not reachable here — a header is data
|
|
1974
|
+
* read out of a `LiterateSpec`, never a value the framework awaits.
|
|
1975
|
+
*/
|
|
1976
|
+
interface LiterateHeader {
|
|
1977
|
+
/** `env:` lines, flattened to their tokens in declaration order. */
|
|
1978
|
+
env: LiterateEnvToken[];
|
|
1979
|
+
/** `fixture:` paths, in declaration order (they layer). */
|
|
1980
|
+
fixtures: string[];
|
|
1981
|
+
given: string;
|
|
1982
|
+
/** `serve:` lines, in declaration order. */
|
|
1983
|
+
serve: LiterateServeEntry[];
|
|
1984
|
+
/** The vitest test title. */
|
|
1985
|
+
test: string;
|
|
1986
|
+
then: string;
|
|
1987
|
+
}
|
|
1988
|
+
/** One `$ <argv>` block: the command, its exit code, and both streams verbatim. */
|
|
1989
|
+
interface LiterateBlock {
|
|
1990
|
+
/** The command line, exactly as written after `$ ` (may be empty). */
|
|
1991
|
+
argv: string;
|
|
1992
|
+
exitCode: number;
|
|
1993
|
+
/** 1-based line of the `$ ` line in the source file. */
|
|
1994
|
+
line: number;
|
|
1995
|
+
/** Expected stderr; `null` when the block declares no `--- stderr` section. */
|
|
1996
|
+
stderr: null | string;
|
|
1997
|
+
/** Expected stdout, verbatim (no trailing newline). */
|
|
1998
|
+
stdout: string;
|
|
1999
|
+
}
|
|
2000
|
+
/** A parsed `<case>.cli` file. */
|
|
2001
|
+
interface LiterateSpec {
|
|
2002
|
+
blocks: LiterateBlock[];
|
|
2003
|
+
header: LiterateHeader;
|
|
2004
|
+
/** The header text verbatim, including its trailing blank line — never rewritten. */
|
|
2005
|
+
headerText: string;
|
|
2006
|
+
}
|
|
2007
|
+
//#endregion
|
|
1843
2008
|
//#region src/integrations/docker/docker-lookup.d.ts
|
|
1844
2009
|
/** Return all container IDs (running or stopped) that carry `key=value`. */
|
|
1845
2010
|
declare function findContainersByLabel(key: string, value: string): string[];
|
|
@@ -2262,7 +2427,7 @@ declare const mockOfDate: MockDatePort;
|
|
|
2262
2427
|
//#endregion
|
|
2263
2428
|
//#region src/index.d.ts
|
|
2264
2429
|
declare module 'vitest' {
|
|
2265
|
-
interface
|
|
2430
|
+
interface Assertion<T = any> {
|
|
2266
2431
|
/**
|
|
2267
2432
|
* Assert the subject is empty — zero rows for a table (async), an
|
|
2268
2433
|
* empty stream for a text accessor (console, errors, stdout).
|
|
@@ -2272,17 +2437,6 @@ declare module 'vitest' {
|
|
|
2272
2437
|
} ? () => Promise<void> : never;
|
|
2273
2438
|
/** Assert the container is running. Async — docker-backed subject. */
|
|
2274
2439
|
toBeRunning: T extends ContainerAccessor ? () => Promise<void> : never;
|
|
2275
|
-
/**
|
|
2276
|
-
* Assert the table contains exactly the given rows for the given
|
|
2277
|
-
* columns. Cells accept `match.*` dynamic-value matchers. Async —
|
|
2278
|
-
* queries the database.
|
|
2279
|
-
*/
|
|
2280
|
-
toMatchRows: T extends TableAccessor ? (expected: {
|
|
2281
|
-
columns: string[];
|
|
2282
|
-
rows: readonly (readonly unknown[])[];
|
|
2283
|
-
}) => Promise<void> : never;
|
|
2284
|
-
}
|
|
2285
|
-
interface Assertion<T = any> {
|
|
2286
2440
|
/**
|
|
2287
2441
|
* On `@jterrazz/test` accessors: assert the subject matches a fixture
|
|
2288
2442
|
* file under `expected/<name>` (flat — a slash creates a subfolder;
|
|
@@ -2297,7 +2451,16 @@ declare module 'vitest' {
|
|
|
2297
2451
|
* the behaviour under test.
|
|
2298
2452
|
*/
|
|
2299
2453
|
toMatch: T extends DirectoryAccessor | FilesystemAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => Promise<void> : T extends JsonAccessor | ResponseAccessor | TextAccessor ? (name: RegExp | string, options?: MatchFixtureOptions) => void : (expected: RegExp | string, options?: MatchFixtureOptions) => void;
|
|
2454
|
+
/**
|
|
2455
|
+
* Assert the table contains exactly the given rows for the given
|
|
2456
|
+
* columns. Cells accept `match.*` dynamic-value matchers. Async —
|
|
2457
|
+
* queries the database.
|
|
2458
|
+
*/
|
|
2459
|
+
toMatchRows: T extends TableAccessor ? (expected: {
|
|
2460
|
+
columns: string[];
|
|
2461
|
+
rows: readonly (readonly unknown[])[];
|
|
2462
|
+
}) => Promise<void> : never;
|
|
2300
2463
|
}
|
|
2301
2464
|
}
|
|
2302
2465
|
//#endregion
|
|
2303
|
-
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CaptureScope, type CliEnv, type CliHandle, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, ContainerAccessor, type ContainerPort, type Contract, type ContractInput, type ContractRequest, type ContractResponder, type ContractResponse, type ContractResponseValue, type Contracts, type DatabaseKeys, type DatabasePort, type DeviceOpenOptions, type DevicePort, type DeviceScreen, DirectoryAccessor, type DockerSpecConfig, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, HttpResult, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type LandmarkKind, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MobileBackendOptions, type MobileElementKind, type MobileElementMatch, type MobileElementRef, type MobileHandle, type MobileScenario, type MobileSpecification, type MobileSpecificationOptions, type MobileVisitor, type MockDatePort, type MockPort, Orchestrator, PageResult, type PostgresOptions, type RedisOptions, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, type ServiceHandle, type ServiceRecord, type SpecificationConfig, type SpecificationMode, type SqliteOptions, TableAccessor, TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, complementary, content, contentinfo, defineContract, defineContracts, field, findContainersByLabel, form, heading, http, inspectContainer, link, main, match, mockOf, mockOfDate, navigation, openai, postgres, redis, region, removeContainers, search, specification, sqlite, testId, text, within };
|
|
2466
|
+
export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CaptureScope, type CliEnv, type CliHandle, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, ContainerAccessor, type ContainerPort, type Contract, type ContractInput, type ContractRequest, type ContractResponder, type ContractResponse, type ContractResponseValue, type Contracts, type DatabaseKeys, type DatabasePort, type DeviceOpenOptions, type DevicePort, type DeviceScreen, DirectoryAccessor, type DockerSpecConfig, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, HttpResult, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type LandmarkKind, type LiterateBlock, type LiterateHeader, type LiterateRunFlags, type LiterateServeRegistration, type LiterateSpec, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MobileBackendOptions, type MobileElementKind, type MobileElementMatch, type MobileElementRef, type MobileHandle, type MobileScenario, type MobileSpecification, type MobileSpecificationOptions, type MobileVisitor, type MockDatePort, type MockPort, Orchestrator, PageResult, type PostgresOptions, type RedisOptions, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, type ServiceHandle, type ServiceRecord, type SpecificationConfig, type SpecificationMode, type SqliteOptions, TableAccessor, TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, complementary, content, contentinfo, defineContract, defineContracts, field, findContainersByLabel, form, heading, http, inspectContainer, link, main, match, mockOf, mockOfDate, navigation, openai, postgres, redis, region, removeContainers, search, specification, sqlite, testId, text, within };
|