Drove src/lib/csv.ts to 100% stmts/branch/funcs/lines (was 97.95%/55.55%): - added a null-fallback row test covering every ?? default (activityName, userName, insoleType→Kurk, pairCount→2, duration/paused→0) and the null end-time branch (line 65). - added CSV-escaping edge tests: commas, embedded quotes, and newlines inside a field stay inside the quoted cell. - added formatDuration edge (hours >99 → 100:00:00). - added a /api/export ordering-by-start-time behavior test and adopted resetDb per-test isolation in the export suite. No bugs found; CSV code behaves correctly. sessions.ts /api/export route already at 100%. Full api suite green (150 tests).
83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { buildSessionsCsv, quote, formatDuration, type SessionCsvRow } from '../src/lib/csv';
|
|
|
|
const row: SessionCsvRow = {
|
|
id: 7,
|
|
activityName: 'Frezen',
|
|
userName: 'Jan',
|
|
insoleType: 'Kurk',
|
|
pairCount: 2,
|
|
startTime: new Date('2026-06-17T08:00:00Z'),
|
|
endTime: new Date('2026-06-17T08:01:30Z'),
|
|
durationSeconds: 90,
|
|
pausedSeconds: 0,
|
|
};
|
|
|
|
describe('buildSessionsCsv', () => {
|
|
it('omits the Worker column by default (legacy header)', () => {
|
|
const lines = buildSessionsCsv([row]).split('\n');
|
|
expect(lines[0]).toBe(
|
|
'"ID","Task","Insole Type","No. of Insoles","Date","Total Duration","Paused Duration","Start Time","End Time"',
|
|
);
|
|
expect(lines[1]).toContain('"Frezen"');
|
|
expect(lines[1]).toContain('"00:01:30"');
|
|
expect(lines[1]).not.toContain('"Jan"');
|
|
});
|
|
|
|
it('prepends a Worker column when includeWorker is true', () => {
|
|
const lines = buildSessionsCsv([row], { includeWorker: true }).split('\n');
|
|
expect(lines[0]).toBe(
|
|
'"Worker","ID","Task","Insole Type","No. of Insoles","Date","Total Duration","Paused Duration","Start Time","End Time"',
|
|
);
|
|
expect(lines[1].startsWith('"Jan"')).toBe(true);
|
|
});
|
|
|
|
it('applies legacy fallbacks for null fields and blanks a missing end time', () => {
|
|
// Every nullable field null (+ pairCount null via cast, since the type is non-null
|
|
// but the code still carries a legacy `?? 2` default). includeWorker exercises the
|
|
// userName fallback too. Covers each `??` fallback branch and the null-end-time branch.
|
|
const emptyRow = {
|
|
id: 1,
|
|
activityName: null,
|
|
userName: null,
|
|
insoleType: null,
|
|
pairCount: null,
|
|
startTime: new Date('2026-06-17T08:00:00Z'),
|
|
endTime: null,
|
|
durationSeconds: null,
|
|
pausedSeconds: null,
|
|
} as unknown as SessionCsvRow;
|
|
|
|
const cells = buildSessionsCsv([emptyRow], { includeWorker: true }).split('\n')[1].split(',');
|
|
expect(cells[0]).toBe('""'); // Worker → ''
|
|
expect(cells[1]).toBe('"1"'); // ID
|
|
expect(cells[2]).toBe('""'); // Task → ''
|
|
expect(cells[3]).toBe('"Kurk"'); // Insole Type → legacy default
|
|
expect(cells[4]).toBe('"2"'); // No. of Insoles → legacy default
|
|
expect(cells[6]).toBe('"00:00:00"'); // Total Duration → 0
|
|
expect(cells[7]).toBe('"00:00:00"'); // Paused Duration → 0
|
|
expect(cells[9]).toBe('""'); // End Time → '' (null end)
|
|
});
|
|
|
|
it('escapes commas, quotes and newlines inside a field', () => {
|
|
const tricky: SessionCsvRow = { ...row, activityName: 'Frezen, "spoed"\n2e regel' };
|
|
const csv = buildSessionsCsv([tricky]);
|
|
// Comma stays inside the quoted cell, embedded quotes are doubled, newline is preserved.
|
|
expect(csv).toContain('"Frezen, ""spoed""\n2e regel"');
|
|
});
|
|
});
|
|
|
|
describe('csv helpers (edges)', () => {
|
|
it('quote wraps and escapes commas, quotes and newlines', () => {
|
|
expect(quote('a,b')).toBe('"a,b"');
|
|
expect(quote('a"b')).toBe('"a""b"');
|
|
expect(quote('a\nb')).toBe('"a\nb"');
|
|
});
|
|
|
|
it('formatDuration renders zero and lets hours exceed 99', () => {
|
|
expect(formatDuration(0)).toBe('00:00:00');
|
|
expect(formatDuration(3661)).toBe('01:01:01');
|
|
expect(formatDuration(360000)).toBe('100:00:00');
|
|
});
|
|
});
|