import { PdfRenderService } from '../billing/documents/pdf-render.service'; import { TabularDoc, TabularExportService } from './tabular-export.service'; /** The PDF path is puppeteer-backed; these specs only cover the sheet writers. */ const service = new TabularExportService(null as unknown as PdfRenderService); const doc: TabularDoc = { title: 'Bookings', description: 'every booking', label: 'test', columns: [ { key: 'ref', label: 'Reference', type: 'string' }, { key: 'customer', label: 'Customer', type: 'string' }, { key: 'amount', label: 'Amount', type: 'money' }, { key: 'gov', label: 'Government', type: 'boolean' }, ], rows: [ { ref: 'BK-1', customer: 'Acme, Inc.', amount: 1234.5, gov: true }, { ref: 'BK-2', customer: 'Quote "Q" Ltd', amount: null, gov: false }, ], kpis: [{ label: 'Bookings', value: 2 }], }; describe('TabularExportService.toCsv', () => { it('quotes a value containing the delimiter — the reason we do not hand-roll join(",")', async () => { const csv = (await service.toCsv(doc)).toString('utf8'); expect(csv).toContain('"Acme, Inc."'); }); it('escapes embedded double quotes by doubling them', async () => { const csv = (await service.toCsv(doc)).toString('utf8'); expect(csv).toContain('"Quote ""Q"" Ltd"'); }); it('starts at the header row — no KPI preamble, so the file parses as a plain table', async () => { const csv = (await service.toCsv(doc)).toString('utf8'); expect(csv.split('\n')[0]).toBe('Reference,Customer,Amount,Government'); expect(csv).not.toContain('Bookings: 2'); }); it('emits one line per row plus the header', async () => { const csv = (await service.toCsv(doc)).toString('utf8'); expect(csv.trim().split('\n').filter(Boolean)).toHaveLength(3); }); it('only the selected columns are written, in the order given', async () => { const csv = ( await service.toCsv({ ...doc, columns: [doc.columns[2], doc.columns[0]] }) ).toString('utf8'); expect(csv.split('\n')[0]).toBe('Amount,Reference'); }); }); describe('TabularExportService.toXlsx', () => { it('writes a real xlsx (a zip, so it starts with the PK magic bytes)', async () => { const buffer = await service.toXlsx(doc); expect(buffer.subarray(0, 2).toString('utf8')).toBe('PK'); expect(buffer.length).toBeGreaterThan(1000); }); it('a title longer than Excel\'s 31-char sheet-name limit does not throw', async () => { const longTitle = 'A'.repeat(60); await expect(service.toXlsx({ ...doc, title: longTitle })).resolves.toBeInstanceOf(Buffer); }); });