- 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>
129 lines
5.1 KiB
TypeScript
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);
|
|
});
|
|
});
|