mirror of
https://github.com/mgeeky/decode-spam-headers.git
synced 2026-02-22 05:23:31 +01:00
MAESTRO: improve standalone HTML export
This commit is contained in:
@@ -54,7 +54,7 @@ ReportContainer
|
|||||||
- [x] Security appliances show as badges; empty state handled gracefully
|
- [x] Security appliances show as badges; empty state handled gracefully
|
||||||
- [x] Search filters results in real-time across test name, header name, and analysis text
|
- [x] Search filters results in real-time across test name, header name, and analysis text
|
||||||
- [x] Export JSON produces a valid JSON file containing all results
|
- [x] Export JSON produces a valid JSON file containing all results
|
||||||
- [ ] Export HTML produces a styled standalone page viewable in any browser
|
- [x] Export HTML produces a styled standalone page viewable in any browser
|
||||||
- [ ] All report components are keyboard accessible
|
- [ ] All report components are keyboard accessible
|
||||||
- [ ] Linting passes (`npx eslint src/`, `npx prettier --check src/`)
|
- [ ] Linting passes (`npx eslint src/`, `npx prettier --check src/`)
|
||||||
- [ ] Run `/speckit.analyze` to verify consistency
|
- [ ] Run `/speckit.analyze` to verify consistency
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ describe("ReportExport", () => {
|
|||||||
expect(createObjectUrlSpy).toHaveBeenCalled();
|
expect(createObjectUrlSpy).toHaveBeenCalled();
|
||||||
expect(clickSpy).toHaveBeenCalled();
|
expect(clickSpy).toHaveBeenCalled();
|
||||||
expect(lastBlob).not.toBeNull();
|
expect(lastBlob).not.toBeNull();
|
||||||
expect(lastBlob?.type).toBe("application/json");
|
expect(lastBlob?.type).toBe("application/json;charset=utf-8");
|
||||||
|
|
||||||
const text = await lastBlob?.text();
|
const text = await lastBlob?.text();
|
||||||
const parsed = JSON.parse(text ?? "{}") as AnalysisReport;
|
const parsed = JSON.parse(text ?? "{}") as AnalysisReport;
|
||||||
@@ -144,7 +144,7 @@ describe("ReportExport", () => {
|
|||||||
expect(parsed.results[0]?.testId).toBe(101);
|
expect(parsed.results[0]?.testId).toBe(101);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("triggers an HTML download", () => {
|
it("triggers an HTML download with styled markup", async () => {
|
||||||
const { container } = render(<ReportExport report={report} />);
|
const { container } = render(<ReportExport report={report} />);
|
||||||
|
|
||||||
const htmlButton = getByTestId(container, "report-export-html");
|
const htmlButton = getByTestId(container, "report-export-html");
|
||||||
@@ -155,5 +155,13 @@ describe("ReportExport", () => {
|
|||||||
|
|
||||||
expect(createObjectUrlSpy).toHaveBeenCalled();
|
expect(createObjectUrlSpy).toHaveBeenCalled();
|
||||||
expect(clickSpy).toHaveBeenCalled();
|
expect(clickSpy).toHaveBeenCalled();
|
||||||
|
expect(lastBlob?.type).toBe("text/html;charset=utf-8");
|
||||||
|
|
||||||
|
const text = await lastBlob?.text();
|
||||||
|
expect(text).toContain("<!doctype html>");
|
||||||
|
expect(text).toContain("<style>");
|
||||||
|
expect(text).toContain("Email Header Analysis Report");
|
||||||
|
expect(text).toContain("Summary");
|
||||||
|
expect(text).toContain("SpamAssassin Rule Hits");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ const buildHtmlReport = (report: AnalysisReport): string => {
|
|||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: "Geist", "Segoe UI", system-ui, sans-serif;
|
font-family: "Geist", "Segoe UI", system-ui, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@@ -162,6 +163,11 @@ const buildHtmlReport = (report: AnalysisReport): string => {
|
|||||||
margin: 0 0 4px;
|
margin: 0 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.muted {
|
.muted {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -427,16 +433,20 @@ const downloadBlob = (content: string, mimeType: string, fileName: string) => {
|
|||||||
document.body.appendChild(anchor);
|
document.body.appendChild(anchor);
|
||||||
anchor.click();
|
anchor.click();
|
||||||
anchor.remove();
|
anchor.remove();
|
||||||
URL.revokeObjectURL(url);
|
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ReportExport({ report }: ReportExportProps) {
|
export default function ReportExport({ report }: ReportExportProps) {
|
||||||
const handleJsonExport = () => {
|
const handleJsonExport = () => {
|
||||||
downloadBlob(JSON.stringify(report, null, 2), "application/json", "analysis-report.json");
|
downloadBlob(
|
||||||
|
JSON.stringify(report, null, 2),
|
||||||
|
"application/json;charset=utf-8",
|
||||||
|
"analysis-report.json",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHtmlExport = () => {
|
const handleHtmlExport = () => {
|
||||||
downloadBlob(buildHtmlReport(report), "text/html", "analysis-report.html");
|
downloadBlob(buildHtmlReport(report), "text/html;charset=utf-8", "analysis-report.html");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user