@elench/testkit 0.1.83 → 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 (83) hide show
  1. package/lib/cli/agents/providers/codex.mjs +1 -1
  2. package/lib/cli/tui/run-session-app.mjs +1 -1
  3. package/node_modules/@elench/next-analysis/package.json +1 -1
  4. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  5. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  6. package/node_modules/@elench/ts-analysis/package.json +1 -1
  7. package/package.json +7 -6
  8. package/lib/app/configs.test.mjs +0 -34
  9. package/lib/app/typecheck.test.mjs +0 -24
  10. package/lib/bundler/index.test.mjs +0 -164
  11. package/lib/cli/agents/investigation-context.test.mjs +0 -144
  12. package/lib/cli/agents/providers/claude.test.mjs +0 -95
  13. package/lib/cli/agents/providers/codex.test.mjs +0 -93
  14. package/lib/cli/args.test.mjs +0 -110
  15. package/lib/cli/command-helpers.test.mjs +0 -122
  16. package/lib/cli/commands/investigate.test.mjs +0 -83
  17. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  18. package/lib/cli/presentation/events-reporter.test.mjs +0 -73
  19. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  20. package/lib/cli/presentation/summary-box.test.mjs +0 -60
  21. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  22. package/lib/cli/presentation/tree-reporter.test.mjs +0 -166
  23. package/lib/cli/tui/run-session-app.test.mjs +0 -50
  24. package/lib/cli/tui/run-tree-state.test.mjs +0 -324
  25. package/lib/config/database.test.mjs +0 -29
  26. package/lib/config/discovery.test.mjs +0 -276
  27. package/lib/config/env.test.mjs +0 -40
  28. package/lib/config/index.test.mjs +0 -44
  29. package/lib/config/paths.test.mjs +0 -27
  30. package/lib/config/runtime.test.mjs +0 -82
  31. package/lib/config/skip-config.test.mjs +0 -63
  32. package/lib/config-api/index.test.mjs +0 -398
  33. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  34. package/lib/coverage/backend-discovery.test.mjs +0 -61
  35. package/lib/coverage/evidence.test.mjs +0 -87
  36. package/lib/coverage/index.test.mjs +0 -715
  37. package/lib/coverage/routing.test.mjs +0 -36
  38. package/lib/coverage/shared.test.mjs +0 -72
  39. package/lib/database/fingerprint.test.mjs +0 -99
  40. package/lib/database/index.test.mjs +0 -95
  41. package/lib/database/naming.test.mjs +0 -39
  42. package/lib/database/state.test.mjs +0 -66
  43. package/lib/database/template-steps.test.mjs +0 -43
  44. package/lib/discovery/file-metadata.test.mjs +0 -51
  45. package/lib/discovery/index.test.mjs +0 -182
  46. package/lib/discovery/path-policy.test.mjs +0 -65
  47. package/lib/drizzle/index.test.mjs +0 -33
  48. package/lib/env/index.test.mjs +0 -82
  49. package/lib/history/index.test.mjs +0 -115
  50. package/lib/package.test.mjs +0 -59
  51. package/lib/playwright/index.test.mjs +0 -43
  52. package/lib/regressions/github.test.mjs +0 -324
  53. package/lib/regressions/index.test.mjs +0 -187
  54. package/lib/reporters/playwright.test.mjs +0 -167
  55. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  56. package/lib/runner/execution-config.test.mjs +0 -67
  57. package/lib/runner/failure-details.test.mjs +0 -114
  58. package/lib/runner/formatting.test.mjs +0 -205
  59. package/lib/runner/metadata.test.mjs +0 -52
  60. package/lib/runner/planning.test.mjs +0 -371
  61. package/lib/runner/playwright-config.test.mjs +0 -78
  62. package/lib/runner/processes.test.mjs +0 -21
  63. package/lib/runner/regressions.test.mjs +0 -168
  64. package/lib/runner/reporting.test.mjs +0 -310
  65. package/lib/runner/results.test.mjs +0 -376
  66. package/lib/runner/runtime-manager.test.mjs +0 -252
  67. package/lib/runner/runtime-preparation.test.mjs +0 -141
  68. package/lib/runner/selection.test.mjs +0 -24
  69. package/lib/runner/setup-operations.test.mjs +0 -94
  70. package/lib/runner/state.test.mjs +0 -62
  71. package/lib/runner/suite-selection.test.mjs +0 -49
  72. package/lib/runner/template.test.mjs +0 -272
  73. package/lib/runtime-src/k6/http-checks.test.mjs +0 -120
  74. package/lib/runtime-src/k6/http.test.mjs +0 -205
  75. package/lib/runtime-src/shared/http-parsing.test.mjs +0 -69
  76. package/lib/shared/build-config.test.mjs +0 -132
  77. package/lib/shared/configured-steps.test.mjs +0 -102
  78. package/lib/shared/execution-schema.test.mjs +0 -26
  79. package/lib/shared/file-timeout.test.mjs +0 -64
  80. package/lib/shared/test-context.test.mjs +0 -43
  81. package/lib/timing/index.test.mjs +0 -64
  82. package/lib/toolchains/index.test.mjs +0 -168
  83. package/lib/vitest/index.test.mjs +0 -20
@@ -1,715 +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 { buildCoverageGraph, buildServiceCoverageContext } from "./index.mjs";
6
-
7
- const cleanups = [];
8
-
9
- afterEach(() => {
10
- while (cleanups.length > 0) {
11
- cleanups.pop()();
12
- }
13
- });
14
-
15
- describe("coverage graph builder", () => {
16
- it("builds a Next page -> surface -> action -> request -> API route -> backend/data capability graph", () => {
17
- const productDir = createProduct();
18
- writeFile(
19
- productDir,
20
- "testkit.config.ts",
21
- `
22
- import { app, defineConfig } from "@elench/testkit/config";
23
-
24
- export default defineConfig({
25
- services: {
26
- web: app.next({
27
- cwd: ".",
28
- start: "node server.js",
29
- mode: "start",
30
- build: null,
31
- baseUrl: "http://127.0.0.1:3000",
32
- readyUrl: "http://127.0.0.1:3000"
33
- })
34
- }
35
- });
36
- `
37
- );
38
- writeFile(
39
- productDir,
40
- "src/app/campaigns/page.tsx",
41
- `
42
- "use client";
43
- import { getJson, postJson } from "@/client/http/http";
44
-
45
- async function refreshCampaigns() {
46
- await getJson("/api/campaigns");
47
- }
48
-
49
- async function createCampaign() {
50
- await postJson("/api/campaigns");
51
- }
52
-
53
- export default function CampaignsPage() {
54
- return (
55
- <main>
56
- <button data-testid="campaign-refresh-button" onClick={() => refreshCampaigns()}>
57
- Refresh campaigns
58
- </button>
59
- <button data-testid="campaign-create-button" onClick={createCampaign}>
60
- Create campaign
61
- </button>
62
- </main>
63
- );
64
- }
65
- `
66
- );
67
- writeFile(
68
- productDir,
69
- "src/client/http/http.ts",
70
- `export function getJson() { return null; }\nexport function postJson() { return null; }`
71
- );
72
- writeFile(
73
- productDir,
74
- "src/app/api/campaigns/route.ts",
75
- `
76
- import { listCampaigns, createCampaign } from "@/backend/server/campaigns";
77
-
78
- export async function GET() {
79
- return listCampaigns();
80
- }
81
-
82
- export async function POST() {
83
- return createCampaign();
84
- }
85
- `
86
- );
87
- writeFile(
88
- productDir,
89
- "src/backend/server/campaigns/index.ts",
90
- `
91
- import { insertCampaignRow, selectCampaignRows } from "@/backend/data/campaigns";
92
-
93
- export async function listCampaigns() {
94
- return selectCampaignRows();
95
- }
96
-
97
- export async function createCampaign() {
98
- return insertCampaignRow();
99
- }
100
- `
101
- );
102
- writeFile(
103
- productDir,
104
- "src/backend/data/campaigns/index.ts",
105
- `export async function selectCampaignRows() {}\nexport async function insertCampaignRow() {}`
106
- );
107
-
108
- const context = buildServiceCoverageContext(productDir, "web", {
109
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
110
- });
111
-
112
- expect(context.pageByRoute.get("/campaigns").node).toMatchObject({
113
- kind: "page_view",
114
- route: "/campaigns",
115
- filePath: "src/app/campaigns/page.tsx",
116
- });
117
- expect(context.pageByRoute.get("/campaigns").surfacesByTargetValue.get("campaign-refresh-button")).toMatchObject({
118
- kind: "ui_surface",
119
- label: "Refresh campaigns",
120
- });
121
- expect(context.apiRouteByKey.get("GET:/api/campaigns").node).toMatchObject({
122
- kind: "api_route",
123
- route: "/campaigns",
124
- method: "GET",
125
- path: "/api/campaigns",
126
- filePath: "src/app/api/campaigns/route.ts",
127
- });
128
- expect(context.graph.edges).toEqual(
129
- expect.arrayContaining([
130
- expect.objectContaining({
131
- kind: "contains",
132
- from: "page_view:web:/campaigns",
133
- to: "ui_surface:web:/campaigns:campaign-refresh-button",
134
- }),
135
- expect.objectContaining({
136
- kind: "triggers",
137
- from: "ui_surface:web:/campaigns:campaign-refresh-button",
138
- to: expect.stringContaining("ui_action:web:/campaigns"),
139
- }),
140
- expect.objectContaining({
141
- kind: "requests",
142
- from: expect.stringContaining("ui_action:web:/campaigns"),
143
- }),
144
- expect.objectContaining({
145
- kind: "handles",
146
- to: "api_route:web:GET:/api/campaigns",
147
- }),
148
- expect.objectContaining({
149
- kind: "delegates_to",
150
- from: "api_route:web:GET:/api/campaigns",
151
- }),
152
- expect.objectContaining({
153
- kind: "delegates_to",
154
- from: "server_capability:src/backend/server/campaigns#createCampaign",
155
- to: "data_capability:src/backend/data/campaigns#insertCampaignRow",
156
- }),
157
- ])
158
- );
159
- });
160
-
161
- it("discovers server action links from pages", () => {
162
- const productDir = createProduct();
163
- writeFile(
164
- productDir,
165
- "src/app/settings/page.tsx",
166
- `
167
- import { saveSettings } from "./actions";
168
-
169
- export default function SettingsPage() {
170
- return (
171
- <form action={saveSettings}>
172
- <button data-testid="settings-save-button" type="submit">
173
- Save settings
174
- </button>
175
- </form>
176
- );
177
- }
178
- `
179
- );
180
- writeFile(
181
- productDir,
182
- "src/app/settings/actions.ts",
183
- `
184
- "use server";
185
- import { updateSettings } from "@/backend/server/settings";
186
-
187
- export async function saveSettings() {
188
- return updateSettings();
189
- }
190
- `
191
- );
192
- writeFile(productDir, "src/backend/server/settings.ts", `export async function updateSettings() {}`);
193
-
194
- const context = buildServiceCoverageContext(productDir, "web", {
195
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
196
- });
197
-
198
- expect(context.serverActionByExportKey.get("src/app/settings/actions.ts#saveSettings").node).toMatchObject({
199
- kind: "server_action",
200
- label: "saveSettings",
201
- });
202
- expect(context.graph.edges).toEqual(
203
- expect.arrayContaining([
204
- expect.objectContaining({
205
- kind: "triggers",
206
- from: expect.stringContaining("ui_surface:web:/settings"),
207
- to: "ui_action:web:/settings:saveSettings",
208
- }),
209
- expect.objectContaining({
210
- kind: "triggers",
211
- from: "ui_action:web:/settings:saveSettings",
212
- to: "server_action:web:src/app/settings/actions.ts#saveSettings",
213
- }),
214
- expect.objectContaining({
215
- kind: "delegates_to",
216
- from: "server_action:web:src/app/settings/actions.ts#saveSettings",
217
- }),
218
- ])
219
- );
220
- });
221
-
222
- it("attaches convention-based test evidence to page, surface, route, and data nodes", () => {
223
- const productDir = createProduct();
224
- writeFile(
225
- productDir,
226
- "src/app/campaigns/page.tsx",
227
- `
228
- "use client";
229
- import { getJson } from "@/client/http/http";
230
-
231
- async function loadCampaigns() {
232
- await getJson("/api/campaigns");
233
- }
234
-
235
- export default function CampaignsPage() {
236
- return <button data-testid="campaign-save-button" onClick={loadCampaigns}>Save campaign</button>;
237
- }
238
- `
239
- );
240
- writeFile(productDir, "src/client/http/http.ts", `export async function getJson() { return null; }`);
241
- writeFile(
242
- productDir,
243
- "src/app/api/campaigns/route.ts",
244
- `
245
- import { listCampaigns } from "@/backend/server/campaigns";
246
-
247
- export async function GET() {
248
- return listCampaigns();
249
- }
250
- `
251
- );
252
- writeFile(
253
- productDir,
254
- "src/backend/server/campaigns/index.ts",
255
- `
256
- import { saveCampaignRow } from "@/backend/data/campaigns";
257
-
258
- export async function listCampaigns() {
259
- return saveCampaignRow();
260
- }
261
- `
262
- );
263
- writeFile(productDir, "src/backend/data/campaigns/index.ts", `export async function saveCampaignRow() {}`);
264
- writeFile(
265
- productDir,
266
- "src/app/campaigns/__testkit__/campaigns.pw.testkit.ts",
267
- `
268
- import { expect, test } from "@playwright/test";
269
-
270
- test("campaigns route", async ({ page }) => {
271
- await page.goto("/campaigns");
272
- await expect(page.getByTestId("campaign-save-button")).toBeVisible();
273
- });
274
- `
275
- );
276
- writeFile(
277
- productDir,
278
- "src/app/api/campaigns/__testkit__/campaigns.int.testkit.ts",
279
- `
280
- import { defineHttpSuite } from "@elench/testkit";
281
-
282
- const suite = defineHttpSuite(({ rawReq }) => {
283
- rawReq("GET", "/api/campaigns");
284
- });
285
-
286
- export default suite;
287
- `
288
- );
289
- writeFile(
290
- productDir,
291
- "src/backend/data/campaigns/__testkit__/campaigns.dal.testkit.ts",
292
- `
293
- import { defineDalSuite } from "@elench/testkit";
294
-
295
- const suite = defineDalSuite(({ db }) => {
296
- db.query("SELECT 1 AS value");
297
- });
298
-
299
- export default suite;
300
- `
301
- );
302
-
303
- const graph = buildCoverageGraph({
304
- productDir,
305
- services: {
306
- web: {
307
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
308
- },
309
- },
310
- discoveryFiles: [
311
- {
312
- serviceName: "web",
313
- type: "e2e",
314
- framework: "playwright",
315
- suiteName: "campaigns",
316
- filePath: "src/app/campaigns/__testkit__/campaigns.pw.testkit.ts",
317
- },
318
- {
319
- serviceName: "web",
320
- type: "integration",
321
- framework: "k6",
322
- suiteName: "campaigns",
323
- filePath: "src/app/api/campaigns/__testkit__/campaigns.int.testkit.ts",
324
- },
325
- {
326
- serviceName: "web",
327
- type: "dal",
328
- framework: "k6",
329
- suiteName: "campaigns",
330
- filePath: "src/backend/data/campaigns/__testkit__/campaigns.dal.testkit.ts",
331
- },
332
- ],
333
- });
334
-
335
- expect(graph.evidence).toEqual(
336
- expect.arrayContaining([
337
- expect.objectContaining({
338
- testFilePath: "src/app/campaigns/__testkit__/campaigns.pw.testkit.ts",
339
- coveredNodeIds: expect.arrayContaining([
340
- "page_view:web:/campaigns",
341
- "ui_surface:web:/campaigns:campaign-save-button",
342
- ]),
343
- details: expect.objectContaining({
344
- route: "/campaigns",
345
- targets: [
346
- expect.objectContaining({
347
- kind: "testId",
348
- value: "campaign-save-button",
349
- }),
350
- ],
351
- }),
352
- }),
353
- expect.objectContaining({
354
- testFilePath: "src/app/api/campaigns/__testkit__/campaigns.int.testkit.ts",
355
- coveredNodeIds: ["api_route:web:GET:/api/campaigns"],
356
- }),
357
- expect.objectContaining({
358
- testFilePath: "src/backend/data/campaigns/__testkit__/campaigns.dal.testkit.ts",
359
- coveredNodeIds: expect.arrayContaining([
360
- expect.stringContaining("data_capability:src/backend/data/campaigns"),
361
- ]),
362
- }),
363
- ])
364
- );
365
- });
366
-
367
- it("normalizes dynamic routes", () => {
368
- const productDir = createProduct();
369
- writeFile(productDir, "src/app/projects/[projectId]/page.tsx", `export default function ProjectPage() { return null; }`);
370
- writeFile(productDir, "src/app/api/projects/[projectId]/route.ts", `export async function GET() { return Response.json({ ok: true }); }`);
371
-
372
- const context = buildServiceCoverageContext(productDir, "web", {
373
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
374
- });
375
-
376
- expect(context.pageByRoute.get("/projects/[projectId]").node).toBeTruthy();
377
- expect(context.apiRouteByKey.get("GET:/api/projects/[projectId]").node).toBeTruthy();
378
- });
379
-
380
- it("resolves root-level PW test via page.goto() content extraction", () => {
381
- const productDir = createProduct();
382
- writeFile(
383
- productDir,
384
- "src/app/dashboard/page.tsx",
385
- `export default function DashboardPage() { return <button data-testid="dash-header">Dashboard</button>; }`
386
- );
387
- writeFile(
388
- productDir,
389
- "__testkit__/dashboard.pw.testkit.ts",
390
- `
391
- import { test, expect } from "@playwright/test";
392
- test("dashboard loads", async ({ page }) => {
393
- await page.goto("/dashboard");
394
- await expect(page.getByTestId("dash-header")).toBeVisible();
395
- });
396
- `
397
- );
398
-
399
- const graph = buildCoverageGraph({
400
- productDir,
401
- services: {
402
- web: {
403
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
404
- },
405
- },
406
- discoveryFiles: [
407
- {
408
- serviceName: "web",
409
- type: "e2e",
410
- framework: "playwright",
411
- suiteName: "dashboard",
412
- filePath: "__testkit__/dashboard.pw.testkit.ts",
413
- },
414
- ],
415
- });
416
-
417
- const evidence = graph.evidence.find((e) => e.testFilePath === "__testkit__/dashboard.pw.testkit.ts");
418
- expect(evidence.coveredNodeIds).toEqual(
419
- expect.arrayContaining([
420
- "page_view:web:/dashboard",
421
- "ui_surface:web:/dashboard:dash-header",
422
- ])
423
- );
424
- });
425
-
426
- it("resolves root-level e2e test via rawReq content extraction", () => {
427
- const productDir = createProduct();
428
- writeFile(
429
- productDir,
430
- "src/app/api/projects/route.ts",
431
- `export async function GET() { return Response.json({ ok: true }); }`
432
- );
433
- writeFile(
434
- productDir,
435
- "__testkit__/projects.e2e.testkit.ts",
436
- `
437
- import { defineHttpSuite } from "@elench/testkit";
438
- export default defineHttpSuite(({ rawReq }) => {
439
- rawReq("GET", "/api/projects");
440
- });
441
- `
442
- );
443
-
444
- const graph = buildCoverageGraph({
445
- productDir,
446
- services: {
447
- web: {
448
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
449
- },
450
- },
451
- discoveryFiles: [
452
- {
453
- serviceName: "web",
454
- type: "e2e",
455
- framework: "k6",
456
- suiteName: "projects",
457
- filePath: "__testkit__/projects.e2e.testkit.ts",
458
- },
459
- ],
460
- });
461
-
462
- const evidence = graph.evidence.find((e) => e.testFilePath === "__testkit__/projects.e2e.testkit.ts");
463
- expect(evidence.coveredNodeIds).toEqual(["api_route:web:GET:/api/projects"]);
464
- });
465
-
466
- it("resolves scenario test via rawReq content extraction", () => {
467
- const productDir = createProduct();
468
- writeFile(
469
- productDir,
470
- "src/app/api/campaigns/route.ts",
471
- `export async function POST() { return Response.json({ ok: true }); }`
472
- );
473
- writeFile(
474
- productDir,
475
- "__testkit__/campaign-flow.scenario.testkit.ts",
476
- `
477
- import { defineHttpSuite } from "@elench/testkit";
478
- export default defineHttpSuite(({ rawReq }) => {
479
- rawReq("POST", "/api/campaigns");
480
- });
481
- `
482
- );
483
-
484
- const graph = buildCoverageGraph({
485
- productDir,
486
- services: {
487
- web: {
488
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
489
- },
490
- },
491
- discoveryFiles: [
492
- {
493
- serviceName: "web",
494
- type: "scenario",
495
- framework: "k6",
496
- suiteName: "campaign-flow",
497
- filePath: "__testkit__/campaign-flow.scenario.testkit.ts",
498
- },
499
- ],
500
- });
501
-
502
- const evidence = graph.evidence.find((e) => e.testFilePath === "__testkit__/campaign-flow.scenario.testkit.ts");
503
- expect(evidence.coveredNodeIds).toEqual(["api_route:web:POST:/api/campaigns"]);
504
- });
505
-
506
- it("builds route-tree coverage from ancestor layouts, provider effects, local handlers, and dynamic request templates", () => {
507
- const productDir = createProduct();
508
- writeFile(
509
- productDir,
510
- "src/app/(dashboard)/layout.tsx",
511
- `
512
- import { ProjectProvider } from "@/components/project-context";
513
-
514
- export default function DashboardLayout({ children }) {
515
- return <ProjectProvider>{children}</ProjectProvider>;
516
- }
517
- `
518
- );
519
- writeFile(
520
- productDir,
521
- "src/components/project-context.tsx",
522
- `
523
- "use client";
524
- import { useEffect } from "react";
525
-
526
- export function ProjectProvider({ children }) {
527
- const fetchProjects = async () => {
528
- await fetch("/api/projects");
529
- };
530
-
531
- useEffect(() => {
532
- fetchProjects();
533
- }, []);
534
-
535
- return <section>{children}</section>;
536
- }
537
- `
538
- );
539
- writeFile(
540
- productDir,
541
- "src/app/(dashboard)/projects/page.tsx",
542
- `
543
- "use client";
544
- import { useState } from "react";
545
-
546
- export default function ProjectsPage() {
547
- const [name, setName] = useState("");
548
-
549
- const handleCreate = async (event) => {
550
- event.preventDefault();
551
- await fetch("/api/projects", { method: "POST" });
552
- setName("");
553
- };
554
-
555
- return (
556
- <form onSubmit={handleCreate}>
557
- <input value={name} onChange={(event) => setName(event.target.value)} />
558
- <button data-testid="project-create-button" type="submit">Create Project</button>
559
- </form>
560
- );
561
- }
562
- `
563
- );
564
- writeFile(
565
- productDir,
566
- "src/app/(dashboard)/events/page.tsx",
567
- `
568
- "use client";
569
- import { useEffect } from "react";
570
-
571
- export default function EventsPage() {
572
- const activeProject = { id: "project-1" };
573
-
574
- useEffect(() => {
575
- fetch(\`/api/projects/\${activeProject.id}/events?limit=100\`);
576
- }, [activeProject.id]);
577
-
578
- return <div>Events</div>;
579
- }
580
- `
581
- );
582
- writeFile(
583
- productDir,
584
- "src/app/api/projects/route.ts",
585
- `
586
- export async function GET() { return Response.json([]); }
587
- export async function POST() { return Response.json({}, { status: 201 }); }
588
- `
589
- );
590
- writeFile(
591
- productDir,
592
- "src/app/api/projects/[projectId]/events/route.ts",
593
- `
594
- export async function GET() { return Response.json([]); }
595
- `
596
- );
597
-
598
- const context = buildServiceCoverageContext(productDir, "web", {
599
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
600
- });
601
-
602
- expect(context.graph.edges).toEqual(
603
- expect.arrayContaining([
604
- expect.objectContaining({
605
- kind: "requests",
606
- from: "page_view:web:/projects",
607
- to: expect.stringContaining("client_request:web:src/components/project-context.tsx:page:/projects"),
608
- }),
609
- expect.objectContaining({
610
- kind: "handles",
611
- from: expect.stringContaining("client_request:web:src/components/project-context.tsx:page:/projects"),
612
- to: "api_route:web:GET:/api/projects",
613
- }),
614
- expect.objectContaining({
615
- kind: "triggers",
616
- from: expect.stringContaining("ui_surface:web:/projects"),
617
- to: "ui_action:web:/projects:handleCreate",
618
- }),
619
- expect.objectContaining({
620
- kind: "requests",
621
- from: "ui_action:web:/projects:handleCreate",
622
- to: expect.stringContaining("client_request:web:src/app/(dashboard)/projects/page.tsx"),
623
- }),
624
- expect.objectContaining({
625
- kind: "handles",
626
- to: "api_route:web:POST:/api/projects",
627
- }),
628
- expect.objectContaining({
629
- kind: "requests",
630
- from: "page_view:web:/events",
631
- to: expect.stringContaining("client_request:web:src/components/project-context.tsx:page:/events"),
632
- }),
633
- expect.objectContaining({
634
- kind: "handles",
635
- to: "api_route:web:GET:/api/projects/[projectId]/events",
636
- }),
637
- ])
638
- );
639
- });
640
-
641
- it("emits zero-coverage-inferred diagnostic when no patterns match", () => {
642
- const productDir = createProduct();
643
- writeFile(productDir, "src/app/page.tsx", `export default function HomePage() { return null; }`);
644
- writeFile(
645
- productDir,
646
- "__testkit__/misc.e2e.testkit.ts",
647
- `// no recognizable patterns here\nconsole.log("hello");`
648
- );
649
-
650
- const graph = buildCoverageGraph({
651
- productDir,
652
- services: {
653
- web: {
654
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
655
- },
656
- },
657
- discoveryFiles: [
658
- {
659
- serviceName: "web",
660
- type: "e2e",
661
- framework: "k6",
662
- suiteName: "misc",
663
- filePath: "__testkit__/misc.e2e.testkit.ts",
664
- },
665
- ],
666
- });
667
-
668
- expect(graph.diagnostics).toEqual(
669
- expect.arrayContaining([
670
- expect.objectContaining({
671
- level: "info",
672
- code: "zero-coverage-inferred",
673
- filePath: "__testkit__/misc.e2e.testkit.ts",
674
- service: "web",
675
- }),
676
- ])
677
- );
678
-
679
- const evidence = graph.evidence.find((e) => e.testFilePath === "__testkit__/misc.e2e.testkit.ts");
680
- expect(evidence.coveredNodeIds).toEqual([expect.stringContaining("test_file:web:")]);
681
- });
682
-
683
- it("does not exclude app/coverage source routes while still allowing top-level coverage output to be ignored", () => {
684
- const productDir = createProduct();
685
- writeFile(productDir, "app/coverage/page.tsx", `export default function CoveragePage() { return null; }`);
686
- writeFile(productDir, "coverage/page.tsx", `export default function OutputPage() { return null; }`);
687
-
688
- const context = buildServiceCoverageContext(
689
- productDir,
690
- "web",
691
- {
692
- local: { cwd: ".", start: "node server.js", baseUrl: "http://127.0.0.1:3000", readyUrl: "http://127.0.0.1:3000" },
693
- discovery: { roots: ["app"] },
694
- },
695
- { exclude: ["coverage"] }
696
- );
697
-
698
- expect(context.pageByRoute.get("/coverage").node).toMatchObject({
699
- filePath: "app/coverage/page.tsx",
700
- });
701
- expect(context.graph.nodes.some((node) => node.filePath === "coverage/page.tsx")).toBe(false);
702
- });
703
- });
704
-
705
- function createProduct() {
706
- const productDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-coverage-"));
707
- cleanups.push(() => fs.rmSync(productDir, { recursive: true, force: true }));
708
- return productDir;
709
- }
710
-
711
- function writeFile(productDir, relativePath, content = "export {};\n") {
712
- const absolutePath = path.join(productDir, relativePath);
713
- fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
714
- fs.writeFileSync(absolutePath, content);
715
- }