292 lines
8.5 KiB
TypeScript
292 lines
8.5 KiB
TypeScript
import { generateSchedule, generateReminderSchedule } from '../schedule';
|
|
import { Medication, Frequency, CustomReminder } from '../../types';
|
|
|
|
describe('Schedule Utilities', () => {
|
|
const baseDate = new Date('2024-01-15T12:00:00.000Z');
|
|
|
|
describe('generateSchedule', () => {
|
|
const createMockMedication = (
|
|
overrides: Partial<Medication> = {}
|
|
): Medication => ({
|
|
_id: 'med-1',
|
|
_rev: 'rev-1',
|
|
name: 'Test Medication',
|
|
dosage: '10mg',
|
|
frequency: Frequency.Daily,
|
|
startTime: '08:00',
|
|
notes: '',
|
|
...overrides,
|
|
});
|
|
|
|
test('should return empty array for empty medications', () => {
|
|
const schedule = generateSchedule([], baseDate);
|
|
expect(schedule).toEqual([]);
|
|
});
|
|
|
|
test('should generate one dose for daily frequency', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.Daily,
|
|
});
|
|
|
|
const schedule = generateSchedule([medication], baseDate);
|
|
|
|
expect(schedule).toHaveLength(1);
|
|
expect(schedule[0].medicationId).toBe('med-1');
|
|
expect(schedule[0].scheduledTime).toBeInstanceOf(Date);
|
|
});
|
|
|
|
test('should generate two doses for twice daily frequency', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.TwiceADay,
|
|
});
|
|
|
|
const schedule = generateSchedule([medication], baseDate);
|
|
|
|
expect(schedule).toHaveLength(2);
|
|
expect(schedule[0].medicationId).toBe('med-1');
|
|
expect(schedule[1].medicationId).toBe('med-1');
|
|
});
|
|
|
|
test('should generate three doses for three times daily frequency', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.ThreeTimesADay,
|
|
});
|
|
|
|
const schedule = generateSchedule([medication], baseDate);
|
|
|
|
expect(schedule).toHaveLength(3);
|
|
});
|
|
|
|
test('should handle EveryXHours frequency', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.EveryXHours,
|
|
hoursBetween: 6,
|
|
});
|
|
|
|
const schedule = generateSchedule([medication], baseDate);
|
|
|
|
expect(schedule.length).toBeGreaterThan(0);
|
|
expect(schedule.length).toBeLessThanOrEqual(24);
|
|
});
|
|
|
|
test('should sort doses by time', () => {
|
|
const medications = [
|
|
createMockMedication({
|
|
_id: 'med-1',
|
|
startTime: '18:00',
|
|
}),
|
|
createMockMedication({
|
|
_id: 'med-2',
|
|
startTime: '08:00',
|
|
}),
|
|
];
|
|
|
|
const schedule = generateSchedule(medications, baseDate);
|
|
|
|
expect(schedule).toHaveLength(2);
|
|
// Should be sorted by time
|
|
expect(schedule[0].scheduledTime.getTime()).toBeLessThanOrEqual(
|
|
schedule[1].scheduledTime.getTime()
|
|
);
|
|
});
|
|
|
|
test('should handle invalid hoursBetween gracefully', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.EveryXHours,
|
|
hoursBetween: 0,
|
|
});
|
|
|
|
const schedule = generateSchedule([medication], baseDate);
|
|
expect(schedule).toEqual([]);
|
|
});
|
|
|
|
describe('DST boundaries', () => {
|
|
test('maintains morning doses during spring forward transition', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.Daily,
|
|
startTime: '08:00',
|
|
});
|
|
|
|
const beforeTransition = generateSchedule(
|
|
[medication],
|
|
new Date('2024-03-09T12:00:00.000Z')
|
|
);
|
|
const duringTransition = generateSchedule(
|
|
[medication],
|
|
new Date('2024-03-10T12:00:00.000Z')
|
|
);
|
|
|
|
expect(beforeTransition).toHaveLength(1);
|
|
expect(duringTransition).toHaveLength(1);
|
|
const diffHours =
|
|
(duringTransition[0].scheduledTime.getTime() -
|
|
beforeTransition[0].scheduledTime.getTime()) /
|
|
3600000;
|
|
expect(diffHours).toBe(23);
|
|
});
|
|
|
|
test('shifts to next valid slot when scheduled time is skipped by DST', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.Daily,
|
|
startTime: '02:30',
|
|
});
|
|
|
|
const schedule = generateSchedule(
|
|
[medication],
|
|
new Date('2024-03-10T12:00:00.000Z')
|
|
);
|
|
|
|
expect(schedule).toHaveLength(1);
|
|
expect(schedule[0].scheduledTime.getHours()).toBe(3);
|
|
expect(schedule[0].scheduledTime.getMinutes()).toBe(30);
|
|
});
|
|
|
|
test('extends interval by one hour during fall back transition', () => {
|
|
const medication = createMockMedication({
|
|
frequency: Frequency.Daily,
|
|
startTime: '08:00',
|
|
});
|
|
|
|
const beforeTransition = generateSchedule(
|
|
[medication],
|
|
new Date('2024-11-02T12:00:00.000Z')
|
|
);
|
|
const duringTransition = generateSchedule(
|
|
[medication],
|
|
new Date('2024-11-03T12:00:00.000Z')
|
|
);
|
|
|
|
expect(beforeTransition).toHaveLength(1);
|
|
expect(duringTransition).toHaveLength(1);
|
|
const diffHours =
|
|
(duringTransition[0].scheduledTime.getTime() -
|
|
beforeTransition[0].scheduledTime.getTime()) /
|
|
3600000;
|
|
expect(diffHours).toBe(25);
|
|
expect(duringTransition[0].scheduledTime.getHours()).toBe(8);
|
|
expect(duringTransition[0].scheduledTime.getMinutes()).toBe(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('generateReminderSchedule', () => {
|
|
const createMockReminder = (
|
|
overrides: Partial<CustomReminder> = {}
|
|
): CustomReminder => ({
|
|
_id: 'reminder-1',
|
|
_rev: 'rev-1',
|
|
title: 'Test Reminder',
|
|
icon: '💊',
|
|
startTime: '09:00',
|
|
endTime: '17:00',
|
|
frequencyMinutes: 60,
|
|
...overrides,
|
|
});
|
|
|
|
test('should return empty array for empty reminders', () => {
|
|
const schedule = generateReminderSchedule([], baseDate);
|
|
expect(schedule).toEqual([]);
|
|
});
|
|
|
|
test('should generate reminders within time window', () => {
|
|
const reminder = createMockReminder({
|
|
startTime: '10:00',
|
|
endTime: '12:00',
|
|
frequencyMinutes: 60,
|
|
});
|
|
|
|
const schedule = generateReminderSchedule([reminder], baseDate);
|
|
|
|
expect(schedule.length).toBeGreaterThan(0);
|
|
schedule.forEach(instance => {
|
|
expect(instance.reminderId).toBe('reminder-1');
|
|
expect(instance.title).toBe('Test Reminder');
|
|
expect(instance.icon).toBe('💊');
|
|
expect(instance.scheduledTime).toBeInstanceOf(Date);
|
|
});
|
|
});
|
|
|
|
test('should handle single time window', () => {
|
|
const reminder = createMockReminder({
|
|
startTime: '15:00',
|
|
endTime: '15:00',
|
|
frequencyMinutes: 60,
|
|
});
|
|
|
|
const schedule = generateReminderSchedule([reminder], baseDate);
|
|
|
|
expect(schedule).toHaveLength(1);
|
|
});
|
|
|
|
test('should sort reminders by time', () => {
|
|
const reminders = [
|
|
createMockReminder({
|
|
_id: 'reminder-1',
|
|
startTime: '18:00',
|
|
endTime: '18:00',
|
|
}),
|
|
createMockReminder({
|
|
_id: 'reminder-2',
|
|
startTime: '08:00',
|
|
endTime: '08:00',
|
|
}),
|
|
];
|
|
|
|
const schedule = generateReminderSchedule(reminders, baseDate);
|
|
|
|
expect(schedule).toHaveLength(2);
|
|
// Should be sorted by time
|
|
expect(schedule[0].scheduledTime.getTime()).toBeLessThanOrEqual(
|
|
schedule[1].scheduledTime.getTime()
|
|
);
|
|
});
|
|
|
|
test('should generate unique IDs', () => {
|
|
const reminder = createMockReminder({
|
|
startTime: '10:00',
|
|
endTime: '11:00',
|
|
frequencyMinutes: 30,
|
|
});
|
|
|
|
const schedule = generateReminderSchedule([reminder], baseDate);
|
|
|
|
const ids = schedule.map(s => s.id);
|
|
const uniqueIds = new Set(ids);
|
|
expect(uniqueIds.size).toBe(ids.length);
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
test('should handle malformed time strings', () => {
|
|
const medication: Medication = {
|
|
_id: 'bad-med',
|
|
_rev: 'rev-1',
|
|
name: 'Bad Med',
|
|
dosage: '10mg',
|
|
frequency: Frequency.Daily,
|
|
startTime: 'invalid:time',
|
|
notes: '',
|
|
};
|
|
|
|
// This will throw because the time parsing creates an invalid date
|
|
expect(() => generateSchedule([medication], baseDate)).toThrow();
|
|
});
|
|
|
|
test('should handle large frequency values', () => {
|
|
const medication: Medication = {
|
|
_id: 'large-med',
|
|
_rev: 'rev-1',
|
|
name: 'Large Med',
|
|
dosage: '10mg',
|
|
frequency: Frequency.EveryXHours,
|
|
startTime: '08:00',
|
|
hoursBetween: 100,
|
|
notes: '',
|
|
};
|
|
|
|
const schedule = generateSchedule([medication], baseDate);
|
|
expect(schedule.length).toBeLessThan(100);
|
|
});
|
|
});
|
|
});
|