fix: dedupe scheduling emails, Stalwart-compatible calendar filters
This commit is contained in:
@@ -323,6 +323,89 @@ describe('expandRecurringEvents', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// excludedRecurrenceRules (EXRULE)
|
||||
// -----------------------------------------------------------------------
|
||||
describe('excludedRecurrenceRules', () => {
|
||||
it('removes occurrences generated by excluded rules', () => {
|
||||
const event = makeEvent({
|
||||
recurrenceRules: [{ '@type': 'RecurrenceRule', frequency: 'daily' } as any],
|
||||
excludedRecurrenceRules: [{
|
||||
'@type': 'RecurrenceRule',
|
||||
frequency: 'weekly',
|
||||
byDay: [{ day: 'tu' }],
|
||||
} as any],
|
||||
});
|
||||
// Jan 6-12 2025: Mon-Sun, Tuesday Jan 7 excluded
|
||||
const result = expand(event, '2025-01-06T00:00:00', '2025-01-13T00:00:00');
|
||||
const days = result.map(e => e.start.substring(0, 10));
|
||||
expect(days).not.toContain('2025-01-07');
|
||||
expect(days).toContain('2025-01-06');
|
||||
expect(days).toContain('2025-01-08');
|
||||
expect(result).toHaveLength(6);
|
||||
});
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Long-running series
|
||||
// -----------------------------------------------------------------------
|
||||
describe('long-running series', () => {
|
||||
it('expands a daily series started years before the visible range', () => {
|
||||
const event = makeEvent({
|
||||
start: '2022-01-03T09:00:00',
|
||||
recurrenceRules: [{ '@type': 'RecurrenceRule', frequency: 'daily' } as any],
|
||||
});
|
||||
const result = expand(event, '2025-06-02T00:00:00', '2025-06-05T00:00:00');
|
||||
expect(starts(result)).toEqual([
|
||||
'2025-06-02T09:00:00',
|
||||
'2025-06-03T09:00:00',
|
||||
'2025-06-04T09:00:00',
|
||||
]);
|
||||
});
|
||||
|
||||
it('expands a weekly series started years before the visible range', () => {
|
||||
const event = makeEvent({
|
||||
start: '2020-01-06T09:00:00', // Monday
|
||||
recurrenceRules: [{ '@type': 'RecurrenceRule', frequency: 'weekly' } as any],
|
||||
});
|
||||
const result = expand(event, '2025-06-01T00:00:00', '2025-06-30T00:00:00');
|
||||
const days = result.map(e => e.start.substring(0, 10));
|
||||
expect(days).toEqual(['2025-06-02', '2025-06-09', '2025-06-16', '2025-06-23']);
|
||||
});
|
||||
|
||||
it('still respects count for old series (no fast-forward shortcut)', () => {
|
||||
const event = makeEvent({
|
||||
start: '2025-01-06T09:00:00',
|
||||
recurrenceRules: [{ '@type': 'RecurrenceRule', frequency: 'daily', count: 5 } as any],
|
||||
});
|
||||
// Range far after the 5 occurrences ran out
|
||||
const result = expand(event, '2025-06-01T00:00:00', '2025-06-30T00:00:00');
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Event-timezone DST handling
|
||||
// -----------------------------------------------------------------------
|
||||
describe('event timezone DST handling', () => {
|
||||
it('keeps wall time across a DST transition in the event timezone', () => {
|
||||
// America/New_York: EST (UTC-5) until 2025-03-09, EDT (UTC-4) after
|
||||
const event = makeEvent({
|
||||
start: '2025-03-03T10:00:00',
|
||||
timeZone: 'America/New_York',
|
||||
utcStart: '2025-03-03T15:00:00Z',
|
||||
utcEnd: '2025-03-03T16:00:00Z',
|
||||
recurrenceRules: [{ '@type': 'RecurrenceRule', frequency: 'weekly' } as any],
|
||||
} as Partial<CalendarEvent>);
|
||||
const result = expand(event, '2025-03-03T00:00:00', '2025-03-17T00:00:00');
|
||||
const utcStarts = result.map(e => (e as any).utcStart);
|
||||
expect(utcStarts[0]).toBe('2025-03-03T15:00:00.000Z'); // EST: 10:00 -5
|
||||
expect(utcStarts[1]).toBe('2025-03-10T14:00:00.000Z'); // EDT: 10:00 -4
|
||||
const utcEnds = result.map(e => (e as any).utcEnd);
|
||||
expect(utcEnds[1]).toBe('2025-03-10T15:00:00.000Z');
|
||||
});
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// All-day events
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user