@elench/testkit 0.1.82 → 0.1.84

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.
Files changed (100) hide show
  1. package/README.md +37 -7
  2. package/lib/cli/agents/index.mjs +64 -0
  3. package/lib/cli/agents/investigate.mjs +75 -0
  4. package/lib/cli/agents/investigation-context.mjs +102 -0
  5. package/lib/cli/agents/prompt-builder.mjs +25 -0
  6. package/lib/cli/agents/providers/claude.mjs +74 -0
  7. package/lib/cli/agents/providers/codex.mjs +83 -0
  8. package/lib/cli/agents/providers/shared.mjs +134 -0
  9. package/lib/cli/command-helpers.mjs +53 -25
  10. package/lib/cli/commands/investigate.mjs +87 -0
  11. package/lib/cli/entrypoint.mjs +3 -0
  12. package/lib/cli/presentation/colors.mjs +12 -0
  13. package/lib/cli/presentation/events-reporter.mjs +135 -0
  14. package/lib/cli/presentation/summary-box.mjs +11 -11
  15. package/lib/cli/presentation/tree-reporter.mjs +159 -0
  16. package/lib/cli/tui/run-app.mjs +1 -0
  17. package/lib/cli/tui/run-session-app.mjs +370 -0
  18. package/lib/cli/tui/run-session-state.mjs +481 -0
  19. package/lib/cli/tui/run-tree-state.mjs +1 -0
  20. package/lib/config-api/auth-fixtures.mjs +15 -10
  21. package/lib/discovery/index.mjs +1 -1
  22. package/lib/index.d.ts +5 -1
  23. package/lib/runner/orchestrator.mjs +1 -0
  24. package/lib/runtime/index.d.ts +138 -5
  25. package/lib/runtime/index.mjs +68 -2
  26. package/lib/runtime-src/k6/http-assertions.js +31 -1
  27. package/lib/runtime-src/k6/http-checks.js +120 -0
  28. package/lib/runtime-src/k6/http-suite-runtime.js +5 -1
  29. package/lib/runtime-src/k6/http.js +213 -23
  30. package/lib/runtime-src/shared/error-body.mjs +42 -0
  31. package/lib/runtime-src/shared/http-parsing.mjs +68 -0
  32. package/node_modules/@elench/next-analysis/package.json +1 -1
  33. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  34. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  35. package/node_modules/@elench/ts-analysis/package.json +1 -1
  36. package/package.json +7 -6
  37. package/lib/app/configs.test.mjs +0 -34
  38. package/lib/app/typecheck.test.mjs +0 -24
  39. package/lib/bundler/index.test.mjs +0 -164
  40. package/lib/cli/args.test.mjs +0 -110
  41. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  42. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  43. package/lib/cli/presentation/summary-box.test.mjs +0 -43
  44. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  45. package/lib/config/database.test.mjs +0 -29
  46. package/lib/config/discovery.test.mjs +0 -276
  47. package/lib/config/env.test.mjs +0 -40
  48. package/lib/config/index.test.mjs +0 -44
  49. package/lib/config/paths.test.mjs +0 -27
  50. package/lib/config/runtime.test.mjs +0 -82
  51. package/lib/config/skip-config.test.mjs +0 -63
  52. package/lib/config-api/index.test.mjs +0 -344
  53. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  54. package/lib/coverage/backend-discovery.test.mjs +0 -61
  55. package/lib/coverage/evidence.test.mjs +0 -87
  56. package/lib/coverage/index.test.mjs +0 -715
  57. package/lib/coverage/routing.test.mjs +0 -36
  58. package/lib/coverage/shared.test.mjs +0 -72
  59. package/lib/database/fingerprint.test.mjs +0 -99
  60. package/lib/database/index.test.mjs +0 -95
  61. package/lib/database/naming.test.mjs +0 -39
  62. package/lib/database/state.test.mjs +0 -66
  63. package/lib/database/template-steps.test.mjs +0 -43
  64. package/lib/discovery/file-metadata.test.mjs +0 -51
  65. package/lib/discovery/index.test.mjs +0 -182
  66. package/lib/discovery/path-policy.test.mjs +0 -65
  67. package/lib/drizzle/index.test.mjs +0 -33
  68. package/lib/env/index.test.mjs +0 -82
  69. package/lib/history/index.test.mjs +0 -115
  70. package/lib/package.test.mjs +0 -59
  71. package/lib/playwright/index.test.mjs +0 -43
  72. package/lib/regressions/github.test.mjs +0 -324
  73. package/lib/regressions/index.test.mjs +0 -187
  74. package/lib/reporters/playwright.test.mjs +0 -167
  75. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  76. package/lib/runner/execution-config.test.mjs +0 -67
  77. package/lib/runner/failure-details.test.mjs +0 -114
  78. package/lib/runner/formatting.test.mjs +0 -205
  79. package/lib/runner/metadata.test.mjs +0 -52
  80. package/lib/runner/planning.test.mjs +0 -371
  81. package/lib/runner/playwright-config.test.mjs +0 -78
  82. package/lib/runner/processes.test.mjs +0 -21
  83. package/lib/runner/regressions.test.mjs +0 -168
  84. package/lib/runner/reporting.test.mjs +0 -310
  85. package/lib/runner/results.test.mjs +0 -376
  86. package/lib/runner/runtime-manager.test.mjs +0 -252
  87. package/lib/runner/runtime-preparation.test.mjs +0 -141
  88. package/lib/runner/selection.test.mjs +0 -24
  89. package/lib/runner/setup-operations.test.mjs +0 -94
  90. package/lib/runner/state.test.mjs +0 -62
  91. package/lib/runner/suite-selection.test.mjs +0 -49
  92. package/lib/runner/template.test.mjs +0 -272
  93. package/lib/shared/build-config.test.mjs +0 -132
  94. package/lib/shared/configured-steps.test.mjs +0 -102
  95. package/lib/shared/execution-schema.test.mjs +0 -26
  96. package/lib/shared/file-timeout.test.mjs +0 -64
  97. package/lib/shared/test-context.test.mjs +0 -43
  98. package/lib/timing/index.test.mjs +0 -64
  99. package/lib/toolchains/index.test.mjs +0 -168
  100. package/lib/vitest/index.test.mjs +0 -20
@@ -1,164 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { pathToFileURL } from "url";
5
- import { afterEach, describe, expect, it } from "vitest";
6
- import { bundleK6File } from "./index.mjs";
7
-
8
- const cleanups = [];
9
-
10
- afterEach(() => {
11
- while (cleanups.length > 0) {
12
- cleanups.pop()();
13
- }
14
- });
15
-
16
- describe("runtime bundler", () => {
17
- it("bundles root and runtime package imports for execution", async () => {
18
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-bundle-"));
19
- cleanups.push(() => fs.rmSync(tmpDir, { force: true, recursive: true }));
20
-
21
- const sourceFile = path.join(tmpDir, "health.js");
22
- fs.writeFileSync(
23
- sourceFile,
24
- [
25
- 'import { defineHttpSuite } from "@elench/testkit";',
26
- 'import { check, json } from "@elench/testkit/runtime";',
27
- "const suite = defineHttpSuite(({ rawReq }) => {",
28
- ' const res = rawReq("GET", "/health");',
29
- " check(json(res), {",
30
- ' "has status": (body) => typeof body.status === "string",',
31
- " });",
32
- "});",
33
- "export default suite;",
34
- "",
35
- ].join("\n")
36
- );
37
-
38
- const bundledFile = await bundleK6File({
39
- productDir: tmpDir,
40
- serviceName: "api",
41
- sourceFile,
42
- });
43
-
44
- const bundled = fs.readFileSync(bundledFile, "utf8");
45
- expect(bundled).toContain("defineHttpSuite");
46
- expect(bundled).toContain('import { check');
47
- expect(bundled).toContain('from "k6"');
48
- });
49
-
50
- it("bundles DAL execution through the public package surface", async () => {
51
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-bundle-"));
52
- cleanups.push(() => fs.rmSync(tmpDir, { force: true, recursive: true }));
53
-
54
- const sourceFile = path.join(tmpDir, "dal.js");
55
- fs.writeFileSync(
56
- sourceFile,
57
- [
58
- 'import { defineDalSuite } from "@elench/testkit";',
59
- "const suite = defineDalSuite(({ db }) => {",
60
- ' db.query("SELECT 1");',
61
- "});",
62
- "export default suite;",
63
- "",
64
- ].join("\n")
65
- );
66
-
67
- const bundledFile = await bundleK6File({
68
- productDir: tmpDir,
69
- serviceName: "api",
70
- sourceFile,
71
- });
72
-
73
- const bundled = fs.readFileSync(bundledFile, "utf8");
74
- expect(bundled).toContain("defineDalSuite");
75
- expect(bundled).toContain('import sql from "k6/x/sql"');
76
- });
77
-
78
- it("bundles scenario execution through the public package surface", async () => {
79
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-bundle-"));
80
- cleanups.push(() => fs.rmSync(tmpDir, { force: true, recursive: true }));
81
-
82
- const sourceFile = path.join(tmpDir, "scenario.js");
83
- fs.writeFileSync(
84
- sourceFile,
85
- [
86
- 'import { defineScenarioSuite } from "@elench/testkit";',
87
- "const suite = defineScenarioSuite(({ scenario }) => {",
88
- " const plan = scenario.choose('journey', { endpoint: scenario.pick('endpoint', ['/a', '/b']) });",
89
- " scenario.step('record choice', () => plan.endpoint);",
90
- "});",
91
- "export default suite;",
92
- "",
93
- ].join("\n")
94
- );
95
-
96
- const bundledFile = await bundleK6File({
97
- productDir: tmpDir,
98
- serviceName: "api",
99
- sourceFile,
100
- });
101
-
102
- const bundled = fs.readFileSync(bundledFile, "utf8");
103
- expect(bundled).toContain("defineScenarioSuite");
104
- expect(bundled).toContain("createScenarioRuntime");
105
- });
106
-
107
- it("normalizes a default-exported suite object with no setup override", async () => {
108
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-bundle-"));
109
- cleanups.push(() => fs.rmSync(tmpDir, { force: true, recursive: true }));
110
-
111
- const sourceFile = path.join(tmpDir, "no-setup.js");
112
- fs.writeFileSync(
113
- sourceFile,
114
- [
115
- "const suite = {",
116
- " options: { vus: 1, iterations: 1 },",
117
- " exec() {",
118
- " return 'ok';",
119
- " },",
120
- "};",
121
- "export default suite;",
122
- "",
123
- ].join("\n")
124
- );
125
-
126
- const bundledFile = await bundleK6File({
127
- productDir: tmpDir,
128
- serviceName: "api",
129
- sourceFile,
130
- });
131
-
132
- const bundled = await import(`${pathToFileURL(bundledFile).href}?v=${Date.now()}`);
133
- expect(typeof bundled.setup).toBe("function");
134
- expect(bundled.setup()).toBeNull();
135
- expect(typeof bundled.default).toBe("function");
136
- expect(bundled.default()).toBe("ok");
137
- });
138
-
139
- it("throws a clear error when the default export is not a suite object", async () => {
140
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-bundle-"));
141
- cleanups.push(() => fs.rmSync(tmpDir, { force: true, recursive: true }));
142
-
143
- const sourceFile = path.join(tmpDir, "legacy-shape.js");
144
- fs.writeFileSync(
145
- sourceFile,
146
- [
147
- "export default function exec() {",
148
- " return 'legacy';",
149
- "}",
150
- "",
151
- ].join("\n")
152
- );
153
-
154
- const bundledFile = await bundleK6File({
155
- productDir: tmpDir,
156
- serviceName: "api",
157
- sourceFile,
158
- });
159
-
160
- await expect(
161
- import(`${pathToFileURL(bundledFile).href}?v=${Date.now()}`)
162
- ).rejects.toThrow(/default-export the suite object/);
163
- });
164
- });
@@ -1,110 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- parseFileTimeoutOption,
4
- parseShardOption,
5
- parseSuiteOption,
6
- parseTypeOption,
7
- parseWorkersOption,
8
- resolveRequestedFiles,
9
- resolveCliSelection,
10
- } from "./args.mjs";
11
-
12
- describe("cli-args", () => {
13
- it("resolves a positional suite type", () => {
14
- expect(
15
- resolveCliSelection({
16
- first: "int",
17
- second: null,
18
- third: null,
19
- })
20
- ).toEqual({
21
- dbAction: null,
22
- lifecycle: null,
23
- positionalType: "int",
24
- });
25
- });
26
-
27
- it("resolves lifecycle commands", () => {
28
- expect(
29
- resolveCliSelection({
30
- first: "status",
31
- second: null,
32
- third: null,
33
- })
34
- ).toEqual({
35
- dbAction: null,
36
- lifecycle: "status",
37
- positionalType: null,
38
- });
39
- });
40
-
41
- it("resolves db subcommands", () => {
42
- expect(
43
- resolveCliSelection({
44
- first: "db",
45
- second: "snapshot",
46
- third: "capture",
47
- })
48
- ).toEqual({
49
- dbAction: "snapshot-capture",
50
- lifecycle: null,
51
- positionalType: null,
52
- });
53
- });
54
-
55
- it("rejects unknown positional arguments", () => {
56
- expect(() =>
57
- resolveCliSelection({
58
- first: "mystery",
59
- second: null,
60
- third: null,
61
- })
62
- ).toThrow('Unknown argument "mystery"');
63
- });
64
-
65
- it("parses types and suite selectors", () => {
66
- expect(parseTypeOption(["e2e,scenario,dal"], "int")).toEqual([
67
- "int",
68
- "e2e",
69
- "scenario",
70
- "dal",
71
- ]);
72
- expect(() => parseTypeOption(["all", "int"])).toThrow("cannot be combined");
73
-
74
- expect(parseSuiteOption(["auth,scenario:journeys,dal:queries"])).toEqual([
75
- { kind: "plain", name: "auth", raw: "auth" },
76
- { kind: "typed", type: "scenario", name: "journeys", raw: "scenario:journeys" },
77
- { kind: "typed", type: "dal", name: "queries", raw: "dal:queries" },
78
- ]);
79
- });
80
-
81
- it("parses and validates execution options", () => {
82
- expect(parseWorkersOption("3")).toBe(3);
83
- expect(parseFileTimeoutOption("45")).toBe(45);
84
- expect(() => parseWorkersOption("0")).toThrow("Invalid --workers value");
85
- expect(() => parseFileTimeoutOption("0")).toThrow(
86
- "Invalid --file-timeout-seconds value"
87
- );
88
- });
89
-
90
- it("parses and validates shards", () => {
91
- expect(parseShardOption("2/5")).toEqual({ index: 2, total: 5 });
92
- expect(parseShardOption(null)).toBeNull();
93
- expect(() => parseShardOption("2-of-5")).toThrow("Invalid --shard value");
94
- expect(() => parseShardOption("3/2")).toThrow("Expected 1 <= i <= n");
95
- });
96
-
97
- it("normalizes requested file paths against the product directory", () => {
98
- expect(
99
- resolveRequestedFiles(
100
- [
101
- "/tmp/product/tests/api/integration/health.int.testkit.ts",
102
- "./tests/api/integration/health.int.testkit.ts",
103
- "product/tests/api/integration/health.int.testkit.ts",
104
- ],
105
- "/tmp/product",
106
- "/tmp"
107
- )
108
- ).toEqual(["tests/api/integration/health.int.testkit.ts"]);
109
- });
110
- });
@@ -1,71 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { findFailureLocation, formatLocation, renderCodeFrame } from "./code-frames.mjs";
6
-
7
- const cleanups = [];
8
-
9
- afterEach(() => {
10
- while (cleanups.length > 0) {
11
- cleanups.pop()();
12
- }
13
- });
14
-
15
- describe("code frame presentation", () => {
16
- it("finds a location from structured failure metadata", () => {
17
- expect(
18
- findFailureLocation({
19
- location: {
20
- path: "/tmp/example.ts",
21
- line: 10,
22
- column: 2,
23
- },
24
- })
25
- ).toEqual({
26
- path: "/tmp/example.ts",
27
- line: 10,
28
- column: 2,
29
- });
30
- });
31
-
32
- it("renders a code frame for a local file", () => {
33
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-code-frame-"));
34
- cleanups.push(() => fs.rmSync(tempDir, { recursive: true, force: true }));
35
- const filePath = path.join(tempDir, "example.ts");
36
- fs.writeFileSync(
37
- filePath,
38
- [
39
- "export function run() {",
40
- " const value = 1;",
41
- " return value + missing;",
42
- "}",
43
- ].join("\n")
44
- );
45
-
46
- const lines = renderCodeFrame(
47
- {
48
- path: filePath,
49
- line: 3,
50
- column: 18,
51
- },
52
- { cwd: tempDir }
53
- );
54
-
55
- expect(lines.join("\n")).toContain("example.ts:3:18");
56
- expect(lines.join("\n")).toContain("return value + missing;");
57
- });
58
-
59
- it("formats locations relative to the cwd", () => {
60
- expect(
61
- formatLocation(
62
- {
63
- path: "/tmp/example.ts",
64
- line: 7,
65
- column: 3,
66
- },
67
- "/tmp"
68
- )
69
- ).toBe("example.ts:7:3");
70
- });
71
- });
@@ -1,192 +0,0 @@
1
- import { Writable } from "stream";
2
- import figures from "figures";
3
- import { describe, expect, it } from "vitest";
4
- import { createRunReporter } from "./run-reporter.mjs";
5
-
6
- function createCapture(columns = 100) {
7
- let stdout = "";
8
- const stream = new Writable({
9
- write(chunk, _encoding, callback) {
10
- stdout += chunk.toString();
11
- callback();
12
- },
13
- });
14
- stream.columns = columns;
15
- return {
16
- stream,
17
- read() {
18
- return stdout;
19
- },
20
- };
21
- }
22
-
23
- describe("run reporter setup output", () => {
24
- it("prints concise high-level setup summaries in compact mode", () => {
25
- const capture = createCapture();
26
- const reporter = createRunReporter({
27
- outputMode: "compact",
28
- stdout: capture.stream,
29
- });
30
-
31
- reporter.setupOperationFinished({
32
- serviceName: "api",
33
- stage: "template",
34
- kind: "database-template",
35
- summary: "template rebuild",
36
- status: "passed",
37
- durationMs: 8_000,
38
- });
39
-
40
- expect(capture.read()).toContain(`${figures.play} RUN SETUP api template rebuild`);
41
- expect(capture.read()).toContain("8s");
42
- });
43
-
44
- it("does not print low-level setup steps in compact mode", () => {
45
- const capture = createCapture();
46
- const reporter = createRunReporter({
47
- outputMode: "compact",
48
- stdout: capture.stream,
49
- });
50
-
51
- reporter.setupOperationFinished({
52
- serviceName: "api",
53
- stage: "template:migrate:api:1",
54
- kind: "setup-step",
55
- summary: "sql-file: db/schema.sql",
56
- status: "passed",
57
- durationMs: 8_000,
58
- });
59
-
60
- expect(capture.read()).toBe("");
61
- });
62
-
63
- it("prints concise setup failures", () => {
64
- const capture = createCapture();
65
- const reporter = createRunReporter({
66
- outputMode: "compact",
67
- stdout: capture.stream,
68
- });
69
-
70
- reporter.setupOperationFinished({
71
- serviceName: "api",
72
- stage: "runtime:prepare",
73
- kind: "runtime-prepare",
74
- summary: "runtime prepare",
75
- status: "failed",
76
- durationMs: 1_200,
77
- error: "Command failed with exit code 1: node scripts/fail-prepare.mjs",
78
- });
79
-
80
- expect(capture.read()).toContain(`${figures.cross} FAIL SETUP api runtime:prepare`);
81
- expect(capture.read()).toContain("Command failed with exit code 1");
82
- });
83
- });
84
-
85
- describe("run reporter task output", () => {
86
- it("prints failure details on lines beneath the failed file result", () => {
87
- const capture = createCapture(88);
88
- const reporter = createRunReporter({
89
- outputMode: "compact",
90
- stdout: capture.stream,
91
- });
92
-
93
- reporter.setTotalFileCount(3);
94
- reporter.taskFinished(
95
- {
96
- serviceName: "api",
97
- type: "integration",
98
- framework: "k6",
99
- file: "__testkit__/health/health.int.testkit.ts",
100
- },
101
- {
102
- status: "failed",
103
- failed: true,
104
- durationMs: 4_000,
105
- error: "Default runtime thresholds failed: checks(rate==1.0)",
106
- failureDetails: [
107
- {
108
- kind: "http-assertion",
109
- title: "status is 200",
110
- message: "GET /health expected 200, got 404",
111
- request: {
112
- method: "GET",
113
- path: "/health",
114
- requestId: "req-1",
115
- },
116
- response: {
117
- status: 404,
118
- bodyPreview: '{"error":"route not found"}',
119
- },
120
- },
121
- ],
122
- }
123
- );
124
-
125
- const lines = capture.read().trimEnd().split("\n");
126
- expect(lines[0]).toContain("[1/3]");
127
- expect(lines[0]).toContain(`${figures.cross} FAIL api int __testkit__/health/health.int.testkit.ts 4s`);
128
- expect(lines[1]).toBe(" GET /health expected 200, got 404");
129
- expect(lines[2]).toContain(' response: {"error":"route not found"}');
130
- expect(lines[3]).toBe(" regression: new");
131
- expect(lines[4]).toBe(" logs: requestId=req-1");
132
- });
133
-
134
- it("renders a compact aggregate summary box", () => {
135
- const capture = createCapture(72);
136
- const reporter = createRunReporter({
137
- outputMode: "compact",
138
- stdout: capture.stream,
139
- });
140
-
141
- reporter.runSummary(
142
- [
143
- {
144
- name: "api",
145
- skipped: false,
146
- failed: true,
147
- suiteCount: 1,
148
- completedSuiteCount: 1,
149
- skippedSuiteCount: 0,
150
- failedSuiteCount: 1,
151
- totalFileCount: 3,
152
- passedFileCount: 2,
153
- failedFileCount: 1,
154
- skippedFileCount: 0,
155
- notRunFileCount: 0,
156
- durationMs: 10_000,
157
- suites: [],
158
- errors: [],
159
- },
160
- ],
161
- 20_000,
162
- {
163
- summary: {
164
- newRegressions: 1,
165
- knownRegressions: 2,
166
- fixedKnownRegressions: 3,
167
- catalogStale: 4,
168
- catalogSyncUnavailable: false,
169
- usedStaleCache: true,
170
- },
171
- }
172
- );
173
-
174
- const output = capture.read();
175
- expect(output).toContain("┌");
176
- expect(output).toContain("│ Result");
177
- expect(output).toContain("FAILED");
178
- expect(output).toContain("│ Passed");
179
- expect(output).toContain("│ Failed");
180
- expect(output).toContain("│ Skipped");
181
- expect(output).toContain("│ Not run");
182
- expect(output).toContain("│ Files");
183
- expect(output).toContain("│ Duration");
184
- expect(output).toContain("│ New regressions");
185
- expect(output).toContain("│ Known regressions");
186
- expect(output).toContain("│ Fixed known");
187
- expect(output).toContain("│ Catalog stale");
188
- expect(output).toContain("│ Catalog sync");
189
- expect(output).not.toContain("Failures:");
190
- expect(output).not.toContain("Runtime Errors:");
191
- });
192
- });
@@ -1,43 +0,0 @@
1
- import { Writable } from "stream";
2
- import { describe, expect, it } from "vitest";
3
- import { renderSummaryBox } from "./summary-box.mjs";
4
-
5
- function createStream(columns) {
6
- const stream = new Writable({
7
- write(_chunk, _encoding, callback) {
8
- callback();
9
- },
10
- });
11
- stream.columns = columns;
12
- return stream;
13
- }
14
-
15
- describe("summary box", () => {
16
- it("renders key/value rows inside a bounded box", () => {
17
- const lines = renderSummaryBox(
18
- [
19
- ["Result", "FAILED"],
20
- ["Passed", "203"],
21
- ["Duration", "8m 56s"],
22
- ],
23
- { stdout: createStream(80) }
24
- );
25
-
26
- expect(lines[0]).toMatch(/^┌/);
27
- expect(lines.at(-1)).toMatch(/^└/);
28
- expect(lines.join("\n")).toContain("Result");
29
- expect(lines.join("\n")).toContain("FAILED");
30
- });
31
-
32
- it("wraps long values instead of widening to content length", () => {
33
- const lines = renderSummaryBox(
34
- [["Catalog sync", "Used stale GitHub cache while catalog validation was unavailable"]],
35
- { stdout: createStream(40) }
36
- );
37
-
38
- expect(lines.length).toBeGreaterThan(4);
39
- expect(lines.join("\n")).toContain("Catalog sync");
40
- expect(lines.join("\n")).toContain("stale");
41
- expect(lines.join("\n")).toContain("cache");
42
- });
43
- });
@@ -1,23 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { getTerminalWidth, renderIndentedBlock, wrapText } from "./terminal-layout.mjs";
3
-
4
- describe("terminal layout", () => {
5
- it("falls back to a default width when stream columns are unavailable", () => {
6
- expect(getTerminalWidth({}, 91)).toBe(91);
7
- });
8
-
9
- it("wraps indented blocks to the provided width", () => {
10
- const lines = renderIndentedBlock("response: abcdefghijklmnopqrstuvwxyz", {
11
- width: 20,
12
- indent: " ",
13
- });
14
-
15
- expect(lines[0].startsWith(" ")).toBe(true);
16
- expect(lines.length).toBeGreaterThan(1);
17
- });
18
-
19
- it("hard-wraps long unbroken values", () => {
20
- const lines = wrapText("abcdefghijklmnopqrstuvwxyz", 8);
21
- expect(lines.length).toBeGreaterThan(1);
22
- });
23
- });
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { normalizeDatabaseConfig } from "./database.mjs";
3
-
4
- describe("config database helpers", () => {
5
- it("normalizes local database config with template defaults", () => {
6
- expect(
7
- normalizeDatabaseConfig(
8
- {
9
- database: {
10
- template: {
11
- migrate: [{ kind: "sql-file", path: "sql/migrate.sql" }],
12
- },
13
- },
14
- },
15
- "web"
16
- )
17
- ).toMatchObject({
18
- provider: "local",
19
- selectedBackend: "local",
20
- reset: true,
21
- template: {
22
- inputs: [],
23
- migrate: [{ kind: "sql-file", path: "sql/migrate.sql" }],
24
- seed: [],
25
- verify: [],
26
- },
27
- });
28
- });
29
- });