@jterrazz/test 12.2.0 → 13.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/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Scalar } from "yaml";
1
2
  import { DeepMockProxy } from "vitest-mock-extended";
2
3
  //#region src/core/matching/match.d.ts
3
4
  /**
@@ -390,9 +391,20 @@ type CliEnv = Record<string, null | string>;
390
391
  * Abstract command interface for specification runners.
391
392
  * Implement this to plug in your command execution strategy.
392
393
  */
394
+ /**
395
+ * What one invocation is handed beyond its arguments — the two per-run inputs a
396
+ * spec document may state. Both are absent by default: the child gets an
397
+ * immediately-closed stdin (never a TTY) and the adapter's own timeout.
398
+ */
399
+ interface CliInput {
400
+ /** Written to the child's stdin, which is then closed. */
401
+ stdin?: string;
402
+ /** Milliseconds after which the run is killed (exit code 124). */
403
+ timeout?: number;
404
+ }
393
405
  interface CliPort {
394
406
  /** Execute a command with the given arguments in the given working directory. */
395
- exec: (args: string, cwd: string, env?: CliEnv) => Promise<CliOutput>;
407
+ exec: (args: string, cwd: string, env?: CliEnv, input?: CliInput) => Promise<CliOutput>;
396
408
  /** Run a long-running process and wait for a pattern or timeout. */
397
409
  watch: (args: string, cwd: string, options: ExecOptions, env?: CliEnv) => Promise<CliOutput>;
398
410
  }
@@ -928,24 +940,25 @@ declare class HttpResult extends BaseResult {
928
940
  //#endregion
929
941
  //#region src/core/specification/cli/literate.d.ts
930
942
  /**
931
- * Running a literate `<case>.cli` spec: one scenario, one workdir, one set of
932
- * servers, every `$` block executed and asserted.
943
+ * Running a `<case>.spec.yaml` document: one scenario, one working directory,
944
+ * one set of servers, every run executed and asserted.
933
945
  *
934
946
  * The engine composes pieces that already exist rather than adding a second of
935
947
  * 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.
948
+ * for each run, {@link ServeAdapter} for `serve:`, the `{{token}}` engine for
949
+ * every comparison, and {@link mergeTextPreservingPlaceholders} for the
950
+ * rewrite. What is new here is only the ORCHESTRATION the chain cannot express:
951
+ * several commands sharing one working directory, each asserted, none stopping
952
+ * the run.
940
953
  */
941
954
  /**
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()`.
955
+ * A server a document may start by name (`serve: mcp`), registered once per app
956
+ * in the `serve` option of `specification.cli()`.
944
957
  */
945
958
  interface LiterateServeRegistration {
946
959
  /** Shell command that starts the server, run from the project root. */
947
960
  command: string;
948
- /** The variable the resolved URL is bound to in every block's child env. */
961
+ /** The variable the resolved URL is bound to in every run's child env. */
949
962
  env: string;
950
963
  /**
951
964
  * Matched against the server's output; the FIRST capture group is the port
@@ -955,15 +968,15 @@ interface LiterateServeRegistration {
955
968
  /** Builds the URL bound to {@link env} from the announced port. */
956
969
  url: (port: number) => string;
957
970
  }
958
- /** Per-call options for {@link runLiterateSpec} / `cli.run()`. */
971
+ /** Per-call options for {@link runSpecDocument} / `cli.run()`. */
959
972
  interface LiterateRunFlags {
960
973
  /**
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 })`.
974
+ * Opt this document OUT of the update-mode rewrite. A frozen document is
975
+ * NEVER written under `TEST_UPDATE=1`: its mismatch still throws its diff.
976
+ * That is what makes a DELIBERATELY-WRONG `.spec.yaml` — one whose failure
977
+ * rendering is the subject of a negative test — survive an update run
978
+ * instead of being silently corrected into a passing file. The document's
979
+ * mirror of `toMatch(name, { frozen: true })`.
967
980
  */
968
981
  frozen?: boolean;
969
982
  }
@@ -1178,7 +1191,7 @@ interface SpecificationConfig {
1178
1191
  */
1179
1192
  dockerTestRunId?: string;
1180
1193
  /**
1181
- * Named environment SETS a literate `.cli` header may name by bare word
1194
+ * Named environment SETS a spec document may name by bare word
1182
1195
  * (`env: frozen`). Declared once per app in `specification.cli()`.
1183
1196
  */
1184
1197
  envSets?: Record<string, CliEnv>;
@@ -1188,12 +1201,12 @@ interface SpecificationConfig {
1188
1201
  */
1189
1202
  interceptDisabledReason?: string;
1190
1203
  jobs?: JobHandle[];
1191
- /** The project root — the working directory a literate `serve:` command runs from. */
1204
+ /** The project root — the working directory a document's `serve:` command runs from. */
1192
1205
  root?: string;
1193
1206
  server?: ServerPort;
1194
1207
  /**
1195
- * Named servers a literate `.cli` header may start (`serve: mcp KEY=value`).
1196
- * Declared once per app in `specification.cli()`.
1208
+ * Named servers a spec document may start (`serve: [mcp]`). Declared once
1209
+ * per app in `specification.cli()`.
1197
1210
  */
1198
1211
  serveRegistry?: Record<string, LiterateServeRegistration>;
1199
1212
  /**
@@ -1285,11 +1298,11 @@ interface CliSpecification<DatabaseKey extends string = string> {
1285
1298
  */
1286
1299
  exec: (args?: string | string[], options?: ExecOptions) => Promise<CliResult>;
1287
1300
  /**
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.
1301
+ * Run a `<case>.spec.yaml` document — its ground (fixtures, env sets,
1302
+ * servers) and every run, each asserted — and resolve with the LAST run's
1303
+ * result, so a `.test.ts` can add an assertion the document cannot express
1304
+ * (a directory golden, a grep). The path is relative to the test file's own
1305
+ * directory, where the document lives.
1293
1306
  */
1294
1307
  run: (file: string, options?: LiterateRunFlags) => Promise<CliResult>;
1295
1308
  }
@@ -1926,83 +1939,84 @@ declare const specification: {
1926
1939
  website: typeof startWebsite;
1927
1940
  };
1928
1941
  //#endregion
1929
- //#region src/core/literate/literate-file.d.ts
1942
+ //#region src/core/literate/spec-document.d.ts
1930
1943
  /**
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.
1944
+ * The facets a document may describe. `cli` is the only one implemented; the
1945
+ * key exists so a second facet can arrive without a second grammar.
1952
1946
  */
1953
- /** One `KEY=value` (or a bare word naming a registered env set) from an `env:` line. */
1954
- type LiterateEnvToken = {
1947
+ declare const SPEC_KINDS: readonly ["cli"];
1948
+ type SpecKind = (typeof SPEC_KINDS)[number];
1949
+ /** One `env:` entry — a bare word naming a registered set, or a `KEY=value` pair. */
1950
+ type SpecEnvToken = {
1955
1951
  key: string;
1956
1952
  kind: 'pair';
1953
+ line: number;
1957
1954
  value: string;
1958
1955
  } | {
1959
1956
  kind: 'set';
1957
+ line: number;
1960
1958
  name: string;
1961
1959
  };
1962
- /** One `serve:` line — a registered server name plus the extra env it is started with. */
1963
- interface LiterateServeEntry {
1960
+ /** One `serve:` entry — a registered server plus the extra env it is started with. */
1961
+ interface SpecServeEntry {
1964
1962
  env: Record<string, string>;
1965
1963
  line: number;
1966
1964
  name: string;
1967
1965
  }
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;
1966
+ /** One `fixture:` entry — a path with the line it was declared on. */
1967
+ interface SpecFixture {
1968
+ line: number;
1969
+ path: string;
1987
1970
  }
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. */
1971
+ /** An expected stream: its text, and the first line of that text in the file. */
1972
+ interface SpecStream {
1973
+ /** 1-based line the scalar's content starts on — where a token defect is reported. */
1994
1974
  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;
1975
+ text: string;
1999
1976
  }
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;
1977
+ /** One `files:` assertion, keyed by a workdir-relative path. */
1978
+ type SpecFileAssertion = {
1979
+ contains: SpecStream[];
1980
+ equals: null | SpecStream;
1981
+ kind: 'content';
1982
+ line: number;
1983
+ path: string;
1984
+ } | {
1985
+ kind: 'absent';
1986
+ line: number;
1987
+ path: string;
1988
+ } | {
1989
+ kind: 'exists';
1990
+ line: number;
1991
+ path: string;
1992
+ };
1993
+ /** One run: a command, what it must exit with, and what it must have produced. */
1994
+ interface SpecRun {
1995
+ command: string;
1996
+ /** 1-based line of the `command:` key — the frame a failure points at. */
1997
+ commandLine: number;
1998
+ exitCode: number;
1999
+ files: SpecFileAssertion[];
2000
+ /** `null` when the key is absent, which asserts an empty stream. */
2001
+ stderr: null | SpecStream;
2002
+ /** Written to the child then closed; `null` keeps the empty-pipe EOF. */
2003
+ stdin: null | string;
2004
+ stdout: null | SpecStream;
2005
+ timeout: null | number;
2006
+ /** Only ever on the LAST run — a long-running command ends the session. */
2007
+ waitFor: null | string;
2008
+ }
2009
+ /** A parsed `<case>.spec.yaml`. */
2010
+ interface SpecDocument {
2011
+ /** The vitest test title. */
2012
+ description: string;
2013
+ /** 1-based line of the `description:` key. */
2014
+ descriptionLine: number;
2015
+ env: SpecEnvToken[];
2016
+ fixtures: SpecFixture[];
2017
+ kind: SpecKind;
2018
+ runs: SpecRun[];
2019
+ serve: SpecServeEntry[];
2006
2020
  }
2007
2021
  //#endregion
2008
2022
  //#region src/integrations/docker/docker-lookup.d.ts
@@ -2463,4 +2477,4 @@ declare module 'vitest' {
2463
2477
  }
2464
2478
  }
2465
2479
  //#endregion
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 };
2480
+ 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 CliInput, 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 LiterateRunFlags, type LiterateServeRegistration, 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 SpecDocument, type SpecEnvToken, type SpecFileAssertion, type SpecFixture, type SpecKind, type SpecRun, type SpecServeEntry, type SpecStream, 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 };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { i as match, n as Matcher, t as CaptureScope } from "./match.js";
2
- import { A as mergeTextPreservingPlaceholders, C as formatDirectoryDiff, D as formatTableDiff, E as formatStdoutDiff, F as textEquals, M as renderExpected, N as structuralEquals, P as structuralSubset, S as walkDirectory, T as formatStartupReport, _ as text, a as UPDATE_HINT, b as DirectoryAccessor, c as CliResult, d as findContainersByLabel, f as inspectContainer, g as TextAccessor, h as JsonAccessor, i as copyPlan, k as mergePreservingPlaceholders, l as BaseResult, m as TableAccessor, n as runLiterateSpec, o as shouldUpdateSnapshots, p as removeContainers, r as ServeAdapter, s as ContainerAccessor, u as validateDatabaseOption, v as getCallerDir, w as formatResponseDiff, x as diffDirectories, y as FilesystemAccessor } from "./literate.js";
1
+ import { a as copyPlan, i as match, n as Matcher, t as CaptureScope } from "./match.js";
2
+ import { C as formatResponseDiff, E as formatTableDiff, M as structuralEquals, N as structuralSubset, O as mergePreservingPlaceholders, P as textEquals, S as formatDirectoryDiff, T as formatStdoutDiff, _ as getCallerDir, a as shouldUpdateSnapshots, b as diffDirectories, c as BaseResult, d as inspectContainer, f as removeContainers, g as text, h as TextAccessor, i as UPDATE_HINT, j as renderExpected, k as mergeTextPreservingPlaceholders, l as validateDatabaseOption, m as JsonAccessor, o as ContainerAccessor, p as TableAccessor, r as ServeAdapter, s as CliResult, t as runSpecDocument, u as findContainersByLabel, v as FilesystemAccessor, w as formatStartupReport, x as walkDirectory, y as DirectoryAccessor } from "./literate.js";
3
3
  import { a as isContract, i as defineContracts, n as contractsOf, o as isContracts, r as defineContract, t as ContractQueue } from "./queue.js";
4
4
  import { closeSync, copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
@@ -1222,18 +1222,18 @@ var SpecificationBuilder = class {
1222
1222
  });
1223
1223
  }
1224
1224
  /**
1225
- * Run a literate `<case>.cli` spec and resolve with the LAST block's
1226
- * result. The whole file runs in ONE working directory with ONE set of
1227
- * servers, and every block is asserted — unlike `.exec([...])`, a non-zero
1225
+ * Run a `<case>.spec.yaml` document and resolve with the LAST run's
1226
+ * result. The whole file executes in ONE working directory with ONE set of
1227
+ * servers, and every run is asserted — unlike `.exec([...])`, a non-zero
1228
1228
  * exit does not stop the sequence, because here each exit code is part of
1229
- * what the file states.
1229
+ * what the document states.
1230
1230
  *
1231
- * Setup chained BEFORE the call layers underneath the file's own header: a
1232
- * chained `.fixture()` is copied first, the header's `fixture:` lines over
1233
- * it, and the header's `env:` wins over a chained `.env()`.
1231
+ * Setup chained BEFORE the call layers underneath the document's own
1232
+ * ground: a chained `.fixture()` is copied first, the document's `fixture:`
1233
+ * entries over it, and its `env:` wins over a chained `.env()`.
1234
1234
  *
1235
1235
  * @example
1236
- * const result = await cli.run('no-estate.cli');
1236
+ * const result = await cli.run('no-estate.spec.yaml');
1237
1237
  * await expect(result.directory('out')).toMatch('scaffold');
1238
1238
  */
1239
1239
  run(file, options) {
@@ -1492,7 +1492,7 @@ var SpecificationBuilder = class {
1492
1492
  }
1493
1493
  runLiterateAction(workDir, file, options) {
1494
1494
  const filePath = resolve(this.testDir, file);
1495
- return runLiterateSpec({
1495
+ return runSpecDocument({
1496
1496
  baseEnv: this.childEnv(workDir),
1497
1497
  frozen: options?.frozen,
1498
1498
  config: this.config,
@@ -2206,21 +2206,23 @@ var ExecAdapter = class {
2206
2206
  constructor(command) {
2207
2207
  this.command = command;
2208
2208
  }
2209
- async exec(args, cwd, extraEnv) {
2209
+ async exec(args, cwd, extraEnv, input) {
2210
2210
  const env = buildEnv(extraEnv);
2211
2211
  const result = spawnSync(`${this.command} ${args}`, [], {
2212
2212
  cwd,
2213
2213
  encoding: "utf8",
2214
2214
  env,
2215
+ input: input?.stdin,
2215
2216
  shell: true,
2216
2217
  stdio: [
2217
2218
  "pipe",
2218
2219
  "pipe",
2219
2220
  "pipe"
2220
- ]
2221
+ ],
2222
+ timeout: input?.timeout
2221
2223
  });
2222
2224
  return {
2223
- exitCode: result.status ?? 1,
2225
+ exitCode: result.error?.code === "ETIMEDOUT" ? 124 : result.status ?? 1,
2224
2226
  stderr: result.stderr ?? "",
2225
2227
  stdout: result.stdout ?? ""
2226
2228
  };