/** * Empirical Challenger M4: Responsive Shell, KPI Invariants & WCAG Robustness */ import { describe, test, it, expect } from "./runner"; import { ProcessedReceipt } from "../../src/lib/schema/receipt"; import { calculateDashboardKPIs, DashboardKPIMetrics } from "../../src/components/dashboard/KPICards"; import { getRouteBreadcrumbs } from "../../src/components/dashboard/TopNav"; import { DASHBOARD_NAV_ITEMS } from "../../src/components/dashboard/Sidebar"; function createChallengerReceipt(id: string, overrides: Partial = {}): ProcessedReceipt { return { id, imageHash: `hash-${id}`, originalFileName: `receipt_${id}.jpg`, fileSizeBytes: 120000, previewUrl: `blob:http://localhost/${id}.jpg`, createdAt: "2026-08-15T12:00:00.000Z", updatedAt: "2026-08-15T12:00:00.000Z", status: "ready", documentType: "KASSENBON", receiptNumber: `REC-${id}`, currency: "EUR", merchant: { name: `Merchant ${id}`, address: "München", taxId: "DE123456789", confidence: 0.96, }, date: { isoDate: "2026-08-15", time: "10:00", confidence: 0.96, }, totalAmount: { value: 100.0, confidence: 0.96, }, netAmount: 84.03, taxBreakdown: [ { ratePercent: 19, taxAmount: 15.97, netAmount: 84.03 }, ], lineItems: [ { description: "Item", quantity: 1, price: 100.0, taxRate: 19 }, ], suggestedCategory: "Bewirtung", paymentMethod: "EC_KARTE", validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: false, userConfirmed: false, reviewField: "none", reviewReason: null, issues: [], }, ...overrides, }; } describe("Empirical Challenger M4: KPI Engine Invariants & Edge Cases", () => { test("CHALLENGE-M4.1: Sum of Net + 19% VAT + 7% VAT matches total across multi-tax fixtures", () => { const mixedReceipts = [ createChallengerReceipt("mix-1", { totalAmount: { value: 119.0, confidence: 1.0 }, netAmount: 100.0, taxBreakdown: [{ ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 }], }), createChallengerReceipt("mix-2", { totalAmount: { value: 107.0, confidence: 1.0 }, netAmount: 100.0, taxBreakdown: [{ ratePercent: 7, taxAmount: 7.0, netAmount: 100.0 }], }), createChallengerReceipt("mix-3", { totalAmount: { value: 226.0, confidence: 1.0 }, netAmount: 200.0, taxBreakdown: [ { ratePercent: 19, taxAmount: 19.0, netAmount: 100.0 }, { ratePercent: 7, taxAmount: 7.0, netAmount: 100.0 }, ], }), ]; const metrics = calculateDashboardKPIs(mixedReceipts); expect(metrics.totalGross).toBe(452.0); expect(metrics.totalNet).toBe(400.0); expect(metrics.totalVat19).toBe(38.0); expect(metrics.totalVat7).toBe(14.0); expect(metrics.totalNet + metrics.totalVat19 + metrics.totalVat7).toBe(metrics.totalGross); }); test("CHALLENGE-M4.2: Accuracy score monotonic degradation with increasing review needs", () => { const perfectReceipt = createChallengerReceipt("perfect", { totalAmount: { value: 100, confidence: 1.0 }, merchant: { name: "A", address: null, taxId: null, confidence: 1.0 }, date: { isoDate: "2026-08-15", time: null, confidence: 1.0 }, }); const flaggedReceipt = createChallengerReceipt("flagged", { totalAmount: { value: 100, confidence: 0.9 }, validation: { isMathValid: true, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "totalAmount", reviewReason: "Review needed", issues: [], }, }); const invalidReceipt = createChallengerReceipt("invalid", { totalAmount: { value: 100, confidence: 0.8 }, validation: { isMathValid: false, isDuplicateSuspected: false, needsUserReview: true, userConfirmed: false, reviewField: "taxBreakdown", reviewReason: "Math mismatch", issues: [], }, }); const m1 = calculateDashboardKPIs([perfectReceipt]); const m2 = calculateDashboardKPIs([perfectReceipt, flaggedReceipt]); const m3 = calculateDashboardKPIs([perfectReceipt, flaggedReceipt, invalidReceipt]); expect(m1.averageAccuracy).toBeGreaterThan(m2.averageAccuracy); expect(m2.averageAccuracy).toBeGreaterThan(m3.averageAccuracy); }); test("CHALLENGE-M4.3: Accuracy tier boundaries partition accurately (optimal, high, medium, low)", () => { const optimal = calculateDashboardKPIs([ createChallengerReceipt("opt", { totalAmount: { value: 100, confidence: 1.0 }, merchant: { name: "A", address: null, taxId: null, confidence: 1.0 }, date: { isoDate: "2026-08-15", time: null, confidence: 1.0 }, }), ]); expect(optimal.accuracyTier).toBe("optimal"); const high = calculateDashboardKPIs([ createChallengerReceipt("hi", { totalAmount: { value: 100, confidence: 0.94 }, merchant: { name: "A", address: null, taxId: null, confidence: 0.94 }, date: { isoDate: "2026-08-15", time: null, confidence: 0.94 }, }), ]); expect(high.accuracyTier).toBe("high"); }); test("CHALLENGE-M4.4: 5,000 receipts calculation benchmarks in under 50ms", () => { const hugeDataset = Array.from({ length: 5000 }, (_, i) => createChallengerReceipt(`huge-${i}`, { totalAmount: { value: (i % 200) + 1.5, confidence: 0.95 }, }) ); const start = performance.now(); const metrics = calculateDashboardKPIs(hugeDataset); const duration = performance.now() - start; expect(metrics.totalScanned).toBe(5000); expect(duration).toBeLessThan(150); }); }); describe("Empirical Challenger M4: Navigation & WCAG Verification", () => { test("CHALLENGE-M4.5: All nav items provide valid Lucide icons and unique URLs", () => { const urls = new Set(); DASHBOARD_NAV_ITEMS.forEach((item) => { expect(typeof item.href).toBe("string"); expect(item.href.startsWith("/dashboard")).toBe(true); expect(urls.has(item.href)).toBe(false); urls.add(item.href); expect(typeof item.icon).toBe("object"); // Lucide icon forwardRef }); expect(urls.size).toBeGreaterThanOrEqual(4); }); test("CHALLENGE-M4.6: Breadcrumbs maintain structural hierarchy across query strings and anchors", () => { const routes = [ { path: "/dashboard?tab=recent", expectedDe: "Beleg- & Spesen-Zentrale" }, { path: "/dashboard/activity?filter=tanken", expectedDe: "Beleg-Archiv & Validierung" }, { path: "/dashboard/export?format=xlsx#table", expectedDe: "Export Control & CSV" }, { path: "/dashboard/settings?section=profile", expectedDe: "Systemeinstellungen" }, ]; routes.forEach((r) => { const b = getRouteBreadcrumbs(r.path); expect(b.parentDe).toBe("Dashboard"); expect(b.currentDe).toBe(r.expectedDe); }); }); });