mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 00:45:41 +00:00
refactor(reports): export through the shared tabular writer
Completes the writer extraction whose other half landed in fb21ad154.
reports.controller now builds a TabularDoc and calls TabularExportService,
so report-export.service.ts and report-export-request.util.ts are dead and
removed — HEAD was carrying both copies with the controller still on the old
one.
Reports gain CSV for free, and the PDF path now passes buildTabularFallbackPdf
as its fallback: previously it passed none, so a box without Chromium silently
returned PdfRenderService's ~900-character generic text dump instead of a
table. Adds a spec covering the CSV writer's quoting of embedded commas and
double quotes — the reason this uses ExcelJS's csv writer rather than a
hand-rolled join.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user