@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 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.endsWith(".test.ts")).map((entry) => join(dir, 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
- for (const literal of collectLiterals(readSource(file))) literals.add(literal);
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 the local server started by `specification.website()`. */
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
- /** Path polled until it answers with any HTTP status. Default `/`. */
1709
- ready?: string;
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 Matchers<T = any> {
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 };