161 lines
5.4 KiB
JavaScript
161 lines
5.4 KiB
JavaScript
'use strict';
|
|
const assert = require('node:assert/strict');
|
|
const { buildOverview } = require('./days.js');
|
|
|
|
const ICS = `BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PRODID:-//test//test//EN
|
|
BEGIN:VEVENT
|
|
UID:standup-1@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260105T090000
|
|
DTEND;TZID=UTC:20260105T093000
|
|
SUMMARY:Standup
|
|
RRULE:FREQ=WEEKLY;BYDAY=MO;COUNT=5
|
|
EXDATE;TZID=UTC:20260119T090000
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:standup-1@test
|
|
RECURRENCE-ID;TZID=UTC:20260126T090000
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260127T140000
|
|
DTEND;TZID=UTC:20260127T143000
|
|
SUMMARY:Standup (moved)
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:trip-1@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;VALUE=DATE:20260110
|
|
DTEND;VALUE=DATE:20260112
|
|
SUMMARY:💜 Weekend trip
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:flight-1@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260106T100000
|
|
DTEND;TZID=UTC:20260106T103000
|
|
SUMMARY:✈️ Flight to Spain
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:party-1@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260108T200000
|
|
DTEND;TZID=UTC:20260108T220000
|
|
SUMMARY:🎉 Party
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:combo-1@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260115T090000
|
|
DTEND;TZID=UTC:20260115T093000
|
|
SUMMARY:✈️💜 Trip with Tessa
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:combo-2@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;VALUE=DATE:20260122
|
|
DTEND;VALUE=DATE:20260123
|
|
SUMMARY:✈️ 💜 Madeira
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:overlap-a@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260109T090000
|
|
DTEND;TZID=UTC:20260109T100000
|
|
SUMMARY:✈️ Overlap A
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:overlap-b@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260109T093000
|
|
DTEND;TZID=UTC:20260109T110000
|
|
SUMMARY:💜 Overlap B
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:overlap-resolved-a@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260113T090000
|
|
DTEND;TZID=UTC:20260113T100000
|
|
SUMMARY:✈️ Overlap C nc
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:quickadd-1@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART:20260116T070000Z
|
|
DTEND:20260116T080000Z
|
|
SUMMARY:Quick add
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:overlap-resolved-b@test
|
|
DTSTAMP:20260101T000000Z
|
|
DTSTART;TZID=UTC:20260113T093000
|
|
DTEND;TZID=UTC:20260113T110000
|
|
SUMMARY:💜 Overlap D
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
`;
|
|
|
|
const tagsConfig = {
|
|
'💜': { label: 'Partner Tessa', color: 'magenta' },
|
|
'✈️': { label: 'Holidays', color: 'aqua' },
|
|
};
|
|
|
|
const overview = buildOverview(ICS, tagsConfig, '2026-01-01', '2026-01-31');
|
|
|
|
// plain weekly recurrence: morning segment, untagged -> muted
|
|
assert.deepEqual(overview['2026-01-05'].segments, ['muted', null, null]);
|
|
assert.match(overview['2026-01-05'].tooltip, /10:00 Standup/);
|
|
|
|
// EXDATE removes this occurrence entirely (nothing else touches this day)
|
|
assert.equal(overview['2026-01-19'], undefined);
|
|
|
|
// RECURRENCE-ID moved the Jan 26 occurrence away
|
|
assert.equal(overview['2026-01-26'], undefined);
|
|
|
|
// ...to Jan 27, 14:00 -> afternoon segment
|
|
assert.deepEqual(overview['2026-01-27'].segments, [null, 'muted', null]);
|
|
assert.match(overview['2026-01-27'].tooltip, /15:00 Standup \(moved\)/);
|
|
|
|
// all-day multi-day event with a known leading emoji lights every segment, both spanned days
|
|
assert.deepEqual(overview['2026-01-10'].segments, ['magenta', 'magenta', 'magenta']);
|
|
assert.deepEqual(overview['2026-01-11'].segments, ['magenta', 'magenta', 'magenta']);
|
|
assert.match(overview['2026-01-10'].tooltip, /All day 💜 Weekend trip/);
|
|
|
|
// DTEND is exclusive: Jan 12 is NOT part of the trip, but it IS a Monday standup
|
|
assert.deepEqual(overview['2026-01-12'].segments, ['muted', null, null]);
|
|
|
|
// leading emoji that IS in tagsConfig takes that entry's color, not re-prefixed in tooltip
|
|
assert.deepEqual(overview['2026-01-06'].segments, ['aqua', null, null]);
|
|
assert.equal(overview['2026-01-06'].tooltip, '11:00 ✈️ Flight to Spain');
|
|
|
|
// leading emoji NOT in tagsConfig still stands out (not muted) via the fallback color
|
|
assert.deepEqual(overview['2026-01-08'].segments, [null, null, 'blue']);
|
|
assert.equal(overview['2026-01-08'].tooltip, '21:00 🎉 Party');
|
|
|
|
// two consecutive leading emoji (no space between) split the segment into both colors
|
|
assert.deepEqual(overview['2026-01-15'].segments, [['aqua', 'magenta'], null, null]);
|
|
assert.equal(overview['2026-01-15'].tooltip, '10:00 ✈️💜 Trip with Tessa');
|
|
// single-emoji case (Jan 6, asserted above) still resolves to a plain string, not an array —
|
|
// the split path only triggers for a genuine two-emoji run.
|
|
assert.equal(Array.isArray(overview['2026-01-06'].segments[0]), false);
|
|
|
|
// two leading emoji WITH a space between them (how this calendar actually types combos) still split
|
|
assert.deepEqual(overview['2026-01-22'].segments, [['aqua', 'magenta'], ['aqua', 'magenta'], ['aqua', 'magenta']]);
|
|
assert.equal(overview['2026-01-22'].tooltip, 'All day ✈️ 💜 Madeira');
|
|
|
|
// two tagged events overlapping in time -> conflict flagged on their shared day
|
|
assert.equal(overview['2026-01-09'].conflict, true);
|
|
// a day with only one tagged event -> no conflict
|
|
assert.equal(overview['2026-01-06'].conflict, false);
|
|
// untagged (muted) events don't count as a conflict even when they overlap
|
|
assert.equal(overview['2026-01-05'].conflict, false);
|
|
|
|
// "nc" as a standalone word in the title marks that event's conflict as resolved
|
|
assert.equal(overview['2026-01-13'].conflict, false);
|
|
|
|
// bare Z-suffixed timestamp (no TZID, how Google's quick-add events are exported) is an
|
|
// absolute instant too -> displayed in Amsterdam local time (08:00 CET), not raw UTC (07:00)
|
|
assert.equal(overview['2026-01-16'].tooltip, '08:00 Quick add');
|
|
|
|
console.log('all assertions passed');
|