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'); }); });