diff --git a/web/e2e/ouverture.spec.ts b/web/e2e/ouverture.spec.ts index 813b470..7831ec5 100644 --- a/web/e2e/ouverture.spec.ts +++ b/web/e2e/ouverture.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from '@playwright/test'; +import { devices, expect, test, type Page } from '@playwright/test'; import { DOC, seedsInForce, shot, signIn, TEST_DOC } from './helpers'; // Lot U3 (Yvv, 2026-11-03, vers 20 h 38 et avant 20 h 44): the rules of operation leave the votes @@ -255,3 +255,77 @@ test('C43 — les informations de fonctionnement là où elles servent : le vote await expect(inForce).toContainText('Si plusieurs versions atteignent leur seuil, celle qui a la marge la plus large entre en vigueur.'); await shot(page, 'c43-3-ordre-rival'); }); + +/** + * What of the table of the five levels is not seen whole in the settings panel: a header or a + * cell that leaves the table or the panel, a text that leaves its cell; the table, the panel or + * the page scrolled sideways to reach it. Empty when every column reads in full. + */ +async function hiddenInSettings(page: Page): Promise { + return page.evaluate(() => { + const panel = document.querySelector('aside.panel'); + const scroll = panel?.querySelector('.settings .scroll'); + const table = scroll?.querySelector('table'); + const body = panel?.querySelector('.body'); + if (!panel || !scroll || !table || !body) return ['pas de tableau dans le panneau']; + const faults: string[] = []; + for (const [el, what] of [ + [scroll, 'le tableau'], + [body, 'le panneau'], + [document.documentElement, 'la page'], + ] as const) { + if (el.scrollWidth > el.clientWidth + 1) faults.push(`${what} défile de côté (${el.scrollWidth} > ${el.clientWidth})`); + } + const p = panel.getBoundingClientRect(); + const s = scroll.getBoundingClientRect(); + const left = Math.max(p.left, s.left, 0); + const right = Math.min(p.right, s.right, document.documentElement.clientWidth); + const cols = table.querySelectorAll('thead th').length; + if (cols !== 6) faults.push(`${cols} colonnes au lieu de 6`); + for (const cell of table.querySelectorAll('th, td')) { + const name = (cell.textContent ?? '').replace(/\s+/g, ' ').trim(); + const c = cell.getBoundingClientRect(); + const range = document.createRange(); + range.selectNodeContents(cell); + const r = range.getBoundingClientRect(); + if (c.left < left - 0.5 || c.right > right + 0.5) + faults.push(`« ${name} » : sa cellule sort du panneau (${Math.round(c.left)}–${Math.round(c.right)} hors de ${Math.round(left)}–${Math.round(right)})`); + if (r.left < c.left - 0.5 || r.right > c.right + 0.5) faults.push(`« ${name} » : son texte sort de sa cellule`); + } + return faults; + }); +} + +test('C44 — les réglages de la formule se lisent en entier : chaque colonne des cinq crans et chaque en-tête dans le panneau, sur bureau et sur téléphone', async ({ page, browser }) => { + // Lot U3b (03/11): on a desktop, the panel cut the table on the right, « Voix pour » out of sight. + await signIn(page, 'Chiara'); + const g = await (await page.request.get('/api/groups/monnaie-libre')).json(); + const d = await (await page.request.get(`/api/docs/${g.docs[0].id}`)).json(); + const e1 = d.units.find((u: { code: string }) => u.code === 'E1'); + const version: string = e1.proposals.find((p: { seedProposal?: boolean }) => p.seedProposal).id; + const phone = await browser.newContext({ ...devices['Pixel 7'], locale: 'fr-FR' }); + const screens: { name: string; tag: string; page: Page; size?: { width: number; height: number } }[] = [ + { name: 'bureau 1360 × 900', tag: 'bureau-1360', page, size: { width: 1360, height: 900 } }, + { name: 'bureau 1024 × 768', tag: 'bureau-1024', page, size: { width: 1024, height: 768 } }, + { name: 'téléphone (Pixel 7)', tag: 'telephone', page: await phone.newPage() }, + ]; + for (const screen of screens) { + const p = screen.page; + if (screen.size) await p.setViewportSize(screen.size); + if (p !== page) await signIn(p, 'Chiara'); + // From the head of the document (the usual turnout), then from a version (« cet article » in its row). + for (const from of ['tete', 'version']) { + await p.goto(from === 'tete' ? `${DOC}?onglet=reglages` : `${DOC}?onglet=reglages&version=${version}`); + const panel = p.locator('aside.panel'); + await expect(panel.getByRole('heading', { name: 'Réglages de la formule' })).toBeVisible(); + await expect(panel.locator('table tbody tr')).toHaveCount(5); + if (from === 'version') await expect(panel.locator('tr.current th')).toContainText('cet article'); + await expect(panel.locator('thead th').last()).toHaveText('Voix pour'); + await panel.locator('table').scrollIntoViewIfNeeded(); + // Soft: every screen is looked at, and the report names each one where the table is cut. + expect.soft(await hiddenInSettings(p), `${screen.name}, ${from === 'tete' ? 'en tête du document' : 'depuis une version'}`).toEqual([]); + await shot(p, `c44-${screen.tag}-${from}`); + } + } + await phone.close(); +});