import { beforeEach, describe, expect, it, vi } from 'vitest'; import { choosePdfSliceHeight, pdfBlockBreakOffsets } from '../pdfExport.ts'; const mocks = vi.hoisted(() => ({ addImage: vi.fn(), save: vi.fn(), jsPDF: vi.fn(), html2canvas: vi.fn(), })); vi.mock('jspdf', () => ({ jsPDF: mocks.jsPDF, })); vi.mock('html2canvas', () => ({ default: mocks.html2canvas, })); describe('exportCardToPdf compression', () => { beforeEach(() => { mocks.addImage.mockReset(); mocks.save.mockReset(); mocks.jsPDF.mockReset(); mocks.html2canvas.mockReset(); mocks.jsPDF.mockImplementation(function JsPDF() { return { internal: { pageSize: { getWidth: () => 210, getHeight: () => 297 } }, addPage: vi.fn(), addImage: mocks.addImage, save: mocks.save, }; }); mocks.html2canvas.mockImplementation(async () => { const canvas = document.createElement('canvas'); canvas.width = 800; canvas.height = 400; return canvas; }); }); it('embeds JPEG pages in a compressed jsPDF document', async () => { vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({ fillStyle: '', fillRect: vi.fn(), drawImage: vi.fn(), } as unknown as CanvasRenderingContext2D); const toDataURL = vi .spyOn(HTMLCanvasElement.prototype, 'toDataURL') .mockReturnValue('data:image/jpeg;base64,xx'); const { exportCardToPdf } = await import('../pdfExport.ts'); await exportCardToPdf(document.createElement('div'), 'Laporan.pdf'); expect(mocks.jsPDF).toHaveBeenCalledWith(expect.objectContaining({ compress: true })); expect(toDataURL).toHaveBeenCalledWith('image/jpeg', expect.any(Number)); const quality = toDataURL.mock.calls.find((call) => call[0] === 'image/jpeg')?.[1] as number; expect(quality).toBeGreaterThan(0); expect(quality).toBeLessThan(1); expect(mocks.addImage).toHaveBeenCalledWith( expect.any(String), 'JPEG', expect.any(Number), expect.any(Number), expect.any(Number), expect.any(Number) ); expect(mocks.save).toHaveBeenCalledWith('Laporan.pdf'); toDataURL.mockRestore(); vi.restoreAllMocks(); }); }); describe('pdf page breaks', () => { it('does not defer an oversized table past the header', () => { const usableHeight = 1200; const breaks = pdfBlockBreakOffsets([{ top: 80, bottom: 5000 }], usableHeight); expect(breaks).not.toContain(80); expect(breaks).toContain(5000); expect(choosePdfSliceHeight(0, 5000, usableHeight, breaks)).toBe(usableHeight); }); it('keeps a small chart whole by breaking before it when it no longer fits', () => { const usableHeight = 400; const breaks = pdfBlockBreakOffsets([{ top: 350, bottom: 500 }], usableHeight); expect(breaks).toEqual(expect.arrayContaining([350, 500])); expect(choosePdfSliceHeight(0, 900, usableHeight, breaks)).toBe(350); }); });