mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: keep all-day events all-day on Stalwart 0.16
This commit is contained in:
@@ -251,7 +251,8 @@ describe('calendar operations', () => {
|
||||
id: 's1', baseEventId: 'base', recurrenceId: '2026-03-02', start: '2026-03-02T00:00:00',
|
||||
timeZone: 'Europe/Berlin', title: 'Daily',
|
||||
} as any;
|
||||
const single = { id: 's9', baseEventId: 'other', start: '2026-03-02T09:00:00', title: 'Once' } as any;
|
||||
// Stalwart 1.0 returns a one-off event under its own id.
|
||||
const single = { id: 's9', baseEventId: null, start: '2026-03-02T09:00:00', title: 'Once' } as any;
|
||||
|
||||
const [hydrated, untouched] = await hydrateExpandedOccurrences([occurrence, single]);
|
||||
|
||||
@@ -267,10 +268,40 @@ describe('calendar operations', () => {
|
||||
});
|
||||
|
||||
it('skips the round trip when nothing recurs', async () => {
|
||||
const single = { id: 's9', baseEventId: 'other', start: '2026-03-02T09:00:00' } as any;
|
||||
expect(await hydrateExpandedOccurrences([single])).toEqual([single]);
|
||||
const single = { id: 's9', baseEventId: null, start: '2026-03-02T09:00:00' } as any;
|
||||
const own = { id: 'd', baseEventId: 'd', start: '2026-03-02T09:00:00' } as any;
|
||||
expect(await hydrateExpandedOccurrences([single, own])).toEqual([single, own]);
|
||||
expect(mockRequest).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Stalwart 0.16 hands a one-off event out under a synthetic id, with no
|
||||
// recurrenceId and no showWithoutTime, in the query's time zone: the
|
||||
// all-day "Company holiday" read as 00:00 to 00:00.
|
||||
it('gives a one-off all-day event under a synthetic id its all-day flag back', async () => {
|
||||
mockRequest.mockResolvedValue({
|
||||
methodResponses: [['CalendarEvent/get', {
|
||||
list: [
|
||||
{ id: 'c', showWithoutTime: true, duration: 'P1D' },
|
||||
{ id: 'd', timeZone: 'Europe/Berlin', duration: 'PT1H' },
|
||||
],
|
||||
}, '0']],
|
||||
});
|
||||
const holiday = {
|
||||
id: 'h1pceeaaaaac', baseEventId: 'c', '@type': 'Event', title: 'Company holiday',
|
||||
start: '2026-11-01T00:00:00', duration: 'P1D', timeZone: 'Europe/Berlin',
|
||||
utcStart: '2026-10-31T23:00:00Z', utcEnd: '2026-11-01T23:00:00Z',
|
||||
} as any;
|
||||
const review = {
|
||||
id: 'h1pceeaaaaad', baseEventId: 'd', start: '2026-11-01T14:00:00', duration: 'PT1H', timeZone: 'Europe/Berlin',
|
||||
} as any;
|
||||
|
||||
const [allDay, timed] = await hydrateExpandedOccurrences([holiday, review]);
|
||||
|
||||
expect(mockRequest.mock.calls[0][0][0][1]).toMatchObject({ ids: ['c', 'd'] });
|
||||
expect(allDay).toMatchObject({ id: 'h1pceeaaaaac', showWithoutTime: true, timeZone: null, start: '2026-11-01T00:00:00' });
|
||||
expect(allDay.recurrenceRules).toBeUndefined();
|
||||
expect(timed).toBe(review);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEvents for expanded occurrences', () => {
|
||||
|
||||
@@ -937,6 +937,28 @@ live('live Stalwart calendar', () => {
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
describe('an all-day event', () => {
|
||||
// 0.16 expands a one-off event to a synthetic id without showWithoutTime;
|
||||
// the detail sheet then said 00:00 - 00:00 and the editor's switch was off.
|
||||
it('stays all-day on the calendar and in the editor', async () => {
|
||||
await connectSingleton();
|
||||
const created = await createEvent({
|
||||
'@type': 'Event',
|
||||
title: `${RUN} all day`,
|
||||
start: '2027-11-10T00:00:00',
|
||||
duration: 'P1D',
|
||||
showWithoutTime: true,
|
||||
} as Partial<CalendarEvent>, calendarId, false);
|
||||
const uid = created.uid ?? (await getEvents([created.id]))[0].uid!;
|
||||
const [event] = await loadOccurrences(uid, '2027-11-08T00:00:00Z', '2027-11-15T00:00:00Z');
|
||||
expect(event, 'the event is on the calendar').toBeTruthy();
|
||||
expect(event.showWithoutTime).toBe(true);
|
||||
expect(event.timeZone ?? null).toBeNull();
|
||||
expect(editorTimesFromEvent(event).allDay).toBe(true);
|
||||
expect(getEventStartDate(event).getDate()).toBe(10);
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
describe('a subscribed feed', () => {
|
||||
const FEED_URL = 'https://feeds.invalid/live.ics';
|
||||
let feed = '';
|
||||
|
||||
+5
-2
@@ -865,7 +865,10 @@ async function queryExpandedRange(
|
||||
* rule / overrides (and all-day ones without showWithoutTime). Fetch those
|
||||
* from the base events in one batched get and merge them in, so an
|
||||
* occurrence carries the same series context the device-side expansion
|
||||
* copies from its master.
|
||||
* copies from its master. Stalwart 0.16 also hands a one-off event out
|
||||
* under a synthetic id, without a recurrenceId and without
|
||||
* showWithoutTime: an all-day one would read as 00:00 to 00:00, so its base
|
||||
* is fetched as well.
|
||||
*/
|
||||
export async function hydrateExpandedOccurrences(
|
||||
events: CalendarEvent[],
|
||||
@@ -873,7 +876,7 @@ export async function hydrateExpandedOccurrences(
|
||||
): Promise<CalendarEvent[]> {
|
||||
const baseIds = Array.from(new Set(
|
||||
events
|
||||
.filter((event) => isServerRecurrenceInstance(event) && event.recurrenceId)
|
||||
.filter((event) => isServerRecurrenceInstance(event))
|
||||
.map((event) => event.baseEventId as string),
|
||||
));
|
||||
if (baseIds.length === 0) return events;
|
||||
|
||||
@@ -349,9 +349,16 @@ export function hydrateRecurrenceInstances(
|
||||
bases: ReadonlyMap<string, Partial<CalendarEvent>>,
|
||||
): CalendarEvent[] {
|
||||
return collapseDuplicateInstances(instances, bases).map((instance) => {
|
||||
if (!isServerRecurrenceInstance(instance) || !instance.recurrenceId) return instance;
|
||||
if (!isServerRecurrenceInstance(instance)) return instance;
|
||||
const base = instance.baseEventId ? bases.get(instance.baseEventId) : undefined;
|
||||
if (!base) return instance;
|
||||
// Stalwart 0.16 expands a one-off event to a synthetic id too, without
|
||||
// a recurrenceId and without showWithoutTime, in the query's time zone.
|
||||
if (!instance.recurrenceId) {
|
||||
return base.showWithoutTime
|
||||
? { ...instance, showWithoutTime: true, timeZone: base.timeZone ?? null }
|
||||
: instance;
|
||||
}
|
||||
|
||||
const hydrated: CalendarEvent = {
|
||||
...instance,
|
||||
|
||||
Reference in New Issue
Block a user