121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
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')
|
|
}
|
|
})
|
|
})
|