import { describe, expect, it } from 'vitest'; import { countDays, findOverlap, MAX_PERIOD_DAYS, periodsOverlap, upcomingPeriods, validatePeriod, type NamedPeriod, } from './vacations'; const period = (startDate: string, endDate: string): { startDate: string; endDate: string } => ({ startDate, endDate, }); const named = (id: string, startDate: string, endDate: string): NamedPeriod => ({ id, labelFr: id, startDate, endDate, }); describe('countDays', () => { it('counts both ends', () => { // The shop reads "closed from the 20th to the 22nd" as three days. expect(countDays(period('2026-09-20', '2026-09-22'))).toBe(3); }); it('counts a single day as one', () => { expect(countDays(period('2026-09-20', '2026-09-20'))).toBe(1); }); it('crosses a year boundary', () => { expect(countDays(period('2026-12-30', '2027-01-02'))).toBe(4); }); }); describe('periodsOverlap', () => { it('detects a full overlap', () => { expect(periodsOverlap(period('2026-09-20', '2026-09-30'), period('2026-09-22', '2026-09-24'))).toBe( true, ); }); it('detects a partial overlap from either side', () => { expect(periodsOverlap(period('2026-09-20', '2026-09-25'), period('2026-09-24', '2026-09-28'))).toBe( true, ); expect(periodsOverlap(period('2026-09-24', '2026-09-28'), period('2026-09-20', '2026-09-25'))).toBe( true, ); }); it('counts a single shared day as an overlap', () => { expect(periodsOverlap(period('2026-09-20', '2026-09-22'), period('2026-09-22', '2026-09-24'))).toBe( true, ); }); it('leaves adjacent periods alone', () => { // Ending on the 22nd and starting on the 23rd share nothing. expect(periodsOverlap(period('2026-09-20', '2026-09-22'), period('2026-09-23', '2026-09-25'))).toBe( false, ); }); it('leaves distant periods alone', () => { expect(periodsOverlap(period('2026-01-01', '2026-01-10'), period('2026-08-01', '2026-08-10'))).toBe( false, ); }); }); describe('findOverlap', () => { const existing = [named('été', '2026-07-20', '2026-08-10'), named('noël', '2026-12-24', '2027-01-02')]; it('names the period that collides', () => { expect(findOverlap(period('2026-08-05', '2026-08-15'), existing)?.id).toBe('été'); }); it('returns nothing when the candidate is free', () => { expect(findOverlap(period('2026-09-01', '2026-09-10'), existing)).toBeNull(); }); it('returns nothing against an empty list', () => { expect(findOverlap(period('2026-09-01', '2026-09-10'), [])).toBeNull(); }); }); describe('validatePeriod', () => { it('accepts a sound period', () => { expect(validatePeriod({ startDate: '2026-07-20', endDate: '2026-08-10', labelFr: 'Congés' })).toEqual( [], ); }); it('refuses missing dates', () => { expect(validatePeriod({ startDate: '', endDate: '2026-08-10', labelFr: 'Congés' })).toHaveLength(1); }); it('refuses a reversed range', () => { expect( validatePeriod({ startDate: '2026-08-10', endDate: '2026-07-20', labelFr: 'Congés' }).join(' '), ).toMatch(/précède/); }); it('refuses a period longer than a year', () => { expect( validatePeriod({ startDate: '2026-01-01', endDate: '2027-06-01', labelFr: 'Congés' }).join(' '), ).toMatch(String(MAX_PERIOD_DAYS)); }); it('requires a label, because it is shown on the screen', () => { expect( validatePeriod({ startDate: '2026-07-20', endDate: '2026-08-10', labelFr: ' ' }).join(' '), ).toMatch(/libellé/); }); it('reports several problems at once', () => { expect( validatePeriod({ startDate: '2026-08-10', endDate: '2026-07-20', labelFr: '' }), ).toHaveLength(2); }); }); describe('upcomingPeriods', () => { const periods = [ named('noël', '2026-12-24', '2027-01-02'), named('passé', '2026-02-01', '2026-02-10'), named('été', '2026-07-20', '2026-08-10'), ]; it('drops periods that have finished', () => { expect(upcomingPeriods(periods, '2026-09-20').map((p) => p.id)).toEqual(['noël']); }); it('keeps a period currently in progress', () => { expect(upcomingPeriods(periods, '2026-08-01').map((p) => p.id)).toEqual(['été', 'noël']); }); it('keeps a period ending today', () => { expect(upcomingPeriods(periods, '2026-08-10').map((p) => p.id)).toContain('été'); }); it('sorts by start date', () => { expect(upcomingPeriods(periods, '2026-01-01').map((p) => p.id)).toEqual(['passé', 'été', 'noël']); }); });