import {
DEFAULT_REMINDER_TIME,
WEEKDAY_LABELS,
WEEKDAY_DISPLAY_ORDER,
buildReminderBody,
buildReminderOccurrences,
countTasksDueOn,
createUniformSchedule,
describeReminderSchedule,
formatReminderTime,
getReminderTimeForDate,
isValidReminderSchedule,
isValidReminderTime,
withReminderTimeForWeekday,
} from '../reminder';
describe('buildReminderBody', () => {
it('reports when there are no tasks for today', () => {
expect(buildReminderBody(0)).toBe('No tasks scheduled for today');
});
it('uses the singular form for exactly one task', () => {
expect(buildReminderBody(1)).toBe('You have 1 task scheduled for today');
});
it('uses the plural form for multiple tasks', () => {
expect(buildReminderBody(5)).toBe('You have 5 tasks scheduled for today');
});
it('treats negative counts as zero', () => {
expect(buildReminderBody(-3)).toBe('No tasks scheduled for today');
});
});
describe('formatReminderTime', () => {
it('zero-pads hours and minutes (24h format)', () => {
expect(formatReminderTime({ hour: 8, minute: 5 })).toBe('08:05');
});
it('formats the noon default', () => {
expect(formatReminderTime(DEFAULT_REMINDER_TIME)).toBe('12:00');
});
it('formats late evening times', () => {
expect(formatReminderTime({ hour: 23, minute: 59 })).toBe('23:59');
});
});
describe('countTasksDueOn', () => {
const day = new Date(2026, 7, 17);
it('counts open tasks due anywhere within that day', () => {
const tasks = [
{ dueDate: new Date(2026, 7, 17, 0, 0).getTime() },
{ dueDate: new Date(2026, 7, 17, 23, 59).getTime() },
{ dueDate: new Date(2026, 7, 18, 0, 0).getTime() },
{ dueDate: new Date(2026, 7, 16, 23, 59).getTime() },
];
expect(countTasksDueOn(tasks, day)).toBe(2);
});
it('ignores completed tasks and tasks without a due date', () => {
const tasks = [
{ dueDate: new Date(2026, 7, 17, 9, 0).getTime() },
{ dueDate: new Date(2026, 7, 17, 9, 0).getTime(), completed: true },
{ completed: false },
];
expect(countTasksDueOn(tasks, day)).toBe(1);
});
});
describe('createUniformSchedule', () => {
it('gives every weekday the same time', () => {
const schedule = createUniformSchedule({ hour: 7, minute: 30 });
expect(schedule).toHaveLength(7);
expect(schedule.every(time => formatReminderTime(time) === '07:30')).toBe(true);
});
it('does not share one time object between weekdays', () => {
const schedule = createUniformSchedule({ hour: 7, minute: 30 });
expect(schedule[0]).not.toBe(schedule[1]);
});
});
describe('withReminderTimeForWeekday', () => {
it('changes only the given weekday', () => {
const schedule = createUniformSchedule({ hour: 12, minute: 0 });
const updated = withReminderTimeForWeekday(schedule, 1, { hour: 6, minute: 45 });
expect(formatReminderTime(updated[1])).toBe('06:45');
expect(updated.filter(time => formatReminderTime(time) === '12:00')).toHaveLength(6);
});
it('leaves the original schedule untouched', () => {
const schedule = createUniformSchedule({ hour: 12, minute: 0 });
withReminderTimeForWeekday(schedule, 1, { hour: 6, minute: 45 });
expect(formatReminderTime(schedule[1])).toBe('12:00');
});
});
describe('getReminderTimeForDate', () => {
it('picks the time configured for that weekday', () => {
const schedule = createUniformSchedule({ hour: 12, minute: 0 });
schedule[1] = { hour: 6, minute: 45 };
// 2026-08-17 is a Monday, 2026-08-18 a Tuesday.
expect(formatReminderTime(getReminderTimeForDate(schedule, new Date(2026, 7, 17)))).toBe('06:45');
expect(formatReminderTime(getReminderTimeForDate(schedule, new Date(2026, 7, 18)))).toBe('12:00');
});
});
describe('isValidReminderSchedule', () => {
it('accepts a schedule of seven valid times', () => {
expect(isValidReminderSchedule(createUniformSchedule(DEFAULT_REMINDER_TIME))).toBe(true);
});
it('rejects a schedule that does not cover every weekday', () => {
expect(isValidReminderSchedule([{ hour: 8, minute: 0 }])).toBe(false);
});
it('rejects a schedule holding an invalid time', () => {
const schedule = createUniformSchedule(DEFAULT_REMINDER_TIME);
schedule[3] = { hour: 24, minute: 0 };
expect(isValidReminderSchedule(schedule)).toBe(false);
});
it('rejects non-arrays', () => {
expect(isValidReminderSchedule({ hour: 8, minute: 0 } as never)).toBe(false);
});
});
describe('describeReminderSchedule', () => {
it('names the time when every day shares it', () => {
expect(describeReminderSchedule(createUniformSchedule({ hour: 9, minute: 0 }))).toBe('09:00 every day');
});
it('says so when the days differ', () => {
const schedule = createUniformSchedule({ hour: 9, minute: 0 });
schedule[6] = { hour: 11, minute: 0 };
expect(describeReminderSchedule(schedule)).toBe('at a time set per weekday');
});
});
describe('weekday display order', () => {
it('starts the week on Monday and ends on Sunday', () => {
expect(WEEKDAY_DISPLAY_ORDER.map(day => WEEKDAY_LABELS[day])).toEqual([
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
]);
});
});
describe('buildReminderOccurrences', () => {
const schedule = createUniformSchedule({ hour: 12, minute: 0 });
it('starts today when the reminder time is still ahead', () => {
const now = new Date(2026, 7, 17, 9, 30);
const occurrences = buildReminderOccurrences([], schedule, { now, days: 2 });
expect(occurrences.map(o => o.triggerAt)).toEqual([
new Date(2026, 7, 17, 12, 0),
new Date(2026, 7, 18, 12, 0),
]);
});
it('skips today when the reminder time has already passed', () => {
const now = new Date(2026, 7, 17, 13, 0);
const occurrences = buildReminderOccurrences([], schedule, { now, days: 2 });
expect(occurrences.map(o => o.triggerAt)).toEqual([new Date(2026, 7, 18, 12, 0)]);
});
it('fires each day at the time configured for its weekday', () => {
const perDay = createUniformSchedule({ hour: 12, minute: 0 });
perDay[1] = { hour: 6, minute: 30 }; // Monday
perDay[2] = { hour: 20, minute: 15 }; // Tuesday
perDay[3] = { hour: 9, minute: 0 }; // Wednesday
const now = new Date(2026, 7, 17, 5, 0);
const occurrences = buildReminderOccurrences([], perDay, { now, days: 3 });
expect(occurrences.map(o => o.triggerAt)).toEqual([
new Date(2026, 7, 17, 6, 30),
new Date(2026, 7, 18, 20, 15),
new Date(2026, 7, 19, 9, 0),
]);
});
it('skips only today when today’s own time has passed', () => {
const perDay = createUniformSchedule({ hour: 12, minute: 0 });
perDay[1] = { hour: 6, minute: 30 }; // Monday, already over
perDay[2] = { hour: 20, minute: 15 }; // Tuesday
const now = new Date(2026, 7, 17, 7, 0);
const occurrences = buildReminderOccurrences([], perDay, { now, days: 2 });
expect(occurrences.map(o => o.triggerAt)).toEqual([new Date(2026, 7, 18, 20, 15)]);
});
it('counts the tasks of each day separately', () => {
const now = new Date(2026, 7, 17, 9, 0);
const tasks = [
{ dueDate: new Date(2026, 7, 17, 15, 0).getTime() },
{ dueDate: new Date(2026, 7, 18, 8, 0).getTime() },
{ dueDate: new Date(2026, 7, 18, 20, 0).getTime() },
{ dueDate: new Date(2026, 7, 18, 20, 0).getTime(), completed: true },
];
const occurrences = buildReminderOccurrences(tasks, schedule, { now, days: 3 });
expect(occurrences.map(o => o.taskCount)).toEqual([1, 2, 0]);
expect(occurrences.map(o => o.body)).toEqual([
'You have 1 task scheduled for today',
'You have 2 tasks scheduled for today',
'No tasks scheduled for today',
]);
});
});
describe('isValidReminderTime', () => {
it('accepts valid times', () => {
expect(isValidReminderTime({ hour: 0, minute: 0 })).toBe(true);
expect(isValidReminderTime({ hour: 23, minute: 59 })).toBe(true);
});
it('rejects out-of-range or non-integer values', () => {
expect(isValidReminderTime({ hour: 24, minute: 0 })).toBe(false);
expect(isValidReminderTime({ hour: -1, minute: 0 })).toBe(false);
expect(isValidReminderTime({ hour: 12, minute: 60 })).toBe(false);
expect(isValidReminderTime({ hour: 12.5, minute: 0 })).toBe(false);
});
});