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, navigateToPageWithTracks, playFirstTrack } from '../helpers';
|
|
|
|
|
|
import { detectOverlaps } from '../helpers/visual-helpers';
|
|
|
|
|
|
import { TEST_USERS, ROUTES, ALL_PROTECTED_ROUTES } from '../design-tokens';
|
|
|
|
|
|
|
|
|
|
|
|
test.describe('CHEVAUCHEMENTS — Aucun élément interactif ne passe par-dessus un autre', () => {
|
|
|
|
|
|
// --- Pages publiques (pas de login) ---
|
|
|
|
|
|
for (const route of ROUTES.public) {
|
|
|
|
|
|
test(`[PUBLIC] ${route.name} (${route.path}) — zéro chevauchement critique`, async ({ page }) => {
|
|
|
|
|
|
await navigateTo(page, route.path);
|
|
|
|
|
|
|
|
|
|
|
|
const overlaps = await detectOverlaps(page);
|
|
|
|
|
|
const critical = overlaps.filter(o => o.severity === 'critical');
|
|
|
|
|
|
|
|
|
|
|
|
for (const o of overlaps) {
|
|
|
|
|
|
console.log(`[${o.severity.toUpperCase()}] "${o.elementA.text}" ↔ "${o.elementB.text}" : ${o.overlapX}px × ${o.overlapY}px`);
|
|
|
|
|
|
console.log(` FIX: ${o.fix}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expect(critical.length,
|
|
|
|
|
|
`${critical.length} chevauchement(s) critique(s) sur ${route.path}:\n` +
|
|
|
|
|
|
critical.map(o => `• "${o.elementA.text}" (${o.elementA.rect.x},${o.elementA.rect.y} ${o.elementA.rect.width}×${o.elementA.rect.height}) ↔ "${o.elementB.text}" (${o.elementB.rect.x},${o.elementB.rect.y} ${o.elementB.rect.width}×${o.elementB.rect.height}) overlap: ${o.overlapX}×${o.overlapY}px → ${o.fix}`).join('\n')
|
|
|
|
|
|
).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Pages protégées (listener) ---
|
|
|
|
|
|
for (const route of ROUTES.listener) {
|
|
|
|
|
|
test(`[LISTENER] ${route.name} (${route.path}) — zéro chevauchement critique`, async ({ page }) => {
|
|
|
|
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
|
|
|
|
await navigateTo(page, route.path);
|
|
|
|
|
|
|
|
|
|
|
|
const overlaps = await detectOverlaps(page);
|
|
|
|
|
|
const critical = overlaps.filter(o => o.severity === 'critical');
|
|
|
|
|
|
|
|
|
|
|
|
for (const o of overlaps) {
|
|
|
|
|
|
console.log(`[${o.severity.toUpperCase()}] "${o.elementA.text}" ↔ "${o.elementB.text}" : ${o.overlapX}px × ${o.overlapY}px`);
|
|
|
|
|
|
console.log(` FIX: ${o.fix}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expect(critical.length,
|
|
|
|
|
|
`${critical.length} chevauchement(s) critique(s) sur ${route.path}:\n` +
|
|
|
|
|
|
critical.map(o => `• "${o.elementA.text}" ↔ "${o.elementB.text}" ${o.overlapX}×${o.overlapY}px → ${o.fix}`).join('\n')
|
|
|
|
|
|
).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Player bar ne recouvre pas le contenu ---
|
|
|
|
|
|
test('Le player bar ne recouvre aucun contenu interactif de la page', async ({ page }) => {
|
|
|
|
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
|
|
|
|
|
|
|
|
|
|
const hasTracks = await navigateToPageWithTracks(page);
|
|
|
|
|
|
if (!hasTracks) {
|
|
|
|
|
|
console.log('Pas de tracks disponibles — test player bar non applicable');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await playFirstTrack(page);
|
|
|
|
|
|
await page.waitForTimeout(2_000);
|
|
|
|
|
|
|
|
|
|
|
|
const playerIssues = await page.evaluate(() => {
|
|
|
|
|
|
const player = document.querySelector('[data-testid="global-player"]');
|
|
|
|
|
|
if (!player) return ['Player bar non trouvé'];
|
|
|
|
|
|
|
|
|
|
|
|
const playerRect = player.getBoundingClientRect();
|
|
|
|
|
|
const issues: string[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
// Vérifier que le main content a assez de padding en bas
|
|
|
|
|
|
const main = document.querySelector('main, [role="main"]');
|
|
|
|
|
|
if (main) {
|
|
|
|
|
|
const mainRect = main.getBoundingClientRect();
|
|
|
|
|
|
const mainPaddingBottom = parseFloat(getComputedStyle(main).paddingBottom);
|
|
|
|
|
|
if (mainPaddingBottom < playerRect.height) {
|
|
|
|
|
|
issues.push(
|
|
|
|
|
|
`Le main content a padding-bottom=${mainPaddingBottom}px mais le player fait ${Math.round(playerRect.height)}px. ` +
|
|
|
|
|
|
`Les derniers éléments du contenu seront cachés sous le player. ` +
|
|
|
|
|
|
`FIX: Ajouter pb-[${Math.ceil(playerRect.height + 16)}px] au conteneur principal.`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Vérifier qu'aucun bouton du contenu n'est sous le player
|
|
|
|
|
|
document.querySelectorAll('main button, main a, main input').forEach(el => {
|
|
|
|
|
|
const rect = el.getBoundingClientRect();
|
|
|
|
|
|
if (rect.width === 0 || rect.height === 0) return;
|
|
|
|
|
|
if (getComputedStyle(el).display === 'none') return;
|
|
|
|
|
|
|
|
|
|
|
|
const overlapY = Math.max(0, Math.min(rect.bottom, playerRect.bottom) - Math.max(rect.top, playerRect.top));
|
|
|
|
|
|
const overlapX = Math.max(0, Math.min(rect.right, playerRect.right) - Math.max(rect.left, playerRect.left));
|
|
|
|
|
|
|
|
|
|
|
|
if (overlapX > 0 && overlapY > 10) {
|
|
|
|
|
|
const text = el.textContent?.trim().slice(0, 30) || el.getAttribute('aria-label') || '';
|
|
|
|
|
|
issues.push(
|
|
|
|
|
|
`"${text}" (${el.tagName.toLowerCase()}) est recouvert par le player bar de ${Math.round(overlapY)}px. ` +
|
|
|
|
|
|
`Position: y=${Math.round(rect.top)} vs player.top=${Math.round(playerRect.top)}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return issues;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
for (const issue of playerIssues) {
|
|
|
|
|
|
console.log(`[PLAYER OVERLAP] ${issue}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expect(playerIssues.length,
|
|
|
|
|
|
`Le player bar recouvre du contenu:\n${playerIssues.join('\n')}`
|
|
|
|
|
|
).toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|