import { test, expect } from '@chromatic-com/playwright'; import { loginViaAPI, navigateTo } from '../helpers'; import { TEST_USERS, ROUTES } from '../design-tokens'; test.describe('LISIBILITÉ — Taille de texte minimale et line-height', () => { // Public pages for (const route of ROUTES.public) { test(`[PUBLIC] ${route.name} — aucun texte plus petit que 11px`, async ({ page }) => { await navigateTo(page, route.path); const tooSmall = await findTinyText(page); for (const issue of tooSmall) { console.log(`[TEXT SIZE] ${issue}`); } expect(tooSmall.length, `Texte trop petit sur ${route.path}:\n${tooSmall.join('\n')}` ).toBe(0); }); } // Protected pages for (const route of ROUTES.listener.slice(0, 10)) { test(`[PROTECTED] ${route.name} — aucun texte plus petit que 11px`, async ({ page }) => { await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password); await navigateTo(page, route.path); const tooSmall = await findTinyText(page); for (const issue of tooSmall) { console.log(`[TEXT SIZE] ${issue}`); } expect(tooSmall.length, `Texte trop petit sur ${route.path}:\n${tooSmall.join('\n')}` ).toBe(0); }); test(`[PROTECTED] ${route.name} — line-height suffisant sur les blocs de texte`, async ({ page }) => { await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password); await navigateTo(page, route.path); const tightText = await page.evaluate(() => { const issues: string[] = []; const seen = new Set(); document.querySelectorAll('p, li, td, dd').forEach(el => { const text = el.textContent?.trim(); if (!text || text.length < 20) return; const style = getComputedStyle(el); if (style.display === 'none') return; const fontSize = parseFloat(style.fontSize); const lineHeight = parseFloat(style.lineHeight); if (isNaN(lineHeight) || isNaN(fontSize) || fontSize === 0) return; const ratio = lineHeight / fontSize; if (ratio < 1.3 && ratio > 0) { const className = (typeof el.className === 'string' ? el.className : '').split(' ')[0] || ''; const key = `${el.tagName}.${className}`; if (seen.has(key)) return; seen.add(key); const selector = `${el.tagName.toLowerCase()}.${className}`; issues.push( `ÉLÉMENT: ${selector} | PAGE: ${location.pathname} | MESURÉ: line-height ${ratio.toFixed(2)} (${Math.round(lineHeight)}px / ${Math.round(fontSize)}px) | ATTENDU: >= 1.3 | FIX TAILWIND: Ajouter leading-normal (1.5) ou leading-relaxed (1.625) sur ${selector}` ); } }); return issues.slice(0, 10); }); for (const issue of tightText) { console.log(`[LINE HEIGHT] ${issue}`); } expect(tightText.length, `Line-height insuffisant sur ${route.path}:\n${tightText.join('\n')}` ).toBe(0); }); } }); async function findTinyText(page: import('@playwright/test').Page): Promise { return page.evaluate(() => { const issues: string[] = []; const seen = new Set(); document.querySelectorAll('*').forEach(el => { const text = el.textContent?.trim(); if (!text || text.length < 2) return; // Only leaf text nodes if (el.children.length > 0 && el.children[0].textContent?.trim() === text) return; const style = getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden') return; const size = parseFloat(style.fontSize); if (size >= 11 || size === 0) return; // Skip sr-only elements if (el.classList.contains('sr-only')) return; const rect = el.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return; const className = (typeof el.className === 'string' ? el.className : '').split(' ')[0] || ''; const key = `${el.tagName}.${className}`; if (seen.has(key)) return; seen.add(key); const selector = `${el.tagName.toLowerCase()}.${className}`; issues.push( `ÉLÉMENT: ${selector} "${text.slice(0, 20)}" | PAGE: ${location.pathname} | MESURÉ: ${size}px | ATTENDU: >= 11px | FIX TAILWIND: Remplacer text-[${size}px] par text-xs (12px) sur ${selector}` ); }); return issues.slice(0, 10); }); }