chore: clean up docker-compose files and enhance SQL migration for skills #7
|
|
@ -2,6 +2,18 @@
|
||||||
-- 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),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,10 @@ 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 });
|
||||||
|
|
||||||
// Prüfe ob Dashboard-Inhalt da ist
|
// Zwei verschiedene "Willkommen"-Texte im Dashboard → kein ambiguity locator('text=Willkommen')
|
||||||
await expect(page.locator('text=Willkommen')).toBeVisible({ timeout: 5000 });
|
await expect(
|
||||||
|
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');
|
||||||
|
|
@ -53,8 +55,11 @@ 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);
|
||||||
|
|
||||||
// Bottom-Nav: Übungen klicken
|
// Bei Viewport ≥1024px ist .bottom-nav versteckt — Mobile garantieren wie in playwright.config.js
|
||||||
await page.click('text=Übungen');
|
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');
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Prüfe ob Übungen-Seite geladen
|
// Prüfe ob Übungen-Seite geladen
|
||||||
|
|
@ -66,9 +71,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 });
|
||||||
|
|
||||||
// Bottom-Nav: Vereine klicken
|
await page.locator('.bottom-nav a[href="/clubs"]').click();
|
||||||
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
|
||||||
|
|
@ -109,16 +114,17 @@ 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 });
|
||||||
|
|
||||||
// Reload
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||||
await page.reload();
|
|
||||||
await page.waitForLoadState('networkidle');
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Sollte NICHT zur Login-Seite redirecten
|
// Auth lädt erst nach Spinner – nicht auf /login stranden (stabiler als Button „Login“-Tab auf Login-Screen)
|
||||||
const loginButton = page.locator('button:has-text("Login")');
|
await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 20000 });
|
||||||
await expect(loginButton).toHaveCount(0, { timeout: 5000 });
|
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' });
|
await page.screenshot({ path: 'screenshots/07-nach-reload.png' });
|
||||||
console.log('✓ Session bleibt nach Reload erhalten');
|
console.log('✓ Session bleibt nach Reload erhalten');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user