- 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>
123 lines
5 KiB
TypeScript
123 lines
5 KiB
TypeScript
import { test, expect } from '@chromatic-com/playwright';
|
|
import { loginViaAPI, navigateTo } from '../helpers';
|
|
import { getFormFields } from '../helpers/interaction-helpers';
|
|
import { TEST_USERS } from '../design-tokens';
|
|
|
|
test.describe('FORMULAIRES & VALIDATION — Chaque formulaire valide/invalide/vide', () => {
|
|
test('Login — soumission vide affiche des erreurs', async ({ page }) => {
|
|
await navigateTo(page, '/login');
|
|
|
|
const submit = page.getByTestId('login-submit');
|
|
await submit.waitFor({ state: 'visible', timeout: 10_000 });
|
|
|
|
// Vider les champs (au cas où ils sont pré-remplis)
|
|
const email = page.locator('input[type="email"]');
|
|
await email.waitFor({ state: 'visible' });
|
|
await email.clear();
|
|
const password = page.locator('input[type="password"]');
|
|
await password.clear();
|
|
|
|
// Soumettre vide
|
|
await submit.click();
|
|
await page.waitForTimeout(2_000);
|
|
|
|
// Doit rester sur /login
|
|
expect(page.url()).toContain('/login');
|
|
|
|
// Devrait afficher des messages d'erreur (validation HTML5 ou custom)
|
|
const body = await page.textContent('body') || '';
|
|
const hasValidation = body.match(/required|requis|obligatoire|invalide|invalid|veuillez|please/i) ||
|
|
(await page.locator('[class*="error"], [class*="destructive"], [role="alert"]').count()) > 0;
|
|
|
|
console.log(`[FORM] Login — validation errors visible: ${!!hasValidation}`);
|
|
});
|
|
|
|
test('Register — champs requis sont validés', async ({ page }) => {
|
|
await navigateTo(page, '/register');
|
|
|
|
const form = page.getByTestId('register-form').or(page.locator('form')).first();
|
|
await form.waitFor({ state: 'visible', timeout: 10_000 });
|
|
|
|
// Vérifier les champs
|
|
const fields = await getFormFields(page, form.first() ? 'form' : '[data-testid="register-form"]');
|
|
|
|
console.log(`[FORM] Register — ${fields.length} champs trouvés:`);
|
|
for (const field of fields) {
|
|
console.log(` ${field.name} (${field.type}) — required: ${field.required}, label: "${field.label}"`);
|
|
}
|
|
|
|
// Au minimum : email, password, username
|
|
expect(fields.length, 'Le formulaire d\'inscription devrait avoir au moins 3 champs').toBeGreaterThanOrEqual(3);
|
|
|
|
// Tester email invalide
|
|
const emailInput = page.locator('input[type="email"]');
|
|
if (await emailInput.isVisible().catch(() => false)) {
|
|
await emailInput.fill('not-an-email');
|
|
await page.locator('input[type="password"]').first().fill('a');
|
|
const submitBtn = page.locator('button[type="submit"]').first();
|
|
if (await submitBtn.isVisible().catch(() => false)) {
|
|
await submitBtn.click();
|
|
await page.waitForTimeout(2_000);
|
|
// Devrait montrer une erreur
|
|
expect(page.url()).toContain('/register');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Settings — les formulaires de profil sauvegardent correctement', async ({ page }) => {
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
await navigateTo(page, '/settings');
|
|
|
|
// Vérifier que les champs de profil sont pré-remplis
|
|
const inputs = await page.locator('input:visible').all();
|
|
|
|
let prefilledCount = 0;
|
|
for (const input of inputs.slice(0, 10)) {
|
|
const value = await input.inputValue().catch(() => '');
|
|
if (value.length > 0) prefilledCount++;
|
|
}
|
|
|
|
console.log(`[FORM] Settings — ${prefilledCount}/${inputs.length} champs pré-remplis`);
|
|
});
|
|
|
|
test('Forms — pas de double soumission (bouton disabled après clic)', async ({ page }) => {
|
|
await navigateTo(page, '/login');
|
|
|
|
const email = page.locator('input[type="email"]');
|
|
await email.waitFor({ state: 'visible', timeout: 10_000 });
|
|
await email.fill(TEST_USERS.listener.email);
|
|
await page.locator('input[type="password"]').fill(TEST_USERS.listener.password);
|
|
|
|
const submit = page.getByTestId('login-submit');
|
|
await submit.click();
|
|
|
|
// Après le premier clic, vérifier si le bouton est désactivé ou en état loading
|
|
await page.waitForTimeout(500);
|
|
const isDisabledOrLoading = await submit.evaluate(el => {
|
|
return (el as HTMLButtonElement).disabled ||
|
|
el.getAttribute('aria-busy') === 'true' ||
|
|
el.classList.contains('loading') ||
|
|
el.textContent?.includes('...') || false;
|
|
}).catch(() => false);
|
|
|
|
console.log(`[FORM] Submit button disabled/loading after click: ${isDisabledOrLoading}`);
|
|
});
|
|
|
|
test('Forgot password — le formulaire accepte un email et affiche confirmation', async ({ page }) => {
|
|
await navigateTo(page, '/forgot-password');
|
|
|
|
const emailInput = page.locator('input[type="email"]');
|
|
await emailInput.waitFor({ state: 'visible', timeout: 10_000 });
|
|
await emailInput.fill('test@example.com');
|
|
|
|
const submit = page.locator('button[type="submit"]').first();
|
|
if (await submit.isVisible().catch(() => false)) {
|
|
await submit.click();
|
|
await page.waitForTimeout(3_000);
|
|
|
|
// La page ne devrait pas crasher
|
|
const body = await page.textContent('body') || '';
|
|
expect(body).not.toMatch(/500|Internal Server Error/i);
|
|
}
|
|
});
|
|
});
|