diff --git a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md index c1d92c5..a964997 100644 --- a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md +++ b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md @@ -18,7 +18,7 @@ This phase performs final integration, accessibility audit, responsive testing, ## Tasks - [x] T046 Wire all components together in `frontend/src/app/page.tsx` — integrate HeaderInput, FileDropZone, AnalysisControls, AnalyseButton, ProgressIndicator, ReportContainer, CaptchaChallenge into the single-view application with correct data flow. Ensure: input feeds to analysis hook, progress hook drives progress indicator, result feeds to report container, 429 errors trigger CAPTCHA modal, cache hook restores state on mount. Notes: added AnalysisControls + CAPTCHA retry flow, extended analysis hook for bypass token handling, confirmed cache restore. -- [ ] T047 Verify WCAG 2.1 AA compliance across all components (NFR-03) — ARIA labels, keyboard nav order, focus indicators, colour contrast ratios (dark theme). Fix violations. Test with screen reader simulation. Ensure all interactive elements have visible focus states +- [x] T047 Verify WCAG 2.1 AA compliance across all components (NFR-03) — ARIA labels, keyboard nav order, focus indicators, colour contrast ratios (dark theme). Fix violations. Test with screen reader simulation. Ensure all interactive elements have visible focus states. Notes: added keyboard-accessible file picker with ARIA descriptions, focus-visible outlines on drop zone/summary/search fields, boosted low-contrast text from 40% to 60%, linked CAPTCHA dialog description, added file picker tests; ran `npx vitest run src/__tests__/FileDropZone.test.tsx`. - [ ] T048 [P] Verify responsive layout 320px–2560px (NFR-04) at breakpoints: 320px, 768px, 1024px, 1440px, 2560px. No horizontal scroll, no overlapping elements, readable text. Fix any layout issues discovered - [ ] T049 [P] Run full linting pass — `ruff check backend/` and `ruff format backend/` zero errors; `npx eslint src/` and `npx prettier --check src/` zero errors; no `any` types in TypeScript. Fix all violations - [ ] T050 [P] Run full test suites and verify coverage — `pytest backend/tests/ --cov` ≥80% new modules (NFR-06); `npx vitest run --coverage` ≥80% new components (NFR-07). Add missing tests if coverage is below threshold diff --git a/frontend/src/__tests__/FileDropZone.test.tsx b/frontend/src/__tests__/FileDropZone.test.tsx index cfce910..21a74b4 100644 --- a/frontend/src/__tests__/FileDropZone.test.tsx +++ b/frontend/src/__tests__/FileDropZone.test.tsx @@ -128,4 +128,49 @@ describe("FileDropZone", () => { expect(alert?.textContent ?? "").toMatch(/\.eml/i); expect(alert?.textContent ?? "").toMatch(/\.txt/i); }); + + it("opens the file picker on keyboard activation", () => { + const { container } = render( undefined} />); + const dropZone = getDropZone(container); + const input = container.querySelector('input[type="file"]') as HTMLInputElement | null; + expect(input).not.toBeNull(); + if (!input) { + return; + } + + const clickSpy = vi.spyOn(input, "click"); + + act(() => { + dropZone.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true })); + }); + + expect(clickSpy).toHaveBeenCalled(); + clickSpy.mockRestore(); + }); + + it("reads selected file content from the file picker", () => { + const handleContent = vi.fn(); + const restore = mockFileReader("Header from input"); + const { container } = render(); + const input = container.querySelector('input[type="file"]') as HTMLInputElement | null; + expect(input).not.toBeNull(); + if (!input) { + restore(); + return; + } + + const file = new File(["Header from input"], "sample.eml", { type: "message/rfc822" }); + Object.defineProperty(input, "files", { + value: [file], + writable: false, + }); + + act(() => { + input.dispatchEvent(new Event("change", { bubbles: true })); + }); + + restore(); + + expect(handleContent).toHaveBeenCalledWith("Header from input"); + }); }); diff --git a/frontend/src/components/CaptchaChallenge.tsx b/frontend/src/components/CaptchaChallenge.tsx index ae56264..4393b52 100644 --- a/frontend/src/components/CaptchaChallenge.tsx +++ b/frontend/src/components/CaptchaChallenge.tsx @@ -34,6 +34,7 @@ export default function CaptchaChallenge({ const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const titleId = useId(); + const descriptionId = useId(); const inputRef = useRef(null); const answerRef = useRef(""); const modalRef = useRef(null); @@ -151,6 +152,7 @@ export default function CaptchaChallenge({ role="dialog" aria-modal="true" aria-labelledby={titleId} + aria-describedby={descriptionId} data-testid="captcha-challenge" onKeyDown={handleKeyDown} tabIndex={-1} @@ -165,7 +167,9 @@ export default function CaptchaChallenge({

Security Check Required

-

Solve the CAPTCHA to continue analysis.

+

+ Solve the CAPTCHA to continue analysis. +