mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
fix: remind of a task relative to its start when the reminder says so #83
This commit is contained in:
@@ -49,8 +49,8 @@ describe('parseAlertOffset / computeFireTime', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fires task alerts relative to due', () => {
|
it('fires the alerts of a task without a start relative to its due', () => {
|
||||||
const task = { ...event, due: '2026-03-02T09:00:00', alerts: undefined };
|
const task = { ...event, start: undefined as unknown as string, utcStart: undefined, due: '2026-03-02T09:00:00', alerts: undefined };
|
||||||
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT1H' })).toBe(
|
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT1H' })).toBe(
|
||||||
new Date('2026-03-02T09:00:00').getTime() - HOUR,
|
new Date('2026-03-02T09:00:00').getTime() - HOUR,
|
||||||
);
|
);
|
||||||
@@ -58,11 +58,32 @@ describe('parseAlertOffset / computeFireTime', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('reads a zoned task due in its own time zone', () => {
|
it('reads a zoned task due in its own time zone', () => {
|
||||||
const task = { ...event, due: '2026-07-01T17:00:00', timeZone: 'Europe/Berlin', alerts: undefined };
|
const task = { ...event, start: undefined as unknown as string, utcStart: undefined, due: '2026-07-01T17:00:00', timeZone: 'Europe/Berlin', alerts: undefined };
|
||||||
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT1H' })).toBe(
|
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT1H' })).toBe(
|
||||||
new Date('2026-07-01T14:00:00Z').getTime(),
|
new Date('2026-07-01T14:00:00Z').getTime(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// #83 (webmail #1328): start 20:00, due 20:15, five minutes before the start.
|
||||||
|
it('fires a task alert relative to its start from the start, and one relative to its end from the due', () => {
|
||||||
|
const task = {
|
||||||
|
...event, start: '2026-07-01T20:00:00', utcStart: undefined, due: '2026-07-01T20:15:00', timeZone: 'Europe/Berlin', alerts: undefined,
|
||||||
|
};
|
||||||
|
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT5M', relativeTo: 'start' })).toBe(
|
||||||
|
new Date('2026-07-01T17:55:00Z').getTime(),
|
||||||
|
);
|
||||||
|
// relativeTo left out means the start.
|
||||||
|
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT5M' })).toBe(
|
||||||
|
new Date('2026-07-01T17:55:00Z').getTime(),
|
||||||
|
);
|
||||||
|
expect(computeTaskFireTime(task, { '@type': 'OffsetTrigger', offset: '-PT5M', relativeTo: 'end' })).toBe(
|
||||||
|
new Date('2026-07-01T18:10:00Z').getTime(),
|
||||||
|
);
|
||||||
|
// The server's UTC start, when it gave one.
|
||||||
|
expect(computeTaskFireTime({ ...task, utcStart: '2026-07-01T18:00:00Z' }, { '@type': 'OffsetTrigger', offset: '-PT5M' })).toBe(
|
||||||
|
new Date('2026-07-01T17:55:00Z').getTime(),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getEffectiveAlerts', () => {
|
describe('getEffectiveAlerts', () => {
|
||||||
@@ -91,6 +112,8 @@ describe('getUpcomingAlerts', () => {
|
|||||||
const task = {
|
const task = {
|
||||||
...event,
|
...event,
|
||||||
id: 't1',
|
id: 't1',
|
||||||
|
start: undefined as unknown as string,
|
||||||
|
utcStart: undefined,
|
||||||
due: '2026-03-01T12:00:00Z',
|
due: '2026-03-01T12:00:00Z',
|
||||||
alerts: { x: { trigger: { '@type': 'OffsetTrigger', offset: 'PT0S' }, action: 'display' } },
|
alerts: { x: { trigger: { '@type': 'OffsetTrigger', offset: 'PT0S' }, action: 'display' } },
|
||||||
};
|
};
|
||||||
@@ -109,6 +132,8 @@ describe('getUpcomingAlerts', () => {
|
|||||||
const task = {
|
const task = {
|
||||||
...untitled,
|
...untitled,
|
||||||
id: 't1',
|
id: 't1',
|
||||||
|
start: undefined as unknown as string,
|
||||||
|
utcStart: undefined,
|
||||||
due: '2026-03-01T12:00:00Z',
|
due: '2026-03-01T12:00:00Z',
|
||||||
alerts: { x: { trigger: { '@type': 'OffsetTrigger' as const, offset: 'PT0S' }, action: 'display' as const } },
|
alerts: { x: { trigger: { '@type': 'OffsetTrigger' as const, offset: 'PT0S' }, action: 'display' as const } },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -83,6 +83,16 @@ export function computeTaskFireTime(task: CalendarEvent, trigger: Alert['trigger
|
|||||||
if (trigger.offset === undefined) return null;
|
if (trigger.offset === undefined) return null;
|
||||||
const offsetMs = parseAlertOffset(trigger.offset);
|
const offsetMs = parseAlertOffset(trigger.offset);
|
||||||
if (offsetMs === null) return null;
|
if (offsetMs === null) return null;
|
||||||
|
// RFC 8984 §4.5.2: relativeTo defaults to "start", which for a Task is its
|
||||||
|
// start; "end" is its due. A task without a start counts from its due
|
||||||
|
// (#83, webmail #1328).
|
||||||
|
if (trigger.relativeTo !== 'end' && task.start) {
|
||||||
|
const start = task.utcStart
|
||||||
|
? new Date(task.utcStart)
|
||||||
|
// The start read like a due: in the task's zone, or a calendar date.
|
||||||
|
: getTaskDueDate({ due: task.start, timeZone: task.timeZone, showWithoutTime: task.showWithoutTime });
|
||||||
|
if (start && !Number.isNaN(start.getTime())) return start.getTime() + offsetMs;
|
||||||
|
}
|
||||||
const due = getTaskDueDate(task);
|
const due = getTaskDueDate(task);
|
||||||
if (!due) return null;
|
if (!due) return null;
|
||||||
return due.getTime() + offsetMs;
|
return due.getTime() + offsetMs;
|
||||||
|
|||||||
Reference in New Issue
Block a user