veza/tests/e2e/audit/pixel-perfect/10-responsive-layout.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

131 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from '@chromatic-com/playwright';
import { loginViaAPI, navigateTo } from '../helpers';
import { checkOverflow } from '../helpers/visual-helpers';
import { TEST_USERS, ROUTES, VIEWPORTS } from '../design-tokens';
test.describe('RESPONSIVE — Chaque page × chaque viewport, zéro overflow', () => {
const viewportsToTest = [
VIEWPORTS.mobileSE,
VIEWPORTS.tablet,
VIEWPORTS.laptop,
VIEWPORTS.desktop,
] as const;
const viewportNames = ['mobileSE (375×667)', 'tablet (768×1024)', 'laptop (1280×720)', 'desktop (1440×900)'] as const;
// --- Pages publiques ---
for (const route of ROUTES.public) {
for (let v = 0; v < viewportsToTest.length; v++) {
const viewport = viewportsToTest[v];
const vpName = viewportNames[v];
test(`[PUBLIC] ${route.name} @ ${vpName} — pas de débordement horizontal`, async ({ page }) => {
await page.setViewportSize(viewport);
await navigateTo(page, route.path);
const overflows = await checkOverflow(page);
for (const o of overflows) {
console.log(`[OVERFLOW] ${route.path} @ ${vpName}: ${o.selector} dépasse de ${o.overflowX}px`);
console.log(` FIX: ${o.fix}`);
}
expect(overflows.length,
`${overflows.length} débordement(s) sur ${route.path} @ ${vpName}:\n` +
overflows.map(o => `${o.selector}: +${o.overflowX}px → ${o.fix}`).join('\n')
).toBe(0);
});
}
}
// --- Pages protégées (sélection) ---
const protectedPages = [
ROUTES.listener[0], // Dashboard
ROUTES.listener[1], // Feed
ROUTES.listener[2], // Discover
ROUTES.listener[3], // Library
ROUTES.listener[6], // Profile
ROUTES.listener[7], // Settings
ROUTES.listener[10], // Playlists
ROUTES.listener[13], // Marketplace
];
for (const route of protectedPages) {
for (let v = 0; v < viewportsToTest.length; v++) {
const viewport = viewportsToTest[v];
const vpName = viewportNames[v];
test(`[PROTECTED] ${route.name} @ ${vpName} — pas de débordement horizontal`, async ({ page }) => {
await page.setViewportSize(viewport);
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, route.path);
const overflows = await checkOverflow(page);
for (const o of overflows) {
console.log(`[OVERFLOW] ${route.path} @ ${vpName}: ${o.selector} dépasse de ${o.overflowX}px`);
console.log(` FIX: ${o.fix}`);
}
expect(overflows.length,
`${overflows.length} débordement(s) sur ${route.path} @ ${vpName}:\n` +
overflows.map(o => `${o.selector}: +${o.overflowX}px → ${o.fix}`).join('\n')
).toBe(0);
});
}
}
// --- Vérifications mobiles spécifiques ---
test('Mobile — le sidebar est caché par défaut', async ({ page }) => {
await page.setViewportSize(VIEWPORTS.mobileSE);
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, '/dashboard');
const sidebar = page.locator('[data-testid="app-sidebar"]');
// Sur mobile (<1024px), le sidebar devrait être caché ou collapsé
const sidebarVisible = await sidebar.isVisible({ timeout: 3_000 }).catch(() => false);
if (sidebarVisible) {
const box = await sidebar.boundingBox();
if (box && box.width > 100) {
console.log(`[MOBILE] Le sidebar est visible et prend ${box.width}px sur mobile — devrait être caché`);
expect(box.width, `Le sidebar est trop large sur mobile (${box.width}px). FIX: Cacher avec lg:block.`).toBeLessThan(100);
}
}
});
test('Mobile — le contenu principal utilise toute la largeur', async ({ page }) => {
await page.setViewportSize(VIEWPORTS.mobileSE);
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, '/dashboard');
const mainWidth = await page.evaluate(() => {
const main = document.querySelector('main, [role="main"]');
if (!main) return null;
const rect = main.getBoundingClientRect();
return { width: Math.round(rect.width), viewportWidth: window.innerWidth };
});
if (mainWidth) {
const usagePercent = (mainWidth.width / mainWidth.viewportWidth) * 100;
expect(usagePercent,
`Le contenu principal n'utilise que ${usagePercent.toFixed(0)}% de la largeur mobile (${mainWidth.width}px / ${mainWidth.viewportWidth}px). FIX: Retirer les margin-left/right sur mobile.`
).toBeGreaterThan(80);
}
});
test('Mobile — le player bar est visible et accessible', async ({ page }) => {
await page.setViewportSize(VIEWPORTS.mobileSE);
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, '/dashboard');
// Le player bar apparaît quand un track est en lecture
// Vérifier qu'il ne déborde pas sur mobile
const playerBar = page.locator('[data-testid="global-player"]');
if (await playerBar.isVisible({ timeout: 3_000 }).catch(() => false)) {
const box = await playerBar.boundingBox();
if (box) {
expect(box.width, `Player bar dépasse du viewport mobile: ${box.width}px > ${VIEWPORTS.mobileSE.width}px`).toBeLessThanOrEqual(VIEWPORTS.mobileSE.width + 2);
}
}
});
});