import { describe, expect, it } from 'vitest'; import { changedDays, duplicateToOpenDays, hasErrors, normaliseWeek, toDraft, validateWeek, WEEK_ORDER, type WeekDraft, } from './week'; const FULL = [{ open: '10:00', close: '18:30' }]; const SPLIT = [ { open: '10:00', close: '13:00' }, { open: '14:00', close: '18:30' }, ]; function week(overrides: Partial>> = {}): WeekDraft { return WEEK_ORDER.map((dayOfWeek) => ({ dayOfWeek, isClosed: dayOfWeek === 0 || dayOfWeek === 1, slots: dayOfWeek === 0 || dayOfWeek === 1 ? [] : FULL.map((slot) => ({ ...slot })), ...overrides[dayOfWeek], })); } describe('toDraft', () => { it('returns the week Monday first', () => { expect(toDraft([]).map((day) => day.dayOfWeek)).toEqual([1, 2, 3, 4, 5, 6, 0]); }); it('fills in a day the database does not have, as closed', () => { const draft = toDraft([{ dayOfWeek: 2, isClosed: false, slots: FULL }]); expect(draft.find((day) => day.dayOfWeek === 2)?.slots).toEqual(FULL); expect(draft.find((day) => day.dayOfWeek === 5)).toMatchObject({ isClosed: true, slots: [] }); }); it('puts slots in chronological order whatever the stored order', () => { const draft = toDraft([{ dayOfWeek: 3, isClosed: false, slots: [SPLIT[1]!, SPLIT[0]!] }]); expect(draft.find((day) => day.dayOfWeek === 3)?.slots).toEqual(SPLIT); }); }); describe('duplicateToOpenDays', () => { it('copies the source day onto the other open days', () => { const source = week({ 2: { slots: SPLIT } }); const result = duplicateToOpenDays(source, 2); for (const dayOfWeek of [3, 4, 5, 6]) { expect(result.find((day) => day.dayOfWeek === dayOfWeek)?.slots).toEqual(SPLIT); } }); it('leaves closed days closed', () => { // "The days I open, I open like this" — not "open seven days a week". const result = duplicateToOpenDays(week({ 2: { slots: SPLIT } }), 2); expect(result.find((day) => day.dayOfWeek === 1)).toMatchObject({ isClosed: true, slots: [], }); }); it('does nothing when the source day is itself closed', () => { const source = week(); expect(duplicateToOpenDays(source, 1)).toEqual(source); }); it('does nothing when the source day does not exist', () => { const source = week(); expect(duplicateToOpenDays(source, 42)).toEqual(source); }); it('copies by value, so later edits do not leak between days', () => { const result = duplicateToOpenDays(week({ 2: { slots: SPLIT } }), 2); const tuesday = result.find((day) => day.dayOfWeek === 2)!; const wednesday = result.find((day) => day.dayOfWeek === 3)!; expect(wednesday.slots).not.toBe(tuesday.slots); expect(wednesday.slots[0]).not.toBe(tuesday.slots[0]); }); }); describe('validateWeek', () => { it('accepts a sound week', () => { expect(hasErrors(validateWeek(week()))).toBe(false); }); it('ignores the slots of a closed day', () => { // Leftovers from before the day was closed must not block the save. const draft = week({ 2: { isClosed: true, slots: [{ open: '18:00', close: '10:00' }] } }); expect(hasErrors(validateWeek(draft))).toBe(false); }); it('refuses an open day with no hours', () => { const draft = week({ 2: { isClosed: false, slots: [] } }); expect(validateWeek(draft)[2]).toContain('Un jour ouvert doit avoir au moins une plage horaire.'); }); it('reports the problem against the day it belongs to', () => { const draft = week({ 4: { slots: [{ open: '18:30', close: '10:00' }] } }); const errors = validateWeek(draft); expect(errors[4]?.length).toBeGreaterThan(0); expect(errors[2]).toBeUndefined(); }); it('catches overlapping slots', () => { const draft = week({ 3: { slots: [ { open: '10:00', close: '14:00' }, { open: '13:00', close: '18:30' }, ], }, }); expect(errors(validateWeek(draft), 3)).toMatch(/chevauchent/); }); }); function errors(all: Record, day: number): string { return (all[day] ?? []).join(' '); } describe('normaliseWeek', () => { it('strips the slots of a closed day', () => { const draft = week({ 2: { isClosed: true, slots: FULL } }); expect(normaliseWeek(draft).find((day) => day.dayOfWeek === 2)?.slots).toEqual([]); }); it('orders the slots of an open day', () => { const draft = week({ 3: { slots: [SPLIT[1]!, SPLIT[0]!] } }); expect(normaliseWeek(draft).find((day) => day.dayOfWeek === 3)?.slots).toEqual(SPLIT); }); }); describe('changedDays', () => { it('reports nothing when the week is untouched', () => { expect(changedDays(week(), week())).toEqual([]); }); it('ignores a reordering that changes nothing', () => { const before = week({ 3: { slots: SPLIT } }); const after = week({ 3: { slots: [SPLIT[1]!, SPLIT[0]!] } }); expect(changedDays(before, after)).toEqual([]); }); it('names a day whose hours moved', () => { expect(changedDays(week(), week({ 4: { slots: SPLIT } }))).toEqual([4]); }); it('names a day that opened or closed', () => { expect(changedDays(week(), week({ 1: { isClosed: false, slots: FULL } }))).toEqual([1]); }); });