veza/tests/e2e/audit/pixel-perfect/15-images.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

110 lines
4.9 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 { TEST_USERS, ROUTES } from '../design-tokens';
test.describe('IMAGES — Ratio correct, fallback, pas de distorsion', () => {
// Distortion check on protected pages
for (const route of ROUTES.listener.slice(0, 10)) {
test(`[PROTECTED] ${route.name} — aucune image déformée`, async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, route.path);
const distorted = await page.evaluate(() => {
return Array.from(document.querySelectorAll('img[src]'))
.filter(img => {
if (!img.complete || img.naturalWidth === 0) return false;
if (getComputedStyle(img).objectFit === 'cover' || getComputedStyle(img).objectFit === 'contain') return false;
const displayRatio = img.clientWidth / img.clientHeight;
const naturalRatio = img.naturalWidth / img.naturalHeight;
const distortion = Math.abs(displayRatio - naturalRatio) / naturalRatio;
return distortion > 0.1;
})
.map(img => {
const src = img.src.split('/').pop() || img.src.slice(-40);
const testid = img.getAttribute('data-testid');
const selector = testid ? `[data-testid="${testid}"]` : `img[src*="${src.slice(0, 20)}"]`;
return {
src: src.slice(0, 40),
natural: `${img.naturalWidth}×${img.naturalHeight}`,
display: `${img.clientWidth}×${img.clientHeight}`,
fix: `ÉLÉMENT: ${selector} | PAGE: ${location.pathname} | MESURÉ: ratio naturel ${(img.naturalWidth / img.naturalHeight).toFixed(2)} vs affiché ${(img.clientWidth / img.clientHeight).toFixed(2)} | FIX TAILWIND: Ajouter object-cover ou object-contain sur ${selector}`,
};
});
});
for (const d of distorted) {
console.log(`[IMAGE DISTORTED] ${d.fix}`);
}
expect(distorted.length,
`Images déformées sur ${route.path}:\n${distorted.map(d => `${d.fix}`).join('\n')}`
).toBe(0);
});
test(`[PROTECTED] ${route.name} — toutes les images ont un alt ou aria-label`, async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
await navigateTo(page, route.path);
const missingAlt = await page.evaluate(() => {
return Array.from(document.querySelectorAll('img'))
.filter(img => {
if (getComputedStyle(img).display === 'none') return false;
const alt = img.getAttribute('alt');
const ariaLabel = img.getAttribute('aria-label');
const ariaHidden = img.getAttribute('aria-hidden');
const role = img.getAttribute('role');
const isDecorative = alt === '' || role === 'presentation' || ariaHidden === 'true';
return alt === null && !ariaLabel && !isDecorative;
})
.map(img => {
const src = img.src?.split('/').pop()?.slice(0, 30) || '';
return `ÉLÉMENT: img[src*="${src}"] | PAGE: ${location.pathname} | FIX TAILWIND: Ajouter alt="description" ou alt="" si décorative, ou aria-hidden="true"`;
})
.slice(0, 10);
});
for (const issue of missingAlt) {
console.log(`[IMAGE ALT] ${issue}`);
}
expect(missingAlt.length,
`Images sans alt sur ${route.path}:\n${missingAlt.join('\n')}`
).toBe(0);
});
}
// Public pages
for (const route of ROUTES.public) {
test(`[PUBLIC] ${route.name} — aucune image déformée ni sans alt`, async ({ page }) => {
await navigateTo(page, route.path);
const issues = await page.evaluate(() => {
const problems: string[] = [];
document.querySelectorAll('img').forEach(img => {
if (getComputedStyle(img).display === 'none') return;
// Check missing alt
const alt = img.getAttribute('alt');
const isDecorative = alt === '' || img.getAttribute('role') === 'presentation' || img.getAttribute('aria-hidden') === 'true';
if (alt === null && !img.getAttribute('aria-label') && !isDecorative) {
const src = img.src?.split('/').pop()?.slice(0, 25) || '';
problems.push(`img[src*="${src}"] sans alt. FIX: Ajouter alt="" si décorative`);
}
// Check broken
if (img.src && (!img.complete || img.naturalWidth === 0)) {
const src = img.src?.split('/').pop()?.slice(0, 25) || '';
problems.push(`img[src*="${src}"] cassée (ne charge pas). FIX: Vérifier le src ou ajouter un fallback`);
}
});
return problems.slice(0, 10);
});
expect(issues.length,
`Problèmes d'images sur ${route.path}:\n${issues.join('\n')}`
).toBe(0);
});
}
});