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; } /** The seeded week trades Tuesday to Saturday; Sunday and Monday are closed. */ function nextTradingDay(): string { for (let offset = 0; offset < 7; offset += 1) { const date = today(offset); const weekday = new Date(`${date}T00:00:00.000Z`).getUTCDay(); if (weekday >= 2) { return date; } } throw new Error('aucun jour ouvré dans les sept prochains jours'); } 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 quick controls start from the selected day’s own hours', async ({ page }) => { // The seeded week is closed on Sunday and Monday, so the day is chosen // rather than assumed: a conditional assertion would pass in silence on the // days it matters least. const tradingDay = nextTradingDay(); await signIn(page, 'admin'); await page.goto('/admin/exceptions'); await page.getByLabel('Jour à modifier').selectOption(tradingDay); // An arbitrary 14:00 would be wrong on most days and leave the preview // showing hours nobody asked for. await expect(page.getByLabel('Nouvelle heure d’ouverture')).toHaveValue('10:00'); await expect(page.getByLabel('Nouvelle heure de fermeture')).toHaveValue('18:30'); await expect(page.getByText('Habituellement 10:00 – 18:30.')).toBeVisible(); }); 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(); });