veza/tests/e2e/audit/pixel-perfect/11-text-overflow.spec.ts
senke 6fad0ad68d fix: stabilize frontend — 98 TS errors to 0, align API endpoints, optimize bundle
- Fix 98 TypeScript errors across 37 files:
  - Service layer double-unwrapping (subscriptionService, distributionService, gearService)
  - Self-referencing variables in SearchPageResults
  - FeedView/ExploreView .posts→.items alignment
  - useQueueSync Zustand subscribe API
  - AdminAuditLogsView missing interface fields
  - Toast proxy type, interceptor type narrowing
  - 22 unused imports/variables removed
  - 5 storybook mock data fixes

- Align frontend API calls with backend endpoints:
  - Analytics: useAnalyticsView now calls /creator/analytics/dashboard (was /analytics)
  - Chat: chatService uses /conversations (was mock data), WS URL from backend token
  - Dashboard StatsSection: uses real /dashboard API data (was hardcoded zeros)
  - Settings: suppress 2FA toast error when endpoint unavailable

- Fix marketplace products: seed uses 'active' status (was 'published')
- Enrich seed: admin follows all creators (feed has content)

- Optimize bundle: vendor catch-all 793KB→318KB gzip (-60%)
  Split into vendor-charts, vendor-emoji, vendor-swagger, vendor-media, etc.

- Clean repo: remove ~100 orphaned screenshots, audit reports, logs from root

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:18:49 +01:00

124 lines
5.7 KiB
TypeScript

import { test, expect } from '@chromatic-com/playwright';
import { loginViaAPI, navigateTo } from '../helpers';
import { TEST_USERS, ROUTES } from '../design-tokens';
test.describe('TEXTE — Pas de texte coupé ni qui déborde de son conteneur', () => {
// Pages publiques
for (const route of ROUTES.public) {
test(`[PUBLIC] ${route.name} — aucun texte ne déborde`, async ({ page }) => {
await navigateTo(page, route.path);
const overflowingText = await page.evaluate(() => {
const issues: Array<{ text: string; selector: string; containerWidth: number; textWidth: number; overflow: number; fix: string }> = [];
document.querySelectorAll('p, span, h1, h2, h3, h4, h5, h6, a, button, label, td, th, li').forEach(el => {
const text = el.textContent?.trim();
if (!text || text.length < 3) return;
if (el.children.length > 2) return;
const style = getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') return;
if (el.clientWidth === 0) return;
const isOverflowing = el.scrollWidth > el.clientWidth + 2;
const hasEllipsis = style.textOverflow === 'ellipsis' && style.overflow === 'hidden';
const hasLineClamp = style.webkitLineClamp !== '' && style.webkitLineClamp !== 'none';
const isPreWrap = style.whiteSpace === 'pre' || style.whiteSpace === 'pre-wrap' || style.whiteSpace === 'nowrap';
if (isOverflowing && !hasEllipsis && !hasLineClamp) {
// Skip elements inside overflow:hidden parents (already clipped)
let clipped = false;
let parent = el.parentElement;
while (parent) {
const ps = getComputedStyle(parent);
if (ps.overflow === 'hidden' || ps.overflowX === 'hidden') { clipped = true; break; }
parent = parent.parentElement;
}
if (clipped) return;
const className = (typeof el.className === 'string' ? el.className : '').split(' ')[0] || '';
const testid = el.getAttribute('data-testid');
const selector = testid ? `[data-testid="${testid}"]` : `${el.tagName.toLowerCase()}.${className}`;
const overflow = Math.round(el.scrollWidth - el.clientWidth);
issues.push({
text: text.slice(0, 40),
selector,
containerWidth: Math.round(el.clientWidth),
textWidth: Math.round(el.scrollWidth),
overflow,
fix: `ÉLÉMENT: ${selector} | PAGE: ${location.pathname} | MESURÉ: scrollWidth ${Math.round(el.scrollWidth)}px > clientWidth ${Math.round(el.clientWidth)}px (+${overflow}px) | FIX TAILWIND: Ajouter truncate (overflow-hidden text-ellipsis whitespace-nowrap) ou line-clamp-2 sur ${selector}`,
});
}
});
return issues.slice(0, 20);
});
for (const issue of overflowingText) {
console.log(`[TEXT OVERFLOW] ${issue.fix}`);
}
expect(overflowingText.length,
`${overflowingText.length} texte(s) qui déborde(nt) sur ${route.path}:\n` +
overflowingText.map(i => `${i.fix}`).join('\n')
).toBe(0);
});
}
// Pages protégées
for (const route of ROUTES.listener.slice(0, 12)) {
test(`[PROTECTED] ${route.name} — aucun texte ne déborde`, async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, route.path);
const overflowingText = await page.evaluate(() => {
const issues: Array<{ text: string; selector: string; overflow: number; fix: string }> = [];
document.querySelectorAll('p, span, h1, h2, h3, h4, h5, h6, a, button, label, td, th, li').forEach(el => {
const text = el.textContent?.trim();
if (!text || text.length < 3 || el.children.length > 2) return;
const style = getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden' || el.clientWidth === 0) return;
const isOverflowing = el.scrollWidth > el.clientWidth + 2;
const hasEllipsis = style.textOverflow === 'ellipsis' && style.overflow === 'hidden';
const hasLineClamp = style.webkitLineClamp !== '' && style.webkitLineClamp !== 'none';
if (isOverflowing && !hasEllipsis && !hasLineClamp) {
let clipped = false;
let parent = el.parentElement;
while (parent) {
const ps = getComputedStyle(parent);
if (ps.overflow === 'hidden' || ps.overflowX === 'hidden') { clipped = true; break; }
parent = parent.parentElement;
}
if (clipped) return;
const className = (typeof el.className === 'string' ? el.className : '').split(' ')[0] || '';
const testid = el.getAttribute('data-testid');
const selector = testid ? `[data-testid="${testid}"]` : `${el.tagName.toLowerCase()}.${className}`;
const overflow = Math.round(el.scrollWidth - el.clientWidth);
issues.push({
text: text.slice(0, 40),
selector,
overflow,
fix: `ÉLÉMENT: ${selector} | PAGE: ${location.pathname} | MESURÉ: +${overflow}px de texte débordant | FIX TAILWIND: Ajouter truncate ou line-clamp-2 sur ${selector}`,
});
}
});
return issues.slice(0, 20);
});
for (const issue of overflowingText) {
console.log(`[TEXT OVERFLOW] ${issue.fix}`);
}
expect(overflowingText.length,
`${overflowingText.length} texte(s) qui déborde(nt) sur ${route.path}:\n` +
overflowingText.map(i => `${i.fix}`).join('\n')
).toBe(0);
});
}
});