diff --git a/src/components/Toolbar.js b/src/components/Toolbar.js index 9c7c0be..cb5659a 100644 --- a/src/components/Toolbar.js +++ b/src/components/Toolbar.js @@ -83,6 +83,9 @@ const Toolbar = ({ onBackupClick }) => { selectedQuarter, }); } catch (err) { + // Toast is transient — keep a durable trace so export failures are + // diagnosable after the 3s window closes. + console.error('Export summary failed:', err); toast.error(`Export failed: ${err.message || err}`); } }, [hasAssessment, currentAssessment, requirements, findings, artifacts, selectedQuarter]); diff --git a/src/utils/executiveSummaryPDF.js b/src/utils/executiveSummaryPDF.js index 117e7c5..10b5bd1 100644 --- a/src/utils/executiveSummaryPDF.js +++ b/src/utils/executiveSummaryPDF.js @@ -1,7 +1,11 @@ import jsPDF from 'jspdf'; -import 'jspdf-autotable'; +import { applyPlugin } from 'jspdf-autotable'; import { getScoringScale, CMMI_LEVELS } from './scoringScale'; +// jspdf-autotable v5 no longer patches jsPDF on import; without this the +// doc.autoTable calls below throw and the export dies with only a toast. +applyPlugin(jsPDF); + // CSF function order for consistent display const FUNCTION_ORDER = [ 'GOVERN (GV)', diff --git a/src/utils/executiveSummaryPDF.test.js b/src/utils/executiveSummaryPDF.test.js new file mode 100644 index 0000000..115a18a --- /dev/null +++ b/src/utils/executiveSummaryPDF.test.js @@ -0,0 +1,36 @@ +import jsPDF from 'jspdf'; +import { generateExecutiveSummary } from './executiveSummaryPDF'; + +// Regression guard for the jspdf-autotable v5 upgrade: v5 stopped patching +// jsPDF on import, which left doc.autoTable undefined and killed the +// Export summary button with only a transient toast. +describe('generateExecutiveSummary', () => { + it('applies the autoTable plugin to jsPDF', () => { + expect(typeof jsPDF.API.autoTable).toBe('function'); + }); + + it('generates and saves the PDF without throwing for a fresh assessment', () => { + // save lives in the jsPDF constructor closure, not on jsPDF.API, so + // spyOn can't find it — but an API-level assignment wins at construction. + const saveSpy = jest.fn().mockReturnThis(); + jsPDF.API.save = saveSpy; + + generateExecutiveSummary({ + assessment: { + id: 'a-new', + name: 'New Assessment', + year: 2026, + createdDate: '2026-07-31', + scopeIds: ['GV.OC-01', 'ID.AM-01'], + observations: {}, + }, + requirements: [], + findings: [], + artifacts: [], + selectedQuarter: 2, + }); + + expect(saveSpy).toHaveBeenCalledWith('CSF-Executive-Summary.pdf'); + delete jsPDF.API.save; + }); +});