Compare commits

..

No commits in common. "390b0ecb73cb9993b148499e4de6903979c20720" and "7e7adfab54c87e28ebfdf78c1e35e27b1cb62a28" have entirely different histories.

4 changed files with 16 additions and 30 deletions

View File

@ -2,18 +2,6 @@
-- One-time import of skills with categories from karatetrainer.net -- One-time import of skills with categories from karatetrainer.net
-- Source: https://karatetrainer.net/index.php?title=Fähigkeitsmatrix -- 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 -- Create skill categories first
INSERT INTO skill_categories (name, description, sort_order) VALUES INSERT INTO skill_categories (name, description, sort_order) VALUES
('Kihon', 'Grundtechniken', 1), ('Kihon', 'Grundtechniken', 1),

View File

@ -1,3 +1,5 @@
version: '3.8'
# Keine festen container_name — Compose-Namen haben Projektprefix (<projekt>-postgres-1). # Keine festen container_name — Compose-Namen haben Projektprefix (<projekt>-postgres-1).
# Gleiche Variablennamen wie docker-compose.yml; andere Werte in einer eigenen .env neben dieser Datei. # Gleiche Variablennamen wie docker-compose.yml; andere Werte in einer eigenen .env neben dieser Datei.

View File

@ -1,3 +1,5 @@
version: '3.8'
services: services:
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine

View File

@ -43,10 +43,8 @@ test('2. Dashboard lädt ohne Fehler', async ({ page }) => {
// Warte bis Spinner verschwunden // Warte bis Spinner verschwunden
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 }); await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
// Zwei verschiedene "Willkommen"-Texte im Dashboard → kein ambiguity locator('text=Willkommen') // Prüfe ob Dashboard-Inhalt da ist
await expect( await expect(page.locator('text=Willkommen')).toBeVisible({ timeout: 5000 });
page.getByRole('heading', { name: /Willkommen bei Shinkan/i }),
).toBeVisible({ timeout: 5000 });
await page.screenshot({ path: 'screenshots/02-dashboard.png' }); await page.screenshot({ path: 'screenshots/02-dashboard.png' });
console.log('✓ Dashboard OK'); console.log('✓ Dashboard OK');
@ -55,11 +53,8 @@ test('2. Dashboard lädt ohne Fehler', async ({ page }) => {
test('3. Navigation zu Übungen', async ({ page }) => { test('3. Navigation zu Übungen', async ({ page }) => {
await login(page); await login(page);
// Bei Viewport ≥1024px ist .bottom-nav versteckt — Mobile garantieren wie in playwright.config.js // Bottom-Nav: Übungen klicken
await page.setViewportSize({ width: 390, height: 844 }); await page.click('text=Übungen');
// 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'); await page.waitForLoadState('networkidle');
// Prüfe ob Übungen-Seite geladen // Prüfe ob Übungen-Seite geladen
@ -71,9 +66,9 @@ test('3. Navigation zu Übungen', async ({ page }) => {
test('4. Navigation zu Vereine', async ({ page }) => { test('4. Navigation zu Vereine', async ({ page }) => {
await login(page); await login(page);
await page.setViewportSize({ width: 390, height: 844 });
await page.locator('.bottom-nav a[href="/clubs"]').click(); // Bottom-Nav: Vereine klicken
await page.click('text=Vereine');
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
// Prüfe ob Vereine-Seite geladen // Prüfe ob Vereine-Seite geladen
@ -114,17 +109,16 @@ test('6. Bottom-Nav sichtbar (Mobile)', async ({ page }) => {
test('7. Session-Persistenz nach Reload', async ({ page }) => { test('7. Session-Persistenz nach Reload', async ({ page }) => {
await login(page); await login(page);
// Warte auf Dashboard
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 }); await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
await page.reload({ waitUntil: 'domcontentloaded' }); // Reload
await page.reload();
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
// Auth lädt erst nach Spinner nicht auf /login stranden (stabiler als Button „Login“-Tab auf Login-Screen) // Sollte NICHT zur Login-Seite redirecten
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 20000 }); const loginButton = page.locator('button:has-text("Login")');
await expect(page).not.toHaveURL('**/login', { timeout: 20000 }); await expect(loginButton).toHaveCount(0, { timeout: 5000 });
await expect(page.locator('h1').filter({ hasText: /^Dashboard$/ })).toBeVisible({
timeout: 10000,
});
await page.screenshot({ path: 'screenshots/07-nach-reload.png' }); await page.screenshot({ path: 'screenshots/07-nach-reload.png' });
console.log('✓ Session bleibt nach Reload erhalten'); console.log('✓ Session bleibt nach Reload erhalten');