109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
import { describe, it, expect } from 'vitest'
|
|
import { readFileSync } from 'node:fs'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { dirname, join } from 'node:path'
|
|
import { parseICS } from '../src/lib/ical-io.js'
|
|
import { serializeICS } from '../src/lib/ical-io.js'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const GOLDEN = readFileSync(join(__dirname, 'golden-org.ics'), 'utf-8')
|
|
|
|
describe('parseICS', () => {
|
|
const { meta, events } = parseICS(GOLDEN)
|
|
|
|
it('parses calendar meta', () => {
|
|
expect(meta.prodid).toBe('-//Allocate//iCal4j 1.0//EN')
|
|
expect(meta.version).toBe('2.0')
|
|
expect(meta.calscale).toBe('GREGORIAN')
|
|
expect(meta.vtimezones.length).toBe(1)
|
|
})
|
|
|
|
it('parses all 61 events', () => {
|
|
expect(events).toHaveLength(61)
|
|
})
|
|
|
|
it('parses first event fields correctly', () => {
|
|
const e = events[0]
|
|
expect(e.summary).toBe('ACC INFO SYS, Tutorial')
|
|
expect(e.location).toBe('CA_B_B471')
|
|
expect(e.dtstartDate).toBe('2026-07-27')
|
|
expect(e.dtstartTime).toBe('18:00')
|
|
expect(e.dtendTime).toBe('20:00')
|
|
expect(e.tzid).toBe('Australia/Melbourne')
|
|
expect(e.rrule).toBeNull()
|
|
expect(e.exdates).toEqual([])
|
|
})
|
|
|
|
it('preserves description with escaped commas', () => {
|
|
const e = events[0]
|
|
expect(e.description).toContain('ACF2400_CA_S2_ON-CAMPUS')
|
|
expect(e.description).toContain('ACC INFO SYS')
|
|
})
|
|
|
|
it('captures dtstamp in _raw', () => {
|
|
expect(events[0]._raw.dtstamp).toBe('20260711T085008Z')
|
|
})
|
|
|
|
it('events have UIDs', () => {
|
|
expect(events[0].uid).toBe('uid0')
|
|
expect(events[60].uid).toBe('uid60')
|
|
})
|
|
})
|
|
|
|
describe('serializeICS round-trip', () => {
|
|
it('round-trips: parse(serialize(parse(x))) equals parse(x)', () => {
|
|
const original = parseICS(GOLDEN)
|
|
const text = serializeICS(original)
|
|
const reparsed = parseICS(text)
|
|
|
|
expect(reparsed.events).toHaveLength(original.events.length)
|
|
// Spot-check first event
|
|
const e0 = reparsed.events[0]
|
|
expect(e0.summary).toBe(original.events[0].summary)
|
|
expect(e0.dtstartDate).toBe(original.events[0].dtstartDate)
|
|
expect(e0.dtstartTime).toBe(original.events[0].dtstartTime)
|
|
expect(e0.tzid).toBe(original.events[0].tzid)
|
|
expect(e0._raw.dtstamp).toBe(original.events[0]._raw.dtstamp)
|
|
})
|
|
|
|
it('preserves VTIMEZONE in output', () => {
|
|
const { meta } = parseICS(serializeICS(parseICS(GOLDEN)))
|
|
expect(meta.vtimezones).toHaveLength(1)
|
|
})
|
|
|
|
it('serializes a manually-built recurring event', () => {
|
|
const ev = {
|
|
uid: 'test-1',
|
|
summary: 'Test Event',
|
|
location: 'Room A',
|
|
description: 'Desc',
|
|
dtstartDate: '2026-07-27',
|
|
dtstartTime: '09:00',
|
|
dtendTime: '10:30',
|
|
tzid: 'UTC',
|
|
rrule: { freq: 'WEEKLY', interval: 1, byday: 'MO', untilDate: '2026-10-19' },
|
|
exdates: ['2026-09-21'],
|
|
_raw: { dtstamp: '20260711T085008Z' },
|
|
}
|
|
const text = serializeICS({
|
|
meta: {
|
|
prodid: '-//Test//EN', version: '2.0', calscale: 'GREGORIAN',
|
|
method: null, xWrCalname: null, xWrTimezone: null, vtimezones: [],
|
|
},
|
|
events: [ev],
|
|
})
|
|
expect(text).toContain('BEGIN:VEVENT')
|
|
expect(text).toContain('SUMMARY:Test Event')
|
|
expect(text).toContain('RRULE:FREQ=WEEKLY')
|
|
expect(text).toContain('BYDAY=MO')
|
|
expect(text).toContain('EXDATE')
|
|
expect(text).toContain('DTSTART:20260727T090000Z')
|
|
|
|
// Re-parse and verify
|
|
const reparsed = parseICS(text)
|
|
expect(reparsed.events[0].rrule.freq).toBe('WEEKLY')
|
|
expect(reparsed.events[0].rrule.byday).toBe('MO')
|
|
expect(reparsed.events[0].exdates).toContain('2026-09-21')
|
|
})
|
|
})
|