diff --git a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md index 7d30165..1beb57a 100644 --- a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md +++ b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-06-Interactive-Report.md @@ -39,7 +39,7 @@ ReportContainer ## Tasks - [x] T030 [US4] Write failing tests (TDD Red) in `frontend/src/__tests__/report/TestResultCard.test.tsx` (each severity level, expand/collapse, error indicator), `HopChainVisualisation.test.tsx` (render with sample hops), `ReportSearchBar.test.tsx` (filter simulation), `ReportExport.test.tsx` (export trigger), `SecurityAppliancesSummary.test.tsx` (render with sample appliances, empty state), `ReportContainer.test.tsx` (full report with mixed results) -- [ ] T031 [P] [US4] Create `frontend/src/components/report/TestResultCard.tsx` — collapsible card per test result. Severity-coloured indicator (red=spam, amber=suspicious, green=clean per FR-09), header name, monospace value, analysis text. Failed tests show error indicator (FR-25). Expand/collapse with animation, keyboard accessible (NFR-02). Verify `TestResultCard.test.tsx` passes (TDD Green) +- [x] T031 [P] [US4] Create `frontend/src/components/report/TestResultCard.tsx` — collapsible card per test result. Severity-coloured indicator (red=spam, amber=suspicious, green=clean per FR-09), header name, monospace value, analysis text. Failed tests show error indicator (FR-25). Expand/collapse with animation, keyboard accessible (NFR-02). Verify `TestResultCard.test.tsx` passes (TDD Green) - [ ] T032 [P] [US4] Create `frontend/src/components/report/HopChainVisualisation.tsx` — vertical flow diagram of mail server hop chain (FR-08): hostname, IP, timestamp, server version, connecting arrows. FontAwesome server/network icons. Responsive. Verify `HopChainVisualisation.test.tsx` passes (TDD Green) - [ ] T033 [P] [US4] Create `frontend/src/components/report/ReportSearchBar.tsx` — search/filter bar above report (FR-20). Filters by text match against test name, header name, or analysis text. Highlights matches, shows count. FontAwesome search icon, Escape to clear. Verify `ReportSearchBar.test.tsx` passes (TDD Green) - [ ] T034 [P] [US4] Create `frontend/src/components/report/ReportExport.tsx` — export as HTML (styled standalone page) or JSON (raw data) per FR-21. FontAwesome download icons, triggers browser download. Verify `ReportExport.test.tsx` passes (TDD Green) diff --git a/frontend/src/components/report/TestResultCard.tsx b/frontend/src/components/report/TestResultCard.tsx new file mode 100644 index 0000000..dbd7550 --- /dev/null +++ b/frontend/src/components/report/TestResultCard.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useState } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faBan, + faCheck, + faCircleInfo, + faTriangleExclamation, + faChevronDown, +} from "@fortawesome/free-solid-svg-icons"; + +import type { TestResult, TestSeverity } from "../../types/analysis"; + +const severityStyles: Record< + TestSeverity, + { label: string; className: string; icon: typeof faBan } +> = { + spam: { label: "Spam", className: "text-spam border-spam/40", icon: faBan }, + suspicious: { + label: "Suspicious", + className: "text-suspicious border-suspicious/40", + icon: faTriangleExclamation, + }, + clean: { label: "Clean", className: "text-clean border-clean/40", icon: faCheck }, + info: { label: "Info", className: "text-accent border-accent/40", icon: faCircleInfo }, +}; + +const getErrorMessage = (result: TestResult): string => { + const message = result.error?.trim(); + return message && message.length > 0 ? message : "Unknown failure."; +}; + +type TestResultCardProps = { + result: TestResult; +}; + +export default function TestResultCard({ result }: TestResultCardProps) { + const [isExpanded, setIsExpanded] = useState(false); + const severityStyle = severityStyles[result.severity]; + const detailsId = `test-result-details-${result.testId}`; + + const toggle = () => { + setIsExpanded((previous) => !previous); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + toggle(); + } + }; + + return ( +
+ + +
+
+
+
+ + Header + + {result.headerName} + {result.headerValue} +
+ + {result.analysis ? ( +

{result.analysis}

+ ) : null} + {result.description ? ( +

{result.description}

+ ) : null} + + {result.status === "error" ? ( +
+ + + Error: {getErrorMessage(result)} + +
+ ) : null} +
+
+
+
+ ); +}