veza/tests/e2e/audit/pixel-perfect/17-scroll-containers.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

112 lines
4.5 KiB
TypeScript

import { test, expect } from '@chromatic-com/playwright';
import { loginViaAPI, navigateTo } from '../helpers';
import { TEST_USERS, VIEWPORTS } from '../design-tokens';
test.describe('SCROLL — Les conteneurs scrollables fonctionnent', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
});
test('Header reste sticky/fixed au scroll', async ({ page }) => {
await navigateTo(page, '/dashboard');
// Scroller vers le bas
await page.evaluate(() => window.scrollTo(0, 500));
await page.waitForTimeout(300);
const headerState = await page.evaluate(() => {
const header = document.querySelector('header, [data-testid="app-header"], [role="banner"]');
if (!header) return null;
const rect = header.getBoundingClientRect();
const style = getComputedStyle(header);
return {
top: Math.round(rect.top),
position: style.position,
visible: rect.top >= -5 && rect.top < 100,
};
});
if (headerState) {
console.log(`[SCROLL] Header après scroll: position=${headerState.position}, top=${headerState.top}px, visible=${headerState.visible}`);
expect(headerState.visible,
`ÉLÉMENT: header | PAGE: /dashboard | MESURÉ: top=${headerState.top}px après scroll | ATTENDU: visible (top < 100px) | FIX TAILWIND: Ajouter sticky top-0 z-50 sur le header`
).toBe(true);
}
});
test('Sidebar reste fixed au scroll', async ({ page }) => {
await page.setViewportSize(VIEWPORTS.desktop);
await navigateTo(page, '/dashboard');
await page.evaluate(() => window.scrollTo(0, 500));
await page.waitForTimeout(300);
const sidebarState = await page.evaluate(() => {
const sidebar = document.querySelector('[data-testid="app-sidebar"]');
if (!sidebar) return null;
const rect = sidebar.getBoundingClientRect();
const style = getComputedStyle(sidebar);
return {
top: Math.round(rect.top),
position: style.position,
height: Math.round(rect.height),
visible: rect.height > 200,
};
});
if (sidebarState) {
console.log(`[SCROLL] Sidebar après scroll: position=${sidebarState.position}, top=${sidebarState.top}px`);
expect(sidebarState.visible,
`ÉLÉMENT: [data-testid="app-sidebar"] | PAGE: /dashboard | MESURÉ: height=${sidebarState.height}px après scroll | ATTENDU: visible (height > 200px) | FIX TAILWIND: Ajouter fixed ou sticky sur le sidebar`
).toBe(true);
}
});
test('Scroller tout en bas ne casse pas le layout', async ({ page }) => {
await navigateTo(page, '/discover');
// Scroller tout en bas
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(500);
// Vérifier que rien n'est cassé
const layout = await page.evaluate(() => {
const body = document.body.textContent || '';
const hasError = /500|Internal Server Error|undefined|null/.test(body);
const viewportWidth = document.documentElement.clientWidth;
const scrollWidth = document.documentElement.scrollWidth;
return {
hasError,
horizontalOverflow: scrollWidth > viewportWidth + 5,
bodyLength: body.length,
};
});
expect(layout.hasError, 'Erreur visible après scroll tout en bas').toBe(false);
expect(layout.horizontalOverflow,
`Scroll horizontal apparu après scroll vertical. scrollWidth=${await page.evaluate(() => document.documentElement.scrollWidth)}px > viewport`
).toBe(false);
});
test('Sidebar scrollable si contenu dépasse', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 600 }); // Hauteur réduite pour forcer le scroll
await navigateTo(page, '/dashboard');
const sidebarScroll = await page.evaluate(() => {
const sidebar = document.querySelector('[data-testid="app-sidebar"]');
if (!sidebar) return null;
const nav = sidebar.querySelector('nav') || sidebar;
return {
scrollHeight: nav.scrollHeight,
clientHeight: nav.clientHeight,
isScrollable: nav.scrollHeight > nav.clientHeight + 10,
overflow: getComputedStyle(nav).overflowY,
};
});
if (sidebarScroll && sidebarScroll.isScrollable) {
console.log(`[SCROLL] Sidebar nav scrollable: ${sidebarScroll.scrollHeight}px content dans ${sidebarScroll.clientHeight}px container, overflow=${sidebarScroll.overflow}`);
expect(['auto', 'scroll', 'overlay']).toContain(sidebarScroll.overflow);
}
});
});