Files
ita-ito-horaires/e2e/schedule.spec.ts
T
vliaudatandClaude Opus 5 7312a121df feat: let the quick schedule changes target any day, not just today
Opening later and closing earlier were fixed to today, which was an
assumption rather than a requirement: closing next Thursday afternoon is
the same decision as closing this one, and the shop knows about it in
advance more often than not.

The date is a single control and still defaults to today, so the
two-click path for the most frequent case is unchanged.

The usual hours differ from one weekday to the next, and "opens later"
is late relative to them, so the whole sixty-day window is resolved on
the server and handed over at once. Sixty small entries cost less than a
round trip per change of date.

The date control's accessible name is explicit: "du" alone collided with
the "Du" field of the range form below it, which the end-to-end run
caught as an ambiguous locator — and an ambiguous locator is an
ambiguous label for anyone using a screen reader.

The end-to-end suite ran before this was pushed, unlike last time.

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

123 lines
4.8 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 { expect, pairDevice, query, resetDatabase, signIn, test, today } from './fixtures';
async function panelFilename(request: import('@playwright/test').APIRequestContext, token: string) {
const response = await request.get('/api/display', { headers: { 'Access-Token': token } });
expect(response.ok()).toBeTruthy();
return ((await response.json()) as { filename: string }).filename;
}
test.beforeEach(async () => {
await resetDatabase();
});
test('changing today’s hours reaches the panel', async ({ page, request }) => {
const token = await pairDevice();
const before = await panelFilename(request, token);
await signIn(page, 'admin');
await page.goto('/admin/exceptions');
await page.getByRole('button', { name: /Fermé aujourd’hui/ }).click();
await expect(page.getByRole('status')).toContainText(/sera fermée aujourd’hui/);
// Stored as a manual exception for today, in the shop's timezone.
// Read as text: node-postgres parses a DATE column into a local-midnight
// Date, so toISOString() shifts it back a day at UTC+2. The stored value is
// right; the interpretation would not be.
const [exception] = await query<{ date: string; isClosed: boolean }>(
'SELECT date::text AS date, "isClosed" FROM schedule_exceptions',
);
expect(exception?.date).toBe(today());
expect(exception?.isClosed).toBe(true);
// And the panel is handed a different image, so it will redraw.
expect(await panelFilename(request, token)).not.toBe(before);
await page.goto('/admin');
await expect(page.getByText(/aujourd’hui/).first()).toBeVisible();
});
test('a quick change can target a day other than today', async ({ page }) => {
const target = today(3);
await signIn(page, 'admin');
await page.goto('/admin/exceptions');
// The date defaults to today; closing next Thursday must not cost more
// clicks than closing this afternoon.
await page.getByLabel('Jour à modifier').selectOption(target);
await page.getByRole('button', { name: /^Fermé le / }).click();
await expect(page.getByRole('status')).toContainText(/sera fermée/);
const [exception] = await query<{ date: string; isClosed: boolean }>(
'SELECT date::text AS date, "isClosed" FROM schedule_exceptions',
);
expect(exception?.date).toBe(target);
expect(exception?.isClosed).toBe(true);
});
test('the reference week can be edited and shows what will change', async ({ page }) => {
await signIn(page, 'admin');
await page.goto('/admin/horaires');
// Monday is closed in the seeded week; open it.
await page.getByRole('checkbox', { name: /Ouvert le lundi/i }).check();
await expect(page.getByText(/Lundi sera modifié|Sera modifié|1 jour/i).first()).toBeVisible();
await page.getByRole('button', { name: 'Enregistrer' }).click();
await expect(page.getByText(/Horaires enregistrés|enregistrés/i)).toBeVisible();
const [monday] = await query<{ isClosed: boolean }>(
'SELECT "isClosed" FROM weekly_schedules WHERE "dayOfWeek" = 1',
);
expect(monday?.isClosed).toBe(false);
});
test('a closure period closes the days it covers', async ({ page }) => {
const start = today();
const end = today(3);
await signIn(page, 'admin');
await page.goto('/admin/vacances');
await page.getByLabel('Du').fill(start);
await page.getByLabel('Au (inclus)').fill(end);
await page.getByLabel(/Libellé/).fill('Congés de test');
await page.getByRole('button', { name: 'Ajouter' }).click();
await expect(page.getByText(/Période de fermeture enregistrée/)).toBeVisible();
await expect(page.getByText('Congés de test')).toBeVisible();
// The resolver reads the period directly, so the dashboard shows the days
// closed without anything having been written into the exception table.
await page.goto('/admin');
await expect(page.getByText('Congés de test').first()).toBeVisible();
const [{ count }] = await query<{ count: string }>('SELECT count(*) FROM schedule_exceptions');
expect(Number(count)).toBe(0);
});
test('overlapping periods are refused by name', async ({ page }) => {
const start = today();
const end = today(5);
await signIn(page, 'admin');
await page.goto('/admin/vacances');
await page.getByLabel('Du').fill(start);
await page.getByLabel('Au (inclus)').fill(end);
await page.getByLabel(/Libellé/).fill('Première');
await page.getByRole('button', { name: 'Ajouter' }).click();
await expect(page.getByText(/enregistrée/)).toBeVisible();
// The form empties itself once the server answers; refilling before that
// would have the reset wipe what was just typed.
await expect(page.getByLabel(/Libellé/)).toHaveValue('');
await page.getByLabel('Du').fill(start);
await page.getByLabel('Au (inclus)').fill(end);
await page.getByLabel(/Libellé/).fill('Seconde');
await page.getByRole('button', { name: 'Ajouter' }).click();
await expect(page.getByText(/chevauche « Première »/)).toBeVisible();
});