Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/Toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
6 changes: 5 additions & 1 deletion src/utils/executiveSummaryPDF.js
Original file line number Diff line number Diff line change
@@ -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)',
Expand Down
36 changes: 36 additions & 0 deletions src/utils/executiveSummaryPDF.test.js
Original file line number Diff line number Diff line change
@@ -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;
});
});
Loading