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 20:18:49 +00:00
import { test , expect } from '@chromatic-com/playwright' ;
test: update e2e test suite and add audit tests
Refine auth, player, tracks, playlists, search, workflows, edge cases,
forms, responsive, network errors, error boundary, performance, visual
regression, cross-browser, profile, smoke, storybook, chat, and session
tests. Add audit test suite (accessibility, ethical, functional, design
tokens). Update test helpers and visual snapshots.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 15:06:26 +00:00
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 ) ;
}
}
} ) ;
} ) ;