Support overnight blocks in schedule validation
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
blockDuration,
|
||||
blockSegments,
|
||||
findOverlap,
|
||||
formatTime,
|
||||
isOvernight,
|
||||
snapMinutes,
|
||||
toHm,
|
||||
validateBlock,
|
||||
@@ -52,6 +55,49 @@ describe('snapMinutes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOvernight', () => {
|
||||
it('is true when the end clock time is before the start', () => {
|
||||
expect(isOvernight({ startMinutes: 21 * 60, endMinutes: 9 * 60 })).toBe(true);
|
||||
expect(isOvernight({ startMinutes: 22 * 60, endMinutes: 0 })).toBe(true);
|
||||
});
|
||||
|
||||
it('is false for same-day ranges and a full-day range', () => {
|
||||
expect(isOvernight({ startMinutes: 6 * 60, endMinutes: 8 * 60 })).toBe(false);
|
||||
expect(isOvernight({ startMinutes: 0, endMinutes: 1440 })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blockDuration', () => {
|
||||
it('counts a same-day range directly', () => {
|
||||
expect(blockDuration({ startMinutes: 6 * 60, endMinutes: 8 * 60 })).toBe(120);
|
||||
});
|
||||
|
||||
it('counts an overnight range as wrapping to the next day', () => {
|
||||
expect(blockDuration({ startMinutes: 21 * 60, endMinutes: 9 * 60 })).toBe(720);
|
||||
});
|
||||
|
||||
it('treats an end of midnight as wrapping from the start', () => {
|
||||
expect(blockDuration({ startMinutes: 22 * 60, endMinutes: 0 })).toBe(120);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blockSegments', () => {
|
||||
it('returns a single segment for a same-day range', () => {
|
||||
expect(blockSegments({ startMinutes: 6 * 60, endMinutes: 8 * 60 })).toEqual([[6 * 60, 8 * 60]]);
|
||||
});
|
||||
|
||||
it('splits an overnight range at midnight', () => {
|
||||
expect(blockSegments({ startMinutes: 21 * 60, endMinutes: 9 * 60 })).toEqual([
|
||||
[21 * 60, 1440],
|
||||
[0, 9 * 60]
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns no segments for a zero-length range', () => {
|
||||
expect(blockSegments({ startMinutes: 360, endMinutes: 360 })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOverlap', () => {
|
||||
it('returns null for adjacent ranges', () => {
|
||||
expect(
|
||||
@@ -63,6 +109,28 @@ describe('findOverlap', () => {
|
||||
const other = sibling(7 * 60, 9 * 60);
|
||||
expect(findOverlap({ startMinutes: 8 * 60, endMinutes: 10 * 60 }, [other])).toBe(other);
|
||||
});
|
||||
|
||||
it('detects an overnight block overlapping an early-morning block', () => {
|
||||
const other = sibling(8 * 60, 10 * 60);
|
||||
expect(findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [other])).toBe(other);
|
||||
});
|
||||
|
||||
it('detects an overnight block overlapping a late-evening block', () => {
|
||||
const other = sibling(20 * 60, 22 * 60);
|
||||
expect(findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [other])).toBe(other);
|
||||
});
|
||||
|
||||
it('allows an overnight block and its exact complement (adjacent at both ends)', () => {
|
||||
expect(
|
||||
findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [sibling(9 * 60, 21 * 60)])
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('allows an overnight block and a midday block it does not touch', () => {
|
||||
expect(
|
||||
findOverlap({ startMinutes: 21 * 60, endMinutes: 9 * 60 }, [sibling(10 * 60, 20 * 60)])
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateBlock', () => {
|
||||
@@ -74,17 +142,40 @@ describe('validateBlock', () => {
|
||||
const result = validateBlock(input({ endMinutes: 6 * 60 }), []);
|
||||
expect(result).toEqual({
|
||||
ok: false,
|
||||
error: "Start time must be before end time. Overnight blocks aren't supported."
|
||||
error: "Start and end time can't be the same."
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects an end time past midnight (overnight blocks disallowed)', () => {
|
||||
const result = validateBlock(input({ startMinutes: 23 * 60, endMinutes: 25 * 60 }), []);
|
||||
expect(result.ok).toBe(false);
|
||||
it('rejects a time outside the 24-hour range', () => {
|
||||
expect(validateBlock(input({ startMinutes: 23 * 60, endMinutes: 25 * 60 }), [])).toEqual({
|
||||
ok: false,
|
||||
error: 'Enter a valid start and end time.'
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a negative start time', () => {
|
||||
expect(validateBlock(input({ startMinutes: -1 }), []).ok).toBe(false);
|
||||
expect(validateBlock(input({ startMinutes: -1 }), [])).toEqual({
|
||||
ok: false,
|
||||
error: 'Enter a valid start and end time.'
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts an overnight block', () => {
|
||||
expect(
|
||||
validateBlock(input({ startMinutes: 21 * 60, endMinutes: 9 * 60 }), [], {
|
||||
isCreate: true
|
||||
})
|
||||
).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it('rejects an overnight block that overlaps another and names it', () => {
|
||||
const result = validateBlock(input({ startMinutes: 21 * 60, endMinutes: 9 * 60 }), [
|
||||
sibling(8 * 60, 10 * 60)
|
||||
]);
|
||||
expect(result).toEqual({
|
||||
ok: false,
|
||||
error: 'This overlaps with your 8:00 AM–10:00 AM block.'
|
||||
});
|
||||
});
|
||||
|
||||
it('requires at least one proclamation', () => {
|
||||
|
||||
Reference in New Issue
Block a user