[
{"name": "a 09:00-12:00 morning in 15-minute slots is twelve slots", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "12:00"}], 15, [], []], "expect": [{"date": "2026-10-05", "start": "09:00", "end": "09:15"}, {"date": "2026-10-05", "start": "09:15", "end": "09:30"}, {"date": "2026-10-05", "start": "09:30", "end": "09:45"}, {"date": "2026-10-05", "start": "09:45", "end": "10:00"}, {"date": "2026-10-05", "start": "10:00", "end": "10:15"}, {"date": "2026-10-05", "start": "10:15", "end": "10:30"}, {"date": "2026-10-05", "start": "10:30", "end": "10:45"}, {"date": "2026-10-05", "start": "10:45", "end": "11:00"}, {"date": "2026-10-05", "start": "11:00", "end": "11:15"}, {"date": "2026-10-05", "start": "11:15", "end": "11:30"}, {"date": "2026-10-05", "start": "11:30", "end": "11:45"}, {"date": "2026-10-05", "start": "11:45", "end": "12:00"}]},
{"name": "a daily 12:30-13:30 lunch splits 09:00-17:00; 20-minute slots restart at 13:30 and the 10 minutes left before lunch and at the end are dropped", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "17:00"}], 20, [{"date": null, "start": "12:30", "end": "13:30"}], []], "expect": [{"date": "2026-10-05", "start": "09:00", "end": "09:20"}, {"date": "2026-10-05", "start": "09:20", "end": "09:40"}, {"date": "2026-10-05", "start": "09:40", "end": "10:00"}, {"date": "2026-10-05", "start": "10:00", "end": "10:20"}, {"date": "2026-10-05", "start": "10:20", "end": "10:40"}, {"date": "2026-10-05", "start": "10:40", "end": "11:00"}, {"date": "2026-10-05", "start": "11:00", "end": "11:20"}, {"date": "2026-10-05", "start": "11:20", "end": "11:40"}, {"date": "2026-10-05", "start": "11:40", "end": "12:00"}, {"date": "2026-10-05", "start": "12:00", "end": "12:20"}, {"date": "2026-10-05", "start": "13:30", "end": "13:50"}, {"date": "2026-10-05", "start": "13:50", "end": "14:10"}, {"date": "2026-10-05", "start": "14:10", "end": "14:30"}, {"date": "2026-10-05", "start": "14:30", "end": "14:50"}, {"date": "2026-10-05", "start": "14:50", "end": "15:10"}, {"date": "2026-10-05", "start": "15:10", "end": "15:30"}, {"date": "2026-10-05", "start": "15:30", "end": "15:50"}, {"date": "2026-10-05", "start": "15:50", "end": "16:10"}, {"date": "2026-10-05", "start": "16:10", "end": "16:30"}, {"date": "2026-10-05", "start": "16:30", "end": "16:50"}]},
{"name": "a break with a date applies to that day only", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "10:00"}, {"date": "2026-10-06", "start": "09:00", "end": "10:00"}], 30, [{"date": "2026-10-06", "start": "09:30", "end": "10:00"}], []], "expect": [{"date": "2026-10-05", "start": "09:00", "end": "09:30"}, {"date": "2026-10-05", "start": "09:30", "end": "10:00"}, {"date": "2026-10-06", "start": "09:00", "end": "09:30"}]},
{"name": "a booking straddling two slots removes both", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "10:00"}], 15, [], [{"date": "2026-10-05", "start": "09:20", "end": "09:40"}]], "expect": [{"date": "2026-10-05", "start": "09:00", "end": "09:15"}, {"date": "2026-10-05", "start": "09:45", "end": "10:00"}]},
{"name": "bookings that only touch a slot's edge do not remove it", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "10:00"}], 15, [], [{"date": "2026-10-05", "start": "08:30", "end": "09:00"}, {"date": "2026-10-05", "start": "09:15", "end": "09:30"}]], "expect": [{"date": "2026-10-05", "start": "09:00", "end": "09:15"}, {"date": "2026-10-05", "start": "09:30", "end": "09:45"}, {"date": "2026-10-05", "start": "09:45", "end": "10:00"}]},
{"name": "a booking that does not fill its slot does not shift the grid", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "10:00"}], 20, [], [{"date": "2026-10-05", "start": "09:05", "end": "09:10"}]], "expect": [{"date": "2026-10-05", "start": "09:20", "end": "09:40"}, {"date": "2026-10-05", "start": "09:40", "end": "10:00"}]},
{"name": "bookings on other dates are ignored", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "10:00"}], 15, [], [{"date": "2026-10-06", "start": "09:00", "end": "09:15"}]], "expect": [{"date": "2026-10-05", "start": "09:00", "end": "09:15"}, {"date": "2026-10-05", "start": "09:15", "end": "09:30"}, {"date": "2026-10-05", "start": "09:30", "end": "09:45"}, {"date": "2026-10-05", "start": "09:45", "end": "10:00"}]},
{"name": "sessions given out of order come back by date and time", "args": [[{"date": "2026-10-05", "start": "14:00", "end": "15:00"}, {"date": "2026-10-05", "start": "09:00", "end": "10:00"}, {"date": "2026-10-04", "start": "10:00", "end": "10:30"}], 30, [], []], "expect": [{"date": "2026-10-04", "start": "10:00", "end": "10:30"}, {"date": "2026-10-05", "start": "09:00", "end": "09:30"}, {"date": "2026-10-05", "start": "09:30", "end": "10:00"}, {"date": "2026-10-05", "start": "14:00", "end": "14:30"}, {"date": "2026-10-05", "start": "14:30", "end": "15:00"}]},
{"name": "a break overlapping the session start moves the first slot to the break's end", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "11:00"}], 30, [{"date": null, "start": "08:30", "end": "09:30"}], []], "expect": [{"date": "2026-10-05", "start": "09:30", "end": "10:00"}, {"date": "2026-10-05", "start": "10:00", "end": "10:30"}, {"date": "2026-10-05", "start": "10:30", "end": "11:00"}]},
{"name": "a break covering the whole session leaves no slots", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "10:00"}], 15, [{"date": null, "start": "08:00", "end": "12:00"}], []], "expect": []},
{"name": "a slot longer than the session gives no slots", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "09:30"}], 45, [], []], "expect": []},
{"name": "no sessions, no slots", "args": [[], 15, [], []], "expect": []},
{"name": "sessions that overlap on one day are an error", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "12:00"}, {"date": "2026-10-05", "start": "11:30", "end": "13:00"}], 15, [], []], "expectError": "sessions overlap on 2026-10-05"},
{"name": "a time without a leading zero is an error", "args": [[{"date": "2026-10-05", "start": "9:00", "end": "12:00"}], 15, [], []], "expectError": "is not a time of day"},
{"name": "24:00 is not a time of day", "args": [[{"date": "2026-10-05", "start": "20:00", "end": "24:00"}], 15, [], []], "expectError": "is not a time of day"},
{"name": "a session cannot cross midnight: 22:00-00:00 ends before it starts", "args": [[{"date": "2026-10-05", "start": "22:00", "end": "00:00"}], 15, [], []], "expectError": "session on 2026-10-05 must end after it starts"},
{"name": "a booking that ends before it starts is an error", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "12:00"}], 15, [], [{"date": "2026-10-05", "start": "10:00", "end": "09:45"}]], "expectError": "booking on 2026-10-05 must end after it starts"},
{"name": "an impossible date is an error", "args": [[{"date": "2026-02-30", "start": "09:00", "end": "12:00"}], 15, [], []], "expectError": "is not a real calendar date"},
{"name": "a zero slot length is an error", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "12:00"}], 0, [], []], "expectError": "slotMinutes must be a whole number from 1 to 480"},
{"name": "a fractional slot length is an error", "args": [[{"date": "2026-10-05", "start": "09:00", "end": "12:00"}], 7.5, [], []], "expectError": "slotMinutes must be a whole number from 1 to 480"}
]