import { describe, expect, test } from 'bun:test'; import { type QuietHoursWindow, isWithinQuietHours, localMinutesOfDay, parseClockTime, quietHoursEndAfter, } from './quiet-hours'; const LA = 'America/Los_Angeles'; /** 22:00–07:00 local — the common overnight window, which wraps midnight. */ const OVERNIGHT: QuietHoursWindow = { start: '22:00', end: '07:00', timezone: LA }; /** 09:00–17:00 — a same-day window, which does not wrap. */ const WORKDAY: QuietHoursWindow = { start: '09:00', end: '17:00', timezone: LA }; /** * A UTC instant for a given LA wall-clock hour (PDT = UTC-7 in July). * * No modulo on the hour: `Date.UTC` rolls the date forward on overflow, which * is what makes 17:00 PDT land on the following UTC day rather than silently * on the same one. */ const laTime = (hour: number, minute = 0) => new Date(Date.UTC(2026, 6, 28, hour + 7, minute)); describe('parseClockTime', () => { test.each([ ['00:00', 0], ['07:00', 420], ['22:30', 1350], ['23:59', 1439], ])('parses %p', (value, expected) => { expect(parseClockTime(value)).toBe(expected); }); test.each(['24:00', '7:00', '22:60', '', 'noon', '22'])('rejects %p', (value) => { expect(parseClockTime(value)).toBeNull(); }); }); describe('localMinutesOfDay', () => { test('converts a UTC instant to local minutes', () => { expect(localMinutesOfDay(laTime(23, 30), LA)).toBe(23 * 60 + 30); }); test('midnight is zero, not 1440', () => { expect(localMinutesOfDay(laTime(0), LA)).toBe(0); }); }); describe('isWithinQuietHours — overnight window (wraps midnight)', () => { test.each([ [23, true, 'after the start, before midnight'], [2, true, 'after midnight, before the end'], [6, true, 'just before the end'], [7, false, 'at the end — exclusive'], [12, false, 'midday'], [21, false, 'just before the start'], [22, true, 'at the start — inclusive'], ])('%p:00 → %p (%s)', (hour, expected) => { expect(isWithinQuietHours(OVERNIGHT, laTime(hour))).toBe(expected); }); }); describe('isWithinQuietHours — same-day window', () => { test.each([ [8, false], [9, true], [12, true], [16, true], [17, false], [23, false], ])('%p:00 → %p', (hour, expected) => { expect(isWithinQuietHours(WORKDAY, laTime(hour))).toBe(expected); }); }); describe('isWithinQuietHours — degenerate configurations', () => { test('no window configured means always reachable', () => { const none: QuietHoursWindow = { start: null, end: null, timezone: LA }; expect(isWithinQuietHours(none, laTime(3))).toBe(false); }); test('a half-configured window means always reachable', () => { const half: QuietHoursWindow = { start: '22:00', end: null, timezone: LA }; expect(isWithinQuietHours(half, laTime(3))).toBe(false); }); // A typo must not silence someone permanently. test('start equal to end is treated as no window, not as always quiet', () => { const degenerate: QuietHoursWindow = { start: '09:00', end: '09:00', timezone: LA }; expect(isWithinQuietHours(degenerate, laTime(3))).toBe(false); expect(isWithinQuietHours(degenerate, laTime(9))).toBe(false); }); test('a malformed window means always reachable', () => { const bad: QuietHoursWindow = { start: 'evening', end: 'morning', timezone: LA }; expect(isWithinQuietHours(bad, laTime(3))).toBe(false); }); }); describe('quietHoursEndAfter', () => { test('a page at 02:00 waits until 07:00', () => { expect(quietHoursEndAfter(OVERNIGHT, laTime(2))).toEqual(laTime(7)); }); // The window started last night; the end is tomorrow morning, not today's. test('a page at 23:00 waits until 07:00 the next morning', () => { const deliverAt = quietHoursEndAfter(OVERNIGHT, laTime(23)); expect(deliverAt).toEqual(new Date(laTime(23).getTime() + 8 * 60 * 60_000)); }); test('outside the window there is nothing to defer', () => { expect(quietHoursEndAfter(OVERNIGHT, laTime(12))).toBeNull(); }); test('a same-day window defers to its end on the same day', () => { expect(quietHoursEndAfter(WORKDAY, laTime(10))).toEqual(laTime(17)); }); }); describe('timezone is honoured, not assumed', () => { // The same instant is inside one person's window and outside another's. test('two people in different zones disagree about the same moment', () => { const instant = laTime(23); const pacific: QuietHoursWindow = { start: '22:00', end: '07:00', timezone: LA }; const newYork: QuietHoursWindow = { start: '22:00', end: '07:00', timezone: 'America/New_York', }; expect(isWithinQuietHours(pacific, instant)).toBe(true); // 23:00 Pacific is 02:00 Eastern — also inside, but for a different reason. expect(isWithinQuietHours(newYork, instant)).toBe(true); const midday = laTime(12); // 15:00 Eastern expect(isWithinQuietHours(pacific, midday)).toBe(false); expect(isWithinQuietHours(newYork, midday)).toBe(false); }); });