mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-04 16:06:41 +00:00
Every save in the event editor rebuilt the participants with new ids and put every attendee back to needs-action, the user's own answer on an invitation included. Saves now keep each attendee's id and answer; new attendees are asked. As on the web, only an organizer who moves an event (its start, end, all-day or recurrence rule; not a time zone rewrite, and not a zero-length event) asks the others again. The series scopes did not fit that: - "All events" wrote the opened occurrence's start, length, zone and participants onto the series: a stretched occurrence stretched every one, and one day's decline spread to the whole series. The series now keeps its own list and takes only the attendees the user added or removed, and its times change only when the occurrence moved (by the same delta), its rule only when it changed. - "This and following" makes a new series, which Stalwart sends only to attendees who have not answered: its attendees are asked again. - Stalwart sends a new occurrence override, the first change to one occurrence, only to attendees who have not answered (0.16.6 to 1.0). A change that keeps answers now goes out in two writes: the override as the occurrence stands without scheduling, then the change as an update of it, to the occurrence looked up again. Checked live on 1.0 and 0.16.23. - A save of an invitation someone else organizes writes no participants while the user left them alone, and never asks for scheduling, so the organizer gets no reply for a changed alert. The switch to send invitations is hidden there. Also: - a reminder change on one occurrence of a series that uses the calendar's default alerts turns the defaults off for the series first (keeping what it shows under ids of its own); 1.0 kept showing the defaults - all-day and floating occurrences expanded on the device no longer carry the master's utcStart/utcEnd, so their reminders are scheduled for each day; master instants that are not the master's own (0.16.23's year 32548, or the next occurrence's) are ignored - the editor warns when the attendees and the organizer go over the calendars capability's maxParticipantsPerEvent, and a refusal for it (Stalwart 1.0 enforces it, answers included) is explained in the editor, the series dialogs, the detail sheet and the invitation banner, in all languages; a failed save used to fail silently - refused event creates, updates and answers keep their SetError type - Calendar/get asks for its properties by name
53 lines
2.5 KiB
TypeScript
53 lines
2.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { translate, type MessageParams } from '../../i18n';
|
|
import type { RecurrenceRule } from '../../api/types';
|
|
import { buildRecurrenceSummary, recurrenceEndLabel, recurrenceIntervalLabel } from '../recurrence';
|
|
|
|
const t = (key: string, fallback?: string, params?: MessageParams) =>
|
|
translate('en', key, fallback, params);
|
|
|
|
const rule = (r: Partial<RecurrenceRule>): RecurrenceRule => ({ '@type': 'RecurrenceRule', frequency: 'weekly', ...r });
|
|
|
|
describe('recurrence summary', () => {
|
|
it('uses plurals for the interval and the occurrence count', () => {
|
|
expect(recurrenceIntervalLabel('daily', 1, t)).toBe('Daily');
|
|
expect(recurrenceIntervalLabel('weekly', 3, t)).toBe('Every 3 weeks');
|
|
expect(recurrenceEndLabel(rule({ count: 1 }), t)).toBe('1 occurrence');
|
|
expect(recurrenceEndLabel(rule({ count: 12 }), t)).toBe('12 occurrences');
|
|
});
|
|
|
|
it('builds the summary from the catalog fragments', () => {
|
|
expect(buildRecurrenceSummary(rule({
|
|
interval: 2,
|
|
byDay: [{ day: 'we' }, { day: 'mo' }],
|
|
count: 5,
|
|
}), t)).toBe('Every 2 weeks on Mon, Wed · 5 occurrences');
|
|
expect(buildRecurrenceSummary(rule({
|
|
frequency: 'monthly',
|
|
byDay: [{ day: 'th', nthOfPeriod: 3 }],
|
|
until: '2027-03-01T23:59:59',
|
|
}), t)).toBe('Monthly on the third Thursday · Until Mar 1, 2027');
|
|
expect(buildRecurrenceSummary(rule({ frequency: 'hourly' }), t)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('recurrenceRulesEqual', () => {
|
|
it('takes the editor\'s rewrite of an unchanged rule for the server\'s normalised copy', async () => {
|
|
const { recurrenceRulesEqual } = await import('../recurrence');
|
|
const stored = [{
|
|
'@type': 'RecurrenceRule' as const, frequency: 'weekly', interval: 1, rscale: 'gregorian', skip: 'omit',
|
|
firstDayOfWeek: 'mo', byDay: [],
|
|
}];
|
|
expect(recurrenceRulesEqual(stored, [{ frequency: 'weekly', interval: 1 }])).toBe(true);
|
|
expect(recurrenceRulesEqual(undefined, null)).toBe(true);
|
|
expect(recurrenceRulesEqual(stored, [{ frequency: 'daily', interval: 1 }])).toBe(false);
|
|
expect(recurrenceRulesEqual(stored, [{ frequency: 'weekly', interval: 2 }])).toBe(false);
|
|
expect(recurrenceRulesEqual(stored, [{ frequency: 'weekly', count: 5 }])).toBe(false);
|
|
expect(recurrenceRulesEqual(stored, undefined)).toBe(false);
|
|
expect(recurrenceRulesEqual(
|
|
[{ frequency: 'weekly', byDay: [{ day: 'mo' }, { day: 'we' }] }],
|
|
[{ frequency: 'weekly', byDay: [{ day: 'mo' }, { day: 'fr' }] }],
|
|
)).toBe(false);
|
|
});
|
|
});
|