export type VisualPageIdentity = { route: string; role: string; viewport: string }; export type VisualPageCompletion = VisualPageIdentity & { actualRoute: string }; export type VisualGroupResult = { id: string; status: string }; const pageKey = (page: VisualPageIdentity) => JSON.stringify([page.viewport, page.role, page.route]); // Only fully audited pages belong in completed; arrival alone is insufficient. export function visualCoverageProblems( planned: VisualPageIdentity[], completed: VisualPageCompletion[], expectedGroups: string[], groups: VisualGroupResult[], ) { const problems: string[] = []; const plannedKeys = planned.map(pageKey); const completedKeys = completed.map(pageKey); if (!plannedKeys.length) problems.push("empty-plan"); if (new Set(plannedKeys).size !== plannedKeys.length) problems.push("duplicate-plan"); if (new Set(completedKeys).size !== completedKeys.length) problems.push("duplicate-completion"); if (plannedKeys.some(key => !completedKeys.includes(key))) problems.push("missing-page"); if (completedKeys.some(key => !plannedKeys.includes(key))) problems.push("unexpected-page"); if (completed.some(page => page.actualRoute !== page.route)) problems.push("wrong-arrival"); if (!expectedGroups.length || new Set(expectedGroups).size !== expectedGroups.length) problems.push("invalid-group-plan"); if (new Set(groups.map(group => group.id)).size !== groups.length) problems.push("duplicate-group"); if (expectedGroups.some(id => !groups.some(group => group.id === id && group.status === "passed"))) problems.push("incomplete-group"); if (groups.some(group => !expectedGroups.includes(group.id))) problems.push("unexpected-group"); return problems; }