Files
ita-ito-horaires/lib/schedule/format.test.ts
T
vliaudatandClaude Opus 5 ed89d08172 fix: show one clock convention in English on the panel
The labels this application writes itself already read "6:30 pm", while
a translated notice kept whatever the French said, so the screen could
show "Closes at 6:30 pm" above "Delayed opening at 14:00". Two
conventions side by side on one panel.

The conversion is done in code, not by the prompt. The model is still
told to leave times alone — reading a clock and rewriting it is
deterministic work, and asking a language model to do it introduces a
failure mode for no benefit. Only HH:mm is touched, never a time that
already carries am or pm, so the pass is idempotent and years, dates
written 20/07 and percentages are left alone.

Hand-written translations are not rewritten: that wording was somebody's
explicit choice.

Verified against the live service: "Ouvert 10:00 – 13:00 puis 14:00 –
18:30" now comes back as "Open 10:00 am – 1:00 pm then 2:00 pm –
6:30 pm".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
2026-09-20 22:37:59 +02:00

176 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import {
formatDateEn,
formatDateFr,
formatHoursText,
formatNextChangeEn,
formatNextChangeFr,
formatTimeEn,
shortDayLabelEn,
shortDayLabelFr,
toEnglishClock,
} from './format';
describe('formatDateFr / formatDateEn', () => {
it('writes the day and month in full', () => {
expect(formatDateFr('2026-09-20')).toBe('Dimanche 20 septembre');
expect(formatDateEn('2026-09-20')).toBe('Sunday 20 September');
});
it('does not pad the day number', () => {
expect(formatDateFr('2026-09-01')).toBe('Mardi 1 septembre');
expect(formatDateEn('2026-09-01')).toBe('Tuesday 1 September');
});
it('handles every month', () => {
expect(formatDateFr('2026-01-15')).toBe('Jeudi 15 janvier');
expect(formatDateFr('2026-08-15')).toBe('Samedi 15 août');
expect(formatDateFr('2026-12-25')).toBe('Vendredi 25 décembre');
expect(formatDateEn('2026-12-25')).toBe('Friday 25 December');
});
});
describe('short day labels', () => {
it('uses three letters, Monday first on the screen', () => {
expect([1, 2, 3, 4, 5, 6, 0].map(shortDayLabelFr)).toEqual([
'Lun',
'Mar',
'Mer',
'Jeu',
'Ven',
'Sam',
'Dim',
]);
expect([1, 2, 3, 4, 5, 6, 0].map(shortDayLabelEn)).toEqual([
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat',
'Sun',
]);
});
});
describe('formatHoursText', () => {
it('renders a single slot', () => {
expect(formatHoursText([{ open: '10:00', close: '18:30' }])).toBe('10:00 – 18:30');
});
it('joins several slots with a middle dot', () => {
expect(
formatHoursText([
{ open: '10:00', close: '13:00' },
{ open: '14:00', close: '18:30' },
]),
).toBe('10:00 – 13:00 · 14:00 – 18:30');
});
it('returns an empty string when the shop is closed', () => {
expect(formatHoursText([])).toBe('');
});
});
describe('formatTimeEn', () => {
it('uses the twelve-hour clock', () => {
expect(formatTimeEn('18:30')).toBe('6:30 pm');
expect(formatTimeEn('10:00')).toBe('10:00 am');
});
it('names noon and midnight correctly', () => {
expect(formatTimeEn('12:00')).toBe('12:00 pm');
expect(formatTimeEn('00:30')).toBe('12:30 am');
});
});
describe('formatNextChange', () => {
it('announces today’s closing time', () => {
expect(formatNextChangeFr({ kind: 'CLOSES', date: '2026-09-20', time: '18:30' }, '2026-09-20')).toBe(
'Ferme à 18:30',
);
expect(formatNextChangeEn({ kind: 'CLOSES', date: '2026-09-20', time: '18:30' }, '2026-09-20')).toBe(
'Closes at 6:30 pm',
);
});
it('announces a reopening later today', () => {
expect(formatNextChangeFr({ kind: 'OPENS', date: '2026-09-20', time: '14:00' }, '2026-09-20')).toBe(
'Ouvre à 14:00',
);
expect(formatNextChangeEn({ kind: 'OPENS', date: '2026-09-20', time: '14:00' }, '2026-09-20')).toBe(
'Opens at 2:00 pm',
);
});
it('announces tomorrow by name', () => {
expect(formatNextChangeFr({ kind: 'OPENS', date: '2026-09-21', time: '10:00' }, '2026-09-20')).toBe(
'Ouvre demain à 10:00',
);
expect(formatNextChangeEn({ kind: 'OPENS', date: '2026-09-21', time: '10:00' }, '2026-09-20')).toBe(
'Opens tomorrow at 10:00 am',
);
});
it('names the weekday when the reopening is further out', () => {
expect(formatNextChangeFr({ kind: 'OPENS', date: '2026-09-22', time: '10:00' }, '2026-09-20')).toBe(
'Ouvre mardi à 10:00',
);
expect(formatNextChangeEn({ kind: 'OPENS', date: '2026-09-22', time: '10:00' }, '2026-09-20')).toBe(
'Opens Tuesday at 10:00 am',
);
});
it('says nothing when there is nothing to announce', () => {
expect(formatNextChangeFr(null, '2026-09-20')).toBe('');
expect(formatNextChangeEn(null, '2026-09-20')).toBe('');
});
});
describe('toEnglishClock', () => {
it('converts a single time', () => {
expect(toEnglishClock('Delayed opening at 14:00 today')).toBe(
'Delayed opening at 2:00 pm today',
);
});
it('converts every time in a range', () => {
expect(toEnglishClock('Open 10:00 – 13:00 · 14:00 – 18:30')).toBe(
'Open 10:00 am – 1:00 pm · 2:00 pm – 6:30 pm',
);
});
it('handles times written without a space around the dash', () => {
expect(toEnglishClock('10:00-18:30')).toBe('10:00 am-6:30 pm');
});
it('names noon and midnight', () => {
expect(toEnglishClock('Closing at 12:00')).toBe('Closing at 12:00 pm');
expect(toEnglishClock('Until 00:30')).toBe('Until 12:30 am');
});
it('leaves a time that already has am or pm alone', () => {
// Otherwise a second pass would mangle what the first one produced.
expect(toEnglishClock('Closes at 6:30 pm')).toBe('Closes at 6:30 pm');
expect(toEnglishClock('Closes at 6:30 PM')).toBe('Closes at 6:30 PM');
});
it('is stable when applied twice', () => {
const once = toEnglishClock('Open 10:00 – 18:30');
expect(toEnglishClock(once)).toBe(once);
});
it('leaves years, dates and prices alone', () => {
expect(toEnglishClock('Annual leave from July 20 to August 10')).toBe(
'Annual leave from July 20 to August 10',
);
expect(toEnglishClock('Collection 2026')).toBe('Collection 2026');
expect(toEnglishClock('Sale: 30% off')).toBe('Sale: 30% off');
});
it('leaves text with no time untouched', () => {
expect(toEnglishClock('New collection in store')).toBe('New collection in store');
});
});