Spaces:
Sleeping
Sleeping
| import { canonicalRuleKey, actRuleIdsFor } from './act.js'; | |
| /** | |
| * Cross-engine consensus. axe and Alfa both implement W3C ACT rules, so | |
| * the same issue is often reported by both — once you'd naively sum axe | |
| * + Alfa counts, you'd show ~2x the real number. This consolidates by: | |
| * | |
| * primary: the shared ACT rule (axe "image-alt" and Alfa "sia-r2" | |
| * are ACT 23a2a8 -> one canonical issue), and | |
| * secondary: the affected page URL. | |
| * | |
| * An issue = (canonical rule, page). It's "consensus" when both engines | |
| * flagged that canonical rule on that page, else axe-only or alfa-only. | |
| * Unmapped rules keep their own key, so they're never wrongly merged. | |
| * | |
| * Selectors differ between engines (each emits its own), so we correlate | |
| * at canonical-rule + page granularity rather than forcing selector | |
| * equality — that's the reliable signal that "both engines agree". | |
| */ | |
| /** | |
| * Build a consensus summary from the per-rule affectedPages already | |
| * collected in the weekly summary. | |
| * | |
| * axeRules / alfaRules: { ruleId: { affectedPages: [{url, instances}], ... } } | |
| * | |
| * Returns: | |
| * { | |
| * uniqueIssues, // distinct (canonical rule, page) pairs | |
| * consensus, // flagged by BOTH engines | |
| * axeOnly, alfaOnly, | |
| * rawAxe, rawAlfa, // naive per-engine (rule,page) counts, for contrast | |
| * byKey: { canonicalKey: { actRuleId, axeRules:Set, alfaRules:Set, pages:Set, engines } } | |
| * } | |
| */ | |
| export function buildConsensus(axeRules = {}, alfaRules = {}) { | |
| // canonicalKey -> { actRuleId, axeRules:Set, alfaRules:Set, | |
| // pagesAxe:Set, pagesAlfa:Set } | |
| const groups = new Map(); | |
| let rawAxe = 0; | |
| let rawAlfa = 0; | |
| const ingest = (rules, engineKey) => { | |
| for (const [ruleId, rule] of Object.entries(rules)) { | |
| const key = canonicalRuleKey(engineKey, ruleId); | |
| const act = actRuleIdsFor(engineKey, ruleId)[0] ?? null; | |
| let g = groups.get(key); | |
| if (!g) { | |
| g = { actRuleId: act, axeRules: new Set(), alfaRules: new Set(), pagesAxe: new Set(), pagesAlfa: new Set() }; | |
| groups.set(key, g); | |
| } | |
| if (engineKey === 'axe-core') g.axeRules.add(ruleId); | |
| else g.alfaRules.add(ruleId); | |
| for (const p of rule.affectedPages ?? []) { | |
| if (engineKey === 'axe-core') { g.pagesAxe.add(p.url); rawAxe++; } | |
| else { g.pagesAlfa.add(p.url); rawAlfa++; } | |
| } | |
| } | |
| }; | |
| ingest(axeRules, 'axe-core'); | |
| ingest(alfaRules, 'alfa'); | |
| let consensus = 0; | |
| let axeOnly = 0; | |
| let alfaOnly = 0; | |
| let uniqueIssues = 0; | |
| const byKey = {}; | |
| for (const [key, g] of groups) { | |
| const allPages = new Set([...g.pagesAxe, ...g.pagesAlfa]); | |
| for (const page of allPages) { | |
| uniqueIssues++; | |
| const inAxe = g.pagesAxe.has(page); | |
| const inAlfa = g.pagesAlfa.has(page); | |
| if (inAxe && inAlfa) consensus++; | |
| else if (inAxe) axeOnly++; | |
| else alfaOnly++; | |
| } | |
| byKey[key] = { | |
| actRuleId: g.actRuleId, | |
| axeRules: [...g.axeRules], | |
| alfaRules: [...g.alfaRules], | |
| pages: allPages.size, | |
| engines: g.axeRules.size && g.alfaRules.size ? 'both' : g.axeRules.size ? 'axe' : 'alfa', | |
| }; | |
| } | |
| return { | |
| uniqueIssues, | |
| consensus, | |
| axeOnly, | |
| alfaOnly, | |
| rawAxe, | |
| rawAlfa, | |
| byKey, | |
| }; | |
| } | |