From 07b1cd8209cae1f5636608bb10f229f6bc356555 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 29 Apr 2026 13:41:17 +0200 Subject: [PATCH] chore: clean up docker-compose files and enhance SQL migration for skills - Removed version specification from docker-compose.dev-env.yml and docker-compose.yml for consistency. - Added a unique constraint to the skills table in the SQL migration to prevent duplicate entries, ensuring data integrity during imports. - Updated test cases in dev-smoke-test.spec.js to improve locator strategies and enhance viewport handling for mobile navigation. --- .../021_import_skills_from_matrix.sql | 12 ++++++++ docker-compose.dev-env.yml | 2 -- docker-compose.yml | 2 -- tests/dev-smoke-test.spec.js | 30 +++++++++++-------- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/backend/migrations/021_import_skills_from_matrix.sql b/backend/migrations/021_import_skills_from_matrix.sql index e528e15..5bbda10 100644 --- a/backend/migrations/021_import_skills_from_matrix.sql +++ b/backend/migrations/021_import_skills_from_matrix.sql @@ -2,6 +2,18 @@ -- One-time import of skills with categories from karatetrainer.net -- Source: https://karatetrainer.net/index.php?title=Fähigkeitsmatrix +-- ON CONFLICT (name) erfordert einen UNIQUE-Constraint auf skills.name (003/007 legten keinen an) +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint c + JOIN pg_class t ON c.conrelid = t.oid + WHERE t.relname = 'skills' AND c.contype = 'u' AND c.conname = 'skills_name_unique' + ) THEN + ALTER TABLE skills ADD CONSTRAINT skills_name_unique UNIQUE (name); + END IF; +END $$; + -- Create skill categories first INSERT INTO skill_categories (name, description, sort_order) VALUES ('Kihon', 'Grundtechniken', 1), diff --git a/docker-compose.dev-env.yml b/docker-compose.dev-env.yml index 1b5cc8f..231da53 100644 --- a/docker-compose.dev-env.yml +++ b/docker-compose.dev-env.yml @@ -1,5 +1,3 @@ -version: '3.8' - # Keine festen container_name — Compose-Namen haben Projektprefix (-postgres-1). # Gleiche Variablennamen wie docker-compose.yml; andere Werte in einer eigenen .env neben dieser Datei. diff --git a/docker-compose.yml b/docker-compose.yml index 89fbd47..1f4dc53 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: postgres: image: postgres:16-alpine diff --git a/tests/dev-smoke-test.spec.js b/tests/dev-smoke-test.spec.js index cc00a75..c7c12e9 100644 --- a/tests/dev-smoke-test.spec.js +++ b/tests/dev-smoke-test.spec.js @@ -43,8 +43,10 @@ test('2. Dashboard lädt ohne Fehler', async ({ page }) => { // Warte bis Spinner verschwunden await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 }); - // Prüfe ob Dashboard-Inhalt da ist - await expect(page.locator('text=Willkommen')).toBeVisible({ timeout: 5000 }); + // Zwei verschiedene "Willkommen"-Texte im Dashboard → kein ambiguity locator('text=Willkommen') + await expect( + page.getByRole('heading', { name: /Willkommen bei Shinkan/i }), + ).toBeVisible({ timeout: 5000 }); await page.screenshot({ path: 'screenshots/02-dashboard.png' }); console.log('✓ Dashboard OK'); @@ -53,8 +55,11 @@ test('2. Dashboard lädt ohne Fehler', async ({ page }) => { test('3. Navigation zu Übungen', async ({ page }) => { await login(page); - // Bottom-Nav: Übungen klicken - await page.click('text=Übungen'); + // Bei Viewport ≥1024px ist .bottom-nav versteckt — Mobile garantieren wie in playwright.config.js + await page.setViewportSize({ width: 390, height: 844 }); + + // Desktop-Sidebar enthält ebenfalls Übungen – nur Mobile-Bottom-Nav klicken (sichtbarer Link) + await page.locator('.bottom-nav a[href="/exercises"]').click(); await page.waitForLoadState('networkidle'); // Prüfe ob Übungen-Seite geladen @@ -66,9 +71,9 @@ test('3. Navigation zu Übungen', async ({ page }) => { test('4. Navigation zu Vereine', async ({ page }) => { await login(page); + await page.setViewportSize({ width: 390, height: 844 }); - // Bottom-Nav: Vereine klicken - await page.click('text=Vereine'); + await page.locator('.bottom-nav a[href="/clubs"]').click(); await page.waitForLoadState('networkidle'); // Prüfe ob Vereine-Seite geladen @@ -109,16 +114,17 @@ test('6. Bottom-Nav sichtbar (Mobile)', async ({ page }) => { test('7. Session-Persistenz nach Reload', async ({ page }) => { await login(page); - // Warte auf Dashboard await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 }); - // Reload - await page.reload(); + await page.reload({ waitUntil: 'domcontentloaded' }); await page.waitForLoadState('networkidle'); - // Sollte NICHT zur Login-Seite redirecten - const loginButton = page.locator('button:has-text("Login")'); - await expect(loginButton).toHaveCount(0, { timeout: 5000 }); + // Auth lädt erst nach Spinner – nicht auf /login stranden (stabiler als Button „Login“-Tab auf Login-Screen) + await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 20000 }); + await expect(page).not.toHaveURL('**/login', { timeout: 20000 }); + await expect(page.locator('h1').filter({ hasText: /^Dashboard$/ })).toBeVisible({ + timeout: 10000, + }); await page.screenshot({ path: 'screenshots/07-nach-reload.png' }); console.log('✓ Session bleibt nach Reload erhalten');