veza/tests/e2e/audit/pixel-perfect/05-typography.spec.ts
senke 7b39efa176 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

129 lines
5.1 KiB
TypeScript

import { test, expect } from '@chromatic-com/playwright';
import { loginViaAPI, navigateTo } from '../helpers';
import { checkHeadingHierarchy } from '../helpers/interaction-helpers';
import { TEST_USERS, ROUTES, FONTS } from '../design-tokens';
const ALLOWED_FONTS = [
FONTS.heading.family.toLowerCase(),
FONTS.body.family.toLowerCase(),
FONTS.mono.family.toLowerCase(),
FONTS.serif.family.toLowerCase(),
'system-ui',
'ui-sans-serif',
'segoe ui',
'apple', // -apple-system
'helvetica',
'arial',
'sans-serif',
'monospace',
'serif',
];
test.describe('TYPOGRAPHIE — Fonts, tailles et hiérarchie correctes', () => {
// --- Pages publiques ---
for (const route of ROUTES.public) {
test(`[PUBLIC] ${route.name} — toutes les fonts sont du design system SUMI`, async ({ page }) => {
await navigateTo(page, route.path);
const violations = await page.evaluate((allowed) => {
const issues: string[] = [];
const seen = new Set<string>();
document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, button, label, input, textarea').forEach(el => {
if (!el.textContent?.trim()) return;
if (getComputedStyle(el).display === 'none') return;
const font = getComputedStyle(el).fontFamily.toLowerCase();
const firstFont = font.split(',')[0].replace(/['"]/g, '').trim();
const isAllowed = allowed.some((f: string) => font.includes(f));
if (!isAllowed) {
const key = firstFont;
if (seen.has(key)) return;
seen.add(key);
issues.push(`<${el.tagName.toLowerCase()}> "${el.textContent?.trim().slice(0, 20)}" → font: ${firstFont} (devrait être Inter, Space Grotesk, JetBrains Mono, ou Noto Serif JP)`);
}
});
return issues;
}, ALLOWED_FONTS);
expect(violations.length,
`Fonts hors SUMI sur ${route.path}:\n${violations.join('\n')}`
).toBe(0);
});
}
// --- Pages protégées ---
const protectedPages = ROUTES.listener.slice(0, 10);
for (const route of protectedPages) {
test(`[PROTECTED] ${route.name} — toutes les fonts sont du design system SUMI`, async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, route.path);
const violations = await page.evaluate((allowed) => {
const issues: string[] = [];
const seen = new Set<string>();
document.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, button, label').forEach(el => {
if (!el.textContent?.trim()) return;
if (getComputedStyle(el).display === 'none') return;
const font = getComputedStyle(el).fontFamily.toLowerCase();
const firstFont = font.split(',')[0].replace(/['"]/g, '').trim();
const isAllowed = allowed.some((f: string) => font.includes(f));
if (!isAllowed) {
const key = firstFont;
if (seen.has(key)) return;
seen.add(key);
issues.push(`<${el.tagName.toLowerCase()}> "${el.textContent?.trim().slice(0, 20)}" → font: ${firstFont}`);
}
});
return issues;
}, ALLOWED_FONTS);
expect(violations.length,
`Fonts hors SUMI sur ${route.path}:\n${violations.join('\n')}`
).toBe(0);
});
test(`[PROTECTED] ${route.name} — hiérarchie des titres logique`, async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, route.path);
const issues = await checkHeadingHierarchy(page);
for (const issue of issues) {
console.log(`[HEADING] ${route.path}: ${issue.issue}`);
}
expect(issues.length,
`Hiérarchie de titres cassée sur ${route.path}:\n${issues.map(i => i.issue).join('\n')}`
).toBe(0);
});
}
test('Les headings utilisent Space Grotesk (font-heading)', async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, '/dashboard');
const headingFonts = await page.evaluate(() => {
const results: Array<{ tag: string; text: string; font: string }> = [];
document.querySelectorAll('h1, h2, h3').forEach(el => {
if (getComputedStyle(el).display === 'none') return;
results.push({
tag: el.tagName,
text: el.textContent?.trim().slice(0, 30) || '',
font: getComputedStyle(el).fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
});
});
return results;
});
const nonSpaceGrotesk = headingFonts.filter(h => !h.font.toLowerCase().includes('space grotesk'));
if (nonSpaceGrotesk.length > 0) {
console.log('[TYPOGRAPHY] Headings sans Space Grotesk:');
for (const h of nonSpaceGrotesk) {
console.log(` <${h.tag}> "${h.text}" → ${h.font} (devrait être Space Grotesk)`);
}
}
expect(nonSpaceGrotesk.length,
`Headings utilisant la mauvaise police:\n${nonSpaceGrotesk.map(h => `<${h.tag}> "${h.text}" → ${h.font}`).join('\n')}`
).toBe(0);
});
});