forked from yvv/decision
e2e : l'accueil sous la voûte (C1 au bureau, M1 sur téléphone à 412 puis 360) : les trois points dans l'ouverture et centrés, le titre centré en deux lignes, rien d'incliné ni de débordant
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+72
-1
@@ -1,4 +1,4 @@
|
|||||||
import type { Browser, Page } from '@playwright/test';
|
import { expect, type Browser, type Page } from '@playwright/test';
|
||||||
|
|
||||||
export const DOC = '/g/monnaie-libre/docs/acte-engagement-certification';
|
export const DOC = '/g/monnaie-libre/docs/acte-engagement-certification';
|
||||||
export const TEST_DOC = '/g/essai/docs/acte-engagement-certification';
|
export const TEST_DOC = '/g/essai/docs/acte-engagement-certification';
|
||||||
@@ -138,3 +138,74 @@ export async function chain(page: Page, docPath = DOC) {
|
|||||||
(await page.request.post(`${base}/control/${what}`)).json(),
|
(await page.request.post(`${base}/control/${what}`)).json(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The welcome (Yvv, 2026-11-02), in French: the keystone arch shelters the three points, the
|
||||||
|
* toolbox first, each centred under the keystone in the opening; then the title, centred, on
|
||||||
|
* two lines broken after « possible ». Nothing tilted, nothing past the edges of the screen.
|
||||||
|
* Measured once the arch has settled (its entrance done).
|
||||||
|
*/
|
||||||
|
export async function expectWelcome(page: Page) {
|
||||||
|
await expect(page.locator('.vault .leitmotiv li')).toHaveText(['boîte à outils', 'contexte et cadrage', 'processus et modalités']);
|
||||||
|
const h1 = page.getByRole('heading', { level: 1 });
|
||||||
|
await expect(h1).toHaveText('Rendre possible la décision collective');
|
||||||
|
await expect(h1.locator('.line')).toHaveText(['Rendre possible', 'la décision collective']);
|
||||||
|
const m = await page.evaluate(async () => {
|
||||||
|
const intro = document.querySelector('.intro')!;
|
||||||
|
await Promise.all(intro.getAnimations({ subtree: true }).map((a) => a.finished));
|
||||||
|
const text = (el: Element) => {
|
||||||
|
const r = document.createRange();
|
||||||
|
r.selectNodeContents(el);
|
||||||
|
return { box: r.getBoundingClientRect(), rects: r.getClientRects().length };
|
||||||
|
};
|
||||||
|
const v = document.querySelector('.vault')!.getBoundingClientRect();
|
||||||
|
// The arch in its own units: 92 wide (x from −46), the top of the keystone at y = −40.
|
||||||
|
const u = v.width / 92;
|
||||||
|
const at = (x: number, y: number) => ({ x: v.left + (x + 46) * u, y: v.top + (y + 40) * u });
|
||||||
|
const points = [...document.querySelectorAll('.leitmotiv li')].map((li) => {
|
||||||
|
const { box, rects } = text(li);
|
||||||
|
// Back into the arch's units, from its axis and its springing line (y = 0).
|
||||||
|
return { rects, left: (box.left - at(0, 0).x) / u, right: (box.right - at(0, 0).x) / u, top: (box.top - at(0, 0).y) / u, bottom: (box.bottom - at(0, 0).y) / u };
|
||||||
|
});
|
||||||
|
const h = document.querySelector('h1')!.getBoundingClientRect();
|
||||||
|
const lines = [...document.querySelectorAll('h1 .line')].map((l) => ({ ...text(l), block: l.getBoundingClientRect() }));
|
||||||
|
const tilted = [document.querySelector('.vault svg')!, ...document.querySelectorAll('.vault svg path')]
|
||||||
|
.flatMap((n) => {
|
||||||
|
const out: string[] = [];
|
||||||
|
for (let e: Element | null = n; e && e !== document.body; e = e.parentElement) {
|
||||||
|
const cs = getComputedStyle(e);
|
||||||
|
const flat = cs.transform === 'none' || /^matrix\(1, 0, 0, 1, /.test(cs.transform);
|
||||||
|
if (!flat || (cs.rotate && cs.rotate !== 'none')) out.push(`${e.tagName} ${cs.transform} ${cs.rotate}`);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
overflow: document.documentElement.scrollWidth - innerWidth,
|
||||||
|
axis: v.left + v.width / 2,
|
||||||
|
vaultBottom: v.bottom,
|
||||||
|
points,
|
||||||
|
title: { top: h.top, axis: h.left + h.width / 2 },
|
||||||
|
lines: lines.map(({ box, rects, block }) => ({ rects, top: block.top, bottom: block.bottom, axis: box.left + box.width / 2 })),
|
||||||
|
tilted,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(m.overflow, 'nothing past the edge of the screen').toBeLessThanOrEqual(0);
|
||||||
|
expect(m.tilted, 'the arch is never tilted').toEqual([]);
|
||||||
|
// The three points: one line each, top to bottom, centred on the axis, under the keystone (its
|
||||||
|
// foot at y = −19), between the imposts (x = ±19) and above the slab (y = 15.6); a line that
|
||||||
|
// rises above the springing line stays clear of the voussoirs, closer in there (x = ±17).
|
||||||
|
for (const [i, p] of m.points.entries()) {
|
||||||
|
expect(p.rects, `point ${i}: one line`).toBe(1);
|
||||||
|
expect(Math.abs((p.left + p.right) / 2), `point ${i}: centred`).toBeLessThan(0.3);
|
||||||
|
expect(p.top, `point ${i}: under the keystone`).toBeGreaterThan(-19);
|
||||||
|
expect(p.bottom, `point ${i}: above the slab`).toBeLessThan(15.6);
|
||||||
|
expect(p.right, `point ${i}: in the opening`).toBeLessThan(p.top < 0 ? 17 : 19);
|
||||||
|
if (i > 0) expect(p.top, `point ${i}: under the one before`).toBeGreaterThan(m.points[i - 1]!.top);
|
||||||
|
}
|
||||||
|
// The title: under the arch, centred on its axis, two lines of one line each.
|
||||||
|
expect(m.title.top).toBeGreaterThanOrEqual(m.vaultBottom);
|
||||||
|
expect(Math.abs(m.title.axis - m.axis)).toBeLessThan(1);
|
||||||
|
expect(m.lines.map((l) => l.rects)).toEqual([1, 1]);
|
||||||
|
expect(m.lines[1]!.top).toBeGreaterThanOrEqual(m.lines[0]!.bottom - 1);
|
||||||
|
for (const l of m.lines) expect(Math.abs(l.axis - m.axis)).toBeLessThan(1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test, type Locator } from '@playwright/test';
|
import { expect, test, type Locator } from '@playwright/test';
|
||||||
import { DOC, shot, signIn } from './helpers';
|
import { DOC, expectWelcome, shot, signIn } from './helpers';
|
||||||
|
|
||||||
/** WCAG contrast ratio of two CSS colours `rgb(r, g, b)`. */
|
/** WCAG contrast ratio of two CSS colours `rgb(r, g, b)`. */
|
||||||
function contrast(a: string, b: string): number {
|
function contrast(a: string, b: string): number {
|
||||||
@@ -25,13 +25,19 @@ async function backdrop(el: Locator): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test('M1 — mobile : logo dans la barre, menu en tiroir, vote déplié, panier, réglages lisibles, pastilles au clavier', async ({ page }) => {
|
test('M1 — mobile : logo dans la barre, l’accueil sous la voûte, menu en tiroir, vote déplié, panier, réglages lisibles, pastilles au clavier', async ({ page }) => {
|
||||||
await signIn(page, 'Chiara');
|
await signIn(page, 'Chiara');
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
const brand = page.locator('header.topbar a.brand');
|
const brand = page.locator('header.topbar a.brand');
|
||||||
await expect(brand).toHaveAccessibleName('libreDecision');
|
await expect(brand).toHaveAccessibleName('libreDecision');
|
||||||
await expect(brand.locator('svg')).toBeVisible();
|
await expect(brand.locator('svg')).toBeVisible();
|
||||||
await expect(page.locator('a.root')).toContainText('Monnaie libre');
|
await expect(page.locator('a.root')).toContainText('Monnaie libre');
|
||||||
|
// The welcome on this phone, then on a narrow one (360): the arch still shelters the three points.
|
||||||
|
await expectWelcome(page);
|
||||||
|
const size = page.viewportSize();
|
||||||
|
await page.setViewportSize({ width: 360, height: 780 });
|
||||||
|
await expectWelcome(page);
|
||||||
|
if (size) await page.setViewportSize(size);
|
||||||
await page.goto(DOC);
|
await page.goto(DOC);
|
||||||
await page.getByRole('button', { name: 'Menu' }).click();
|
await page.getByRole('button', { name: 'Menu' }).click();
|
||||||
const drawer = page.getByRole('dialog', { name: 'Boîte à outils' });
|
const drawer = page.getByRole('dialog', { name: 'Boîte à outils' });
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
import { BOB, DOC, person, signIn, TEST_DOC, wallet } from './helpers';
|
import { BOB, DOC, expectWelcome, person, signIn, TEST_DOC, wallet } from './helpers';
|
||||||
|
|
||||||
test('C1 — l’accueil sans session : le titre, le leitmotiv en trois lignes, la connexion, le menu complet', async ({ page }) => {
|
test('C1 — l’accueil sans session : la voûte abrite les trois points, le titre centré en deux lignes, la connexion, le menu complet', async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Rendre possible la décision collective');
|
await expectWelcome(page);
|
||||||
await expect(page.locator('.leitmotiv li')).toHaveText(['contexte et cadrage', 'processus et modalités', 'boîte à outils']);
|
|
||||||
const nav = page.getByRole('navigation', { name: 'Boîte à outils' });
|
const nav = page.getByRole('navigation', { name: 'Boîte à outils' });
|
||||||
for (const item of ['Décisions', 'Docs de référence', 'Mandats', 'Protocoles']) {
|
for (const item of ['Décisions', 'Docs de référence', 'Mandats', 'Protocoles']) {
|
||||||
await expect(nav.locator('.tool', { hasText: item })).toBeVisible();
|
await expect(nav.locator('.tool', { hasText: item })).toBeVisible();
|
||||||
|
|||||||
Reference in New Issue
Block a user