diff --git a/docs/architecture/README.md b/docs/architecture/README.md index e96a33b..564a2c6 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -11,6 +11,10 @@ Dieses Bündel ist die **Leitlinie für die große Refaktorierung** nach dem MVP | [UMSETZUNGSPLAN_ROADMAP.md](./UMSETZUNGSPLAN_ROADMAP.md) | Phasen, Meilensteine, Abnahmekriterien, Aufwandsschwerpunkte | | [VERBINDLICHE_REGELN_SHINKAN.md](./VERBINDLICHE_REGELN_SHINKAN.md) | **Verbindliche** Shinkan-spezifische Regeln (Ergänzung zu den globalen Rules) | +## Tests (E2E / Refaktor-Budget) + +- **`tests/dev-smoke-test.spec.js`** – Playwright-Suite (Smoke + Compliance). Enthält u. a. **Test 8:** nach Login und **Reload** des Dashboards werden GET-Aufrufe zu `/api/profiles/me` und `/api/training-units` gezählt (Absicherung Dashboard-Refaktor Phase 1). Ausführung: `npm run test:e2e`; CI: `.gitea/workflows/test.yml` Job **playwright-tests**. + ## Pflege - Bei abgeschlossenen Phasen: Roadmap und Remediation-Dokument aktualisieren; bei Regeländerungen: nur mit **expliziter Projektfreigabe** (gleiches Verfahren wie bei `.claude/rules/ARCHITECTURE.md`). diff --git a/tests/dev-smoke-test.spec.js b/tests/dev-smoke-test.spec.js index 4681ce2..6f3ec83 100644 --- a/tests/dev-smoke-test.spec.js +++ b/tests/dev-smoke-test.spec.js @@ -143,6 +143,51 @@ test('7. Session-Persistenz nach Reload', async ({ page }) => { console.log('✓ Session bleibt nach Reload erhalten'); }); +/** + * Refaktor Phase 1 (Dashboard): kein zweites GET /api/profiles/me; genau drei GET /api/training-units. + * Production-ähnlicher Build empfohlen (kein React StrictMode-Doppel-Mount im lokalen Vite-Dev). + */ +test('8. Dashboard API-Budget nach Reload (profiles/me, training-units)', async ({ page }) => { + await login(page); + + let profilesMe = 0; + let trainingUnits = 0; + + const onRequest = (request) => { + if (request.method() !== 'GET') return; + let pathname = ''; + try { + pathname = new URL(request.url()).pathname; + } catch { + return; + } + if (pathname === '/api/profiles/me') profilesMe += 1; + if (pathname === '/api/training-units') trainingUnits += 1; + }; + + page.on('request', onRequest); + + try { + await page.reload({ waitUntil: 'networkidle' }); + + const main = page.locator('.app-main'); + await expect(main.getByRole('heading', { level: 1, name: 'Dashboard' })).toBeVisible({ + timeout: 15000, + }); + await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 }); + await expect(page.getByRole('heading', { name: 'Nächste Termine' })).toBeVisible({ + timeout: 20000, + }); + + expect(profilesMe).toBe(1); + expect(trainingUnits).toBe(3); + } finally { + page.off('request', onRequest); + } + + console.log('✓ Dashboard API-Budget: 1× profiles/me, 3× training-units'); +}); + test('P-12: sessionStorage wird bei Logout bereinigt (sj_coach_* Schlüssel)', async ({ page }) => { await page.setViewportSize({ width: 1280, height: 800 }); await login(page); @@ -457,7 +502,7 @@ test('P-06e: API-Endpoint /api/admin/media-rights/legacy-summary erreichbar (Sup } }); -test('8. Keine kritischen Console-Fehler', async ({ page }) => { +test('9. Keine kritischen Console-Fehler', async ({ page }) => { const errors = []; page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text());