import { describe, it, expect } from 'vitest' import { generateIcs } from '../useIcalDownload' describe('generateIcs', () => { const baseEvent = { eventToken: '550e8400-e29b-41d4-a716-446655440000', title: 'Sommerfest am See', dateTime: '2026-07-15T18:00:00+02:00', location: 'Stadtpark Berlin', description: 'Bring your own drinks', } it('generates valid VCALENDAR wrapper', () => { const ics = generateIcs(baseEvent) expect(ics).toMatch(/^BEGIN:VCALENDAR\r\n/) expect(ics).toMatch(/\r\nEND:VCALENDAR\r\n$/) }) it('includes VERSION and PRODID', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('VERSION:2.0\r\n') expect(ics).toContain('PRODID:-//fete//EN\r\n') }) it('generates valid VEVENT block', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('BEGIN:VEVENT\r\n') expect(ics).toContain('END:VEVENT\r\n') }) it('sets UID from eventToken', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('UID:550e8400-e29b-41d4-a716-446655440000@fete\r\n') }) it('sets DTSTART in UTC format', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('DTSTART:20260715T160000Z\r\n') }) it('does NOT include DTEND or DURATION', () => { const ics = generateIcs(baseEvent) expect(ics).not.toContain('DTEND') expect(ics).not.toContain('DURATION') }) it('sets SUMMARY from title', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('SUMMARY:Sommerfest am See\r\n') }) it('sets LOCATION when present', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('LOCATION:Stadtpark Berlin\r\n') }) it('sets DESCRIPTION when present', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('DESCRIPTION:Bring your own drinks\r\n') }) it('omits LOCATION when not provided', () => { const { location: _location, ...noLocation } = baseEvent const ics = generateIcs(noLocation) expect(ics).not.toContain('LOCATION') }) it('omits DESCRIPTION when not provided', () => { const { description: _description, ...noDesc } = baseEvent const ics = generateIcs(noDesc) expect(ics).not.toContain('DESCRIPTION') }) it('includes SEQUENCE:0', () => { const ics = generateIcs(baseEvent) expect(ics).toContain('SEQUENCE:0\r\n') }) it('includes DTSTAMP in UTC format', () => { const ics = generateIcs(baseEvent) expect(ics).toMatch(/DTSTAMP:\d{8}T\d{6}Z\r\n/) }) it('escapes commas in text fields', () => { const ics = generateIcs({ ...baseEvent, title: 'Hello, World' }) expect(ics).toContain('SUMMARY:Hello\\, World\r\n') }) it('escapes semicolons in text fields', () => { const ics = generateIcs({ ...baseEvent, description: 'foo; bar' }) expect(ics).toContain('DESCRIPTION:foo\\; bar\r\n') }) it('escapes backslashes in text fields', () => { const ics = generateIcs({ ...baseEvent, title: 'path\\to' }) expect(ics).toContain('SUMMARY:path\\\\to\r\n') }) it('escapes newlines in text fields', () => { const ics = generateIcs({ ...baseEvent, description: 'line1\nline2' }) expect(ics).toContain('DESCRIPTION:line1\\nline2\r\n') }) it('produces deterministic output for the same input', () => { const ics1 = generateIcs(baseEvent) const ics2 = generateIcs(baseEvent) // DTSTAMP changes with time, so strip it for comparison const strip = (s: string) => s.replace(/DTSTAMP:\d{8}T\d{6}Z\r\n/, '') expect(strip(ics1)).toBe(strip(ics2)) }) it('uses CRLF line endings throughout', () => { const ics = generateIcs(baseEvent) const lines = ics.split('\r\n') // Every "line" split by CRLF should not contain a bare LF for (const line of lines) { expect(line).not.toContain('\n') } }) })