import {
Recurrence,
computeNextDueDate,
isValidRecurrence,
describeRecurrence,
} from '../recurrence';
// Helper: build a local timestamp. Month is 0-indexed.
const at = (y: number, m: number, d: number, h = 9, min = 0) =>
new Date(y, m, d, h, min, 0, 0).getTime();
describe('computeNextDueDate', () => {
it('weekly advances by 7 days', () => {
// June 1 2026 is a Monday.
expect(computeNextDueDate(at(2026, 5, 1), { kind: 'weekly' })).toBe(at(2026, 5, 8));
});
it('monthly advances one month keeping the day', () => {
expect(computeNextDueDate(at(2026, 0, 15), { kind: 'monthly' })).toBe(at(2026, 1, 15));
});
it('everyNDays advances by the interval', () => {
expect(
computeNextDueDate(at(2026, 5, 1), { kind: 'everyNDays', intervalDays: 10 })
).toBe(at(2026, 5, 11));
});
it('weekdays picks the next selected weekday', () => {
// Mon June 1 2026, selected Wed(3) + Fri(5) -> next is Wed June 3.
expect(
computeNextDueDate(at(2026, 5, 1), { kind: 'weekdays', weekdays: [3, 5] })
).toBe(at(2026, 5, 3));
});
it('weekdays wraps into the following week when needed', () => {
// Fri June 5 2026, selected only Mon(1) -> next is Mon June 8.
expect(
computeNextDueDate(at(2026, 5, 5), { kind: 'weekdays', weekdays: [1] })
).toBe(at(2026, 5, 8));
});
it('preserves the time of day', () => {
expect(
computeNextDueDate(at(2026, 5, 1, 23, 59), { kind: 'weekly' })
).toBe(at(2026, 5, 8, 23, 59));
});
});
describe('isValidRecurrence', () => {
it('weekly and monthly are always valid', () => {
expect(isValidRecurrence({ kind: 'weekly' })).toBe(true);
expect(isValidRecurrence({ kind: 'monthly' })).toBe(true);
});
it('everyNDays requires a positive integer interval', () => {
expect(isValidRecurrence({ kind: 'everyNDays', intervalDays: 3 })).toBe(true);
expect(isValidRecurrence({ kind: 'everyNDays', intervalDays: 0 })).toBe(false);
expect(isValidRecurrence({ kind: 'everyNDays' })).toBe(false);
});
it('weekdays requires at least one in-range day', () => {
expect(isValidRecurrence({ kind: 'weekdays', weekdays: [0, 6] })).toBe(true);
expect(isValidRecurrence({ kind: 'weekdays', weekdays: [] })).toBe(false);
expect(isValidRecurrence({ kind: 'weekdays', weekdays: [7] })).toBe(false);
});
});
describe('describeRecurrence', () => {
it('labels the presets', () => {
expect(describeRecurrence({ kind: 'weekly' })).toBe('Weekly');
expect(describeRecurrence({ kind: 'monthly' })).toBe('Monthly');
});
it('labels everyNDays', () => {
expect(describeRecurrence({ kind: 'everyNDays', intervalDays: 1 })).toBe('Every day');
expect(describeRecurrence({ kind: 'everyNDays', intervalDays: 3 })).toBe('Every 3 days');
});
it('labels weekdays in week order', () => {
const r: Recurrence = { kind: 'weekdays', weekdays: [5, 1, 3] };
expect(describeRecurrence(r)).toBe('Mon, Wed, Fri');
});
});