- 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>
121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
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<string>();
|
|
|
|
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<string[]> {
|
|
return page.evaluate(() => {
|
|
const issues: string[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
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);
|
|
});
|
|
}
|