@elench/testkit 0.1.62 → 0.1.63
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/lib/coverage/evidence.mjs +14 -3
- package/lib/coverage/evidence.test.mjs +3 -2
- package/lib/coverage/graph-builder.mjs +4 -3
- package/lib/coverage/index.test.mjs +135 -0
- package/lib/coverage/next-discovery.mjs +36 -5
- package/lib/coverage/next-static-analysis.mjs +420 -136
- package/lib/coverage/shared.mjs +67 -0
- package/lib/coverage/shared.test.mjs +33 -0
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-bridge/src/index.mjs +34 -2
- package/node_modules/@elench/testkit-bridge/src/index.test.mjs +204 -17
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/package.json +3 -3
package/lib/coverage/shared.mjs
CHANGED
|
@@ -9,6 +9,7 @@ export const HTTP_WRAPPER_METHODS = {
|
|
|
9
9
|
patchJson: "PATCH",
|
|
10
10
|
deleteJson: "DELETE",
|
|
11
11
|
};
|
|
12
|
+
export const DYNAMIC_SEGMENT_TOKEN = "__TESTKIT_DYNAMIC_SEGMENT__";
|
|
12
13
|
|
|
13
14
|
export function createEmptyGraph() {
|
|
14
15
|
return {
|
|
@@ -196,3 +197,69 @@ export function dedupeDataImports(entries) {
|
|
|
196
197
|
return true;
|
|
197
198
|
});
|
|
198
199
|
}
|
|
200
|
+
|
|
201
|
+
export function findMatchingApiRouteEntry(method, candidatePath, routeEntries = []) {
|
|
202
|
+
const normalizedMethod = String(method || "").trim().toUpperCase();
|
|
203
|
+
const normalizedCandidate = normalizeRequestPathCandidate(candidatePath);
|
|
204
|
+
if (!normalizedMethod || !normalizedCandidate) return null;
|
|
205
|
+
|
|
206
|
+
return [...routeEntries]
|
|
207
|
+
.filter((entry) => entry.method === normalizedMethod)
|
|
208
|
+
.sort(compareRouteSpecificity)
|
|
209
|
+
.find((entry) => routePatternMatchesCandidate(entry.requestPath, normalizedCandidate)) || null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function findMatchingRouteValue(candidateRoute, routeValues = []) {
|
|
213
|
+
const normalizedCandidate = normalizeRequestPathCandidate(candidateRoute);
|
|
214
|
+
if (!normalizedCandidate) return null;
|
|
215
|
+
|
|
216
|
+
return [...routeValues]
|
|
217
|
+
.sort((left, right) => compareRouteSpecificity({ requestPath: left }, { requestPath: right }))
|
|
218
|
+
.find((routeValue) => routePatternMatchesCandidate(routeValue, normalizedCandidate)) || null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function normalizeRequestPathCandidate(value) {
|
|
222
|
+
if (typeof value !== "string") return null;
|
|
223
|
+
const [withoutHash] = value.split("#");
|
|
224
|
+
const [withoutQuery] = withoutHash.split("?");
|
|
225
|
+
return normalizeRoute(withoutQuery || "/");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function routePatternMatchesCandidate(patternPath, candidatePath) {
|
|
229
|
+
const normalizedPattern = normalizeRequestPathCandidate(patternPath);
|
|
230
|
+
const normalizedCandidate = normalizeRequestPathCandidate(candidatePath);
|
|
231
|
+
if (!normalizedPattern || !normalizedCandidate) return false;
|
|
232
|
+
|
|
233
|
+
const patternSegments = splitRouteSegments(normalizedPattern);
|
|
234
|
+
const candidateSegments = splitRouteSegments(normalizedCandidate);
|
|
235
|
+
if (patternSegments.length !== candidateSegments.length) return false;
|
|
236
|
+
|
|
237
|
+
for (let index = 0; index < patternSegments.length; index += 1) {
|
|
238
|
+
const patternSegment = patternSegments[index];
|
|
239
|
+
const candidateSegment = candidateSegments[index];
|
|
240
|
+
if (patternSegment === candidateSegment) continue;
|
|
241
|
+
if (isDynamicRouteSegment(patternSegment) && candidateSegment.length > 0) continue;
|
|
242
|
+
if (candidateSegment === DYNAMIC_SEGMENT_TOKEN && isDynamicRouteSegment(patternSegment)) continue;
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function isDynamicRouteSegment(segment) {
|
|
250
|
+
return /^\[[^/]+\]$/u.test(segment) || /^\[\.\.\.[^/]+\]$/u.test(segment) || /^\[\[\.\.\.[^/]+\]\]$/u.test(segment);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function splitRouteSegments(value) {
|
|
254
|
+
if (value === "/") return [];
|
|
255
|
+
return value.split("/").filter(Boolean);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function compareRouteSpecificity(left, right) {
|
|
259
|
+
const leftSegments = splitRouteSegments(left.requestPath || "");
|
|
260
|
+
const rightSegments = splitRouteSegments(right.requestPath || "");
|
|
261
|
+
const leftStaticSegments = leftSegments.filter((segment) => !isDynamicRouteSegment(segment)).length;
|
|
262
|
+
const rightStaticSegments = rightSegments.filter((segment) => !isDynamicRouteSegment(segment)).length;
|
|
263
|
+
if (leftStaticSegments !== rightStaticSegments) return rightStaticSegments - leftStaticSegments;
|
|
264
|
+
return rightSegments.length - leftSegments.length;
|
|
265
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
|
+
DYNAMIC_SEGMENT_TOKEN,
|
|
3
4
|
dedupeTargets,
|
|
5
|
+
findMatchingApiRouteEntry,
|
|
6
|
+
findMatchingRouteValue,
|
|
4
7
|
modulePathKey,
|
|
5
8
|
pageLabelFromRoute,
|
|
9
|
+
routePatternMatchesCandidate,
|
|
6
10
|
toApiRequestPath,
|
|
7
11
|
toSelectionType,
|
|
8
12
|
} from "./shared.mjs";
|
|
@@ -36,4 +40,33 @@ describe("coverage shared helpers", () => {
|
|
|
36
40
|
expect(toSelectionType("integration", "http")).toBe("int");
|
|
37
41
|
expect(toSelectionType("ui", "playwright")).toBe("pw");
|
|
38
42
|
});
|
|
43
|
+
|
|
44
|
+
it("matches dynamic API route patterns against concrete and template-like request paths", () => {
|
|
45
|
+
expect(routePatternMatchesCandidate("/api/projects/[projectId]/overview", "/api/projects/123/overview")).toBe(true);
|
|
46
|
+
expect(
|
|
47
|
+
routePatternMatchesCandidate(
|
|
48
|
+
"/api/projects/[projectId]/events",
|
|
49
|
+
`/api/projects/${DYNAMIC_SEGMENT_TOKEN}/events`
|
|
50
|
+
)
|
|
51
|
+
).toBe(true);
|
|
52
|
+
expect(routePatternMatchesCandidate("/api/projects/[projectId]/events", "/api/projects/123/stats")).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("prefers the most specific matching API route entry", () => {
|
|
56
|
+
const routeEntry = findMatchingApiRouteEntry("GET", "/api/projects/123/events", [
|
|
57
|
+
{ method: "GET", requestPath: "/api/projects/[projectId]" },
|
|
58
|
+
{ method: "GET", requestPath: "/api/projects/[projectId]/events" },
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
expect(routeEntry).toEqual({
|
|
62
|
+
method: "GET",
|
|
63
|
+
requestPath: "/api/projects/[projectId]/events",
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("matches dynamic page routes against discovered route patterns", () => {
|
|
68
|
+
expect(
|
|
69
|
+
findMatchingRouteValue(`/projects/${DYNAMIC_SEGMENT_TOKEN}`, ["/projects/[projectId]", "/projects"])
|
|
70
|
+
).toBe("/projects/[projectId]");
|
|
71
|
+
});
|
|
39
72
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"src/"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@elench/testkit-protocol": "0.1.
|
|
14
|
+
"@elench/testkit-protocol": "0.1.63"
|
|
15
15
|
},
|
|
16
16
|
"private": false
|
|
17
17
|
}
|
|
@@ -269,6 +269,11 @@ function buildGraphProjection(context, page, matchedServiceName) {
|
|
|
269
269
|
.filter((node) => node?.kind === "ui_surface");
|
|
270
270
|
const pageReachableNodeIds = collectReachableNodeIds(pageNode.id, outgoing);
|
|
271
271
|
|
|
272
|
+
const surfaceReachableEntries = surfaceNodes.map((surfaceNode) => ({
|
|
273
|
+
surfaceNode,
|
|
274
|
+
reachableNodeIds: collectReachableNodeIds(surfaceNode.id, outgoing),
|
|
275
|
+
}));
|
|
276
|
+
|
|
272
277
|
if (surfaceNodes.length === 0) {
|
|
273
278
|
const relevantEvidence = evidence.filter((entry) => intersects(entry.coveredNodeIds || [], pageReachableNodeIds));
|
|
274
279
|
return buildPageLevelProjection({
|
|
@@ -285,8 +290,9 @@ function buildGraphProjection(context, page, matchedServiceName) {
|
|
|
285
290
|
const coverage = [];
|
|
286
291
|
const failures = [];
|
|
287
292
|
|
|
288
|
-
for (const surfaceNode of
|
|
289
|
-
|
|
293
|
+
for (const { surfaceNode, reachableNodeIds: surfaceReachableNodeIds } of surfaceReachableEntries.sort((left, right) =>
|
|
294
|
+
left.surfaceNode.id.localeCompare(right.surfaceNode.id)
|
|
295
|
+
)) {
|
|
290
296
|
const relevantEvidence = evidence.filter((entry) => intersects(entry.coveredNodeIds || [], surfaceReachableNodeIds));
|
|
291
297
|
const supportingTests = relevantEvidence
|
|
292
298
|
.map((entry) => buildSupportingTestRef(entry, discoveryByFile, context.runArtifact))
|
|
@@ -342,6 +348,22 @@ function buildGraphProjection(context, page, matchedServiceName) {
|
|
|
342
348
|
});
|
|
343
349
|
}
|
|
344
350
|
|
|
351
|
+
const routeLevelNodeIds = collectRouteLevelNodeIds(pageReachableNodeIds, surfaceReachableEntries);
|
|
352
|
+
if (routeLevelNodeIds.size > 0) {
|
|
353
|
+
const routeRelevantEvidence = evidence.filter((entry) => intersects(entry.coveredNodeIds || [], routeLevelNodeIds));
|
|
354
|
+
const pageProjection = buildPageLevelProjection({
|
|
355
|
+
pageNode,
|
|
356
|
+
nodeById,
|
|
357
|
+
relevantEvidence: routeRelevantEvidence,
|
|
358
|
+
pageReachableNodeIds: routeLevelNodeIds,
|
|
359
|
+
discoveryByFile,
|
|
360
|
+
failureByFile,
|
|
361
|
+
runArtifact: context.runArtifact,
|
|
362
|
+
});
|
|
363
|
+
coverage.unshift(...pageProjection.coverage);
|
|
364
|
+
failures.unshift(...pageProjection.failures);
|
|
365
|
+
}
|
|
366
|
+
|
|
345
367
|
return { coverage, failures };
|
|
346
368
|
}
|
|
347
369
|
|
|
@@ -417,6 +439,16 @@ function collectReachableNodeIds(startNodeId, outgoing) {
|
|
|
417
439
|
return visited;
|
|
418
440
|
}
|
|
419
441
|
|
|
442
|
+
function collectRouteLevelNodeIds(pageReachableNodeIds, surfaceReachableEntries) {
|
|
443
|
+
const routeLevelNodeIds = new Set(pageReachableNodeIds);
|
|
444
|
+
for (const { reachableNodeIds } of surfaceReachableEntries) {
|
|
445
|
+
for (const nodeId of reachableNodeIds) {
|
|
446
|
+
routeLevelNodeIds.delete(nodeId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return routeLevelNodeIds;
|
|
450
|
+
}
|
|
451
|
+
|
|
420
452
|
function collectViaNodes(evidenceEntries, reachableNodeIds, nodeById, excludedNodeIds = new Set()) {
|
|
421
453
|
const nodes = new Map();
|
|
422
454
|
for (const entry of evidenceEntries) {
|
|
@@ -161,6 +161,163 @@ const context = {
|
|
|
161
161
|
},
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
+
const routeLevelContext = {
|
|
165
|
+
product: {
|
|
166
|
+
name: "dashboard",
|
|
167
|
+
directory: "/tmp/dashboard",
|
|
168
|
+
},
|
|
169
|
+
services: [
|
|
170
|
+
{
|
|
171
|
+
name: "dashboard",
|
|
172
|
+
baseUrl: "http://localhost:3000",
|
|
173
|
+
origin: "http://localhost:3000",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
discovery: {
|
|
177
|
+
files: [
|
|
178
|
+
{
|
|
179
|
+
path: "__testkit__/projects/projects.int.testkit.ts",
|
|
180
|
+
displayName: "Projects",
|
|
181
|
+
service: "dashboard",
|
|
182
|
+
suiteName: "projects",
|
|
183
|
+
selectionType: "int",
|
|
184
|
+
framework: "k6",
|
|
185
|
+
skipped: false,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
coverageGraph: {
|
|
189
|
+
schemaVersion: 1,
|
|
190
|
+
nodes: [
|
|
191
|
+
{
|
|
192
|
+
id: "page_view:dashboard:/projects",
|
|
193
|
+
kind: "page_view",
|
|
194
|
+
service: "dashboard",
|
|
195
|
+
label: "Projects",
|
|
196
|
+
route: "/projects",
|
|
197
|
+
filePath: "src/app/projects/page.tsx",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "ui_surface:dashboard:/projects:project-create-button",
|
|
201
|
+
kind: "ui_surface",
|
|
202
|
+
service: "dashboard",
|
|
203
|
+
label: "Create Project",
|
|
204
|
+
route: "/projects",
|
|
205
|
+
filePath: "src/app/projects/page.tsx",
|
|
206
|
+
target: { kind: "testId", value: "project-create-button", confidence: "high" },
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "ui_action:dashboard:/projects:handleCreate",
|
|
210
|
+
kind: "ui_action",
|
|
211
|
+
service: "dashboard",
|
|
212
|
+
label: "handleCreate",
|
|
213
|
+
route: "/projects",
|
|
214
|
+
filePath: "src/app/projects/page.tsx",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: "client_request:dashboard:provider:GET:/api/projects",
|
|
218
|
+
kind: "client_request",
|
|
219
|
+
service: "dashboard",
|
|
220
|
+
label: "GET /api/projects",
|
|
221
|
+
route: "/projects",
|
|
222
|
+
method: "GET",
|
|
223
|
+
path: "/api/projects",
|
|
224
|
+
filePath: "src/components/project-context.tsx",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "client_request:dashboard:projects:POST:/api/projects",
|
|
228
|
+
kind: "client_request",
|
|
229
|
+
service: "dashboard",
|
|
230
|
+
label: "POST /api/projects",
|
|
231
|
+
route: "/projects",
|
|
232
|
+
method: "POST",
|
|
233
|
+
path: "/api/projects",
|
|
234
|
+
filePath: "src/app/projects/page.tsx",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "api_route:dashboard:GET:/api/projects",
|
|
238
|
+
kind: "api_route",
|
|
239
|
+
service: "dashboard",
|
|
240
|
+
label: "GET /api/projects",
|
|
241
|
+
route: "/projects",
|
|
242
|
+
method: "GET",
|
|
243
|
+
path: "/api/projects",
|
|
244
|
+
filePath: "src/app/api/projects/route.ts",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "api_route:dashboard:POST:/api/projects",
|
|
248
|
+
kind: "api_route",
|
|
249
|
+
service: "dashboard",
|
|
250
|
+
label: "POST /api/projects",
|
|
251
|
+
route: "/projects",
|
|
252
|
+
method: "POST",
|
|
253
|
+
path: "/api/projects",
|
|
254
|
+
filePath: "src/app/api/projects/route.ts",
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
edges: [
|
|
258
|
+
{
|
|
259
|
+
id: "contains:page_view:dashboard:/projects:ui_surface:dashboard:/projects:project-create-button",
|
|
260
|
+
kind: "contains",
|
|
261
|
+
from: "page_view:dashboard:/projects",
|
|
262
|
+
to: "ui_surface:dashboard:/projects:project-create-button",
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "triggers:ui_surface:dashboard:/projects:project-create-button:ui_action:dashboard:/projects:handleCreate",
|
|
266
|
+
kind: "triggers",
|
|
267
|
+
from: "ui_surface:dashboard:/projects:project-create-button",
|
|
268
|
+
to: "ui_action:dashboard:/projects:handleCreate",
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "requests:page_view:dashboard:/projects:client_request:dashboard:provider:GET:/api/projects",
|
|
272
|
+
kind: "requests",
|
|
273
|
+
from: "page_view:dashboard:/projects",
|
|
274
|
+
to: "client_request:dashboard:provider:GET:/api/projects",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "requests:ui_action:dashboard:/projects:handleCreate:client_request:dashboard:projects:POST:/api/projects",
|
|
278
|
+
kind: "requests",
|
|
279
|
+
from: "ui_action:dashboard:/projects:handleCreate",
|
|
280
|
+
to: "client_request:dashboard:projects:POST:/api/projects",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "handles:client_request:dashboard:provider:GET:/api/projects:api_route:dashboard:GET:/api/projects",
|
|
284
|
+
kind: "handles",
|
|
285
|
+
from: "client_request:dashboard:provider:GET:/api/projects",
|
|
286
|
+
to: "api_route:dashboard:GET:/api/projects",
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: "handles:client_request:dashboard:projects:POST:/api/projects:api_route:dashboard:POST:/api/projects",
|
|
290
|
+
kind: "handles",
|
|
291
|
+
from: "client_request:dashboard:projects:POST:/api/projects",
|
|
292
|
+
to: "api_route:dashboard:POST:/api/projects",
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
evidence: [
|
|
296
|
+
{
|
|
297
|
+
id: "evidence:dashboard:__testkit__/projects/projects.int.testkit.ts",
|
|
298
|
+
source: "convention",
|
|
299
|
+
confidence: "high",
|
|
300
|
+
service: "dashboard",
|
|
301
|
+
suiteName: "projects",
|
|
302
|
+
selectionType: "int",
|
|
303
|
+
framework: "k6",
|
|
304
|
+
testFilePath: "__testkit__/projects/projects.int.testkit.ts",
|
|
305
|
+
coveredNodeIds: [
|
|
306
|
+
"api_route:dashboard:GET:/api/projects",
|
|
307
|
+
"api_route:dashboard:POST:/api/projects",
|
|
308
|
+
],
|
|
309
|
+
details: {
|
|
310
|
+
route: "/projects",
|
|
311
|
+
requestPaths: ["/api/projects"],
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
diagnostics: [],
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
runArtifact: null,
|
|
319
|
+
};
|
|
320
|
+
|
|
164
321
|
describe("testkit bridge", () => {
|
|
165
322
|
it("matches pages against service base URLs", () => {
|
|
166
323
|
expect(buildMatchResponse(context, "http://localhost:3000/coverage")).toMatchObject({
|
|
@@ -177,46 +334,76 @@ describe("testkit bridge", () => {
|
|
|
177
334
|
summary: {
|
|
178
335
|
failureState: "failing",
|
|
179
336
|
coverageState: "covered",
|
|
180
|
-
relatedFailureCount:
|
|
181
|
-
relatedCoverageCount:
|
|
337
|
+
relatedFailureCount: 2,
|
|
338
|
+
relatedCoverageCount: 2,
|
|
182
339
|
coverageBreakdown: {
|
|
183
|
-
direct:
|
|
340
|
+
direct: 1,
|
|
184
341
|
indirect: 0,
|
|
185
342
|
mixed: 1,
|
|
186
343
|
},
|
|
187
344
|
},
|
|
188
|
-
failures: [
|
|
189
|
-
{
|
|
345
|
+
failures: expect.arrayContaining([
|
|
346
|
+
expect.objectContaining({
|
|
347
|
+
kind: "page_view",
|
|
348
|
+
label: "Coverage",
|
|
349
|
+
}),
|
|
350
|
+
expect.objectContaining({
|
|
190
351
|
kind: "ui_surface",
|
|
191
352
|
label: "Refresh overlay",
|
|
192
353
|
importance: "high",
|
|
193
354
|
reason: "Refresh overlay is implicated by 1 failing test.",
|
|
194
355
|
targets: [{ kind: "testId", value: "coverage-refresh-button", confidence: "high" }],
|
|
195
356
|
failedTests: [
|
|
196
|
-
{
|
|
357
|
+
expect.objectContaining({
|
|
197
358
|
filePath: "app/coverage/__testkit__/coverage.pw.testkit.ts",
|
|
198
359
|
type: "pw",
|
|
199
|
-
},
|
|
360
|
+
}),
|
|
200
361
|
],
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
coverage: [
|
|
204
|
-
{
|
|
362
|
+
}),
|
|
363
|
+
]),
|
|
364
|
+
coverage: expect.arrayContaining([
|
|
365
|
+
expect.objectContaining({
|
|
366
|
+
kind: "page_view",
|
|
367
|
+
label: "Coverage",
|
|
368
|
+
supportKind: "direct",
|
|
369
|
+
}),
|
|
370
|
+
expect.objectContaining({
|
|
205
371
|
kind: "ui_surface",
|
|
206
372
|
label: "Refresh overlay",
|
|
207
373
|
importance: "high",
|
|
208
374
|
supportKind: "mixed",
|
|
209
375
|
targets: [{ kind: "testId", value: "coverage-refresh-button", confidence: "high" }],
|
|
210
376
|
supportingTests: [
|
|
211
|
-
{
|
|
377
|
+
expect.objectContaining({
|
|
212
378
|
filePath: "app/coverage/__testkit__/coverage.pw.testkit.ts",
|
|
213
|
-
},
|
|
214
|
-
{
|
|
379
|
+
}),
|
|
380
|
+
expect.objectContaining({
|
|
215
381
|
filePath: "app/api/coverage/__testkit__/coverage.int.testkit.ts",
|
|
216
|
-
},
|
|
382
|
+
}),
|
|
217
383
|
],
|
|
218
|
-
},
|
|
219
|
-
],
|
|
384
|
+
}),
|
|
385
|
+
]),
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("retains route-level coverage when support comes from page-owned requests outside any surfaced control", () => {
|
|
390
|
+
expect(buildPageOverlayResponse(routeLevelContext, "http://localhost:3000/projects")).toMatchObject({
|
|
391
|
+
summary: {
|
|
392
|
+
coverageState: "covered",
|
|
393
|
+
relatedCoverageCount: 2,
|
|
394
|
+
},
|
|
395
|
+
coverage: expect.arrayContaining([
|
|
396
|
+
expect.objectContaining({
|
|
397
|
+
kind: "page_view",
|
|
398
|
+
label: "Projects",
|
|
399
|
+
supportKind: "indirect",
|
|
400
|
+
}),
|
|
401
|
+
expect.objectContaining({
|
|
402
|
+
kind: "ui_surface",
|
|
403
|
+
label: "Create Project",
|
|
404
|
+
supportKind: "indirect",
|
|
405
|
+
}),
|
|
406
|
+
]),
|
|
220
407
|
});
|
|
221
408
|
});
|
|
222
409
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
4
4
|
"description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"vitest": "^3.2.4"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@elench/testkit-bridge": "0.1.
|
|
63
|
-
"@elench/testkit-protocol": "0.1.
|
|
62
|
+
"@elench/testkit-bridge": "0.1.63",
|
|
63
|
+
"@elench/testkit-protocol": "0.1.63",
|
|
64
64
|
"@babel/code-frame": "^7.29.0",
|
|
65
65
|
"@oclif/core": "^4.10.6",
|
|
66
66
|
"esbuild": "^0.25.11",
|