Files
intotheeast-com/tests/ui/gpx/gpx-manager.spec.js
T
m038 2c8d676e25 test: add GPX Manager end-to-end spec (GM1-GM7)
Also fix auth.setup.js AUTH_FILE path: the file lives in tests/ui/auth/
so the relative path to tests/.auth/user.json needs ../../ not ../ to
match the storageState path in playwright.config.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPJztrVGbwic2xTG7G9fjM
2026-06-21 16:35:48 +02:00

143 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @ts-check
// Tests: GM1GM7 — GPX Manager end-to-end (real API calls)
// Requires: Grav server at localhost:8081, demo-load completed, user logged in.
const { test, expect } = require('@playwright/test');
const fs = require('fs');
const path = require('path');
const BASE_URL = process.env.GRAV_BASE_URL || 'http://localhost:8081';
const API = `${BASE_URL}/api/v1`;
const TRIP_ROUTE = '/trips/italy-2026-demo';
const AUTH_FILE = path.join(__dirname, '../../.auth/user.json');
const GPX_FIXTURE = path.join(__dirname, '../../fixtures/test-route.gpx');
const GPX_FIXTURE_CONTENT = fs.readFileSync(GPX_FIXTURE);
// Track uploaded filenames for cleanup
const uploaded = [];
async function apiDelete(filename) {
const authState = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf-8'));
const cookie = authState.cookies
.map(c => `${c.name}=${c.value}`)
.join('; ');
await fetch(
`${API}/pages${TRIP_ROUTE}/media/${encodeURIComponent(filename)}`,
{ method: 'DELETE', headers: { Cookie: cookie } }
);
}
test.afterAll(async () => {
for (const name of uploaded) {
try { await apiDelete(name); } catch (_) { /* best-effort */ }
}
});
// ── GM1: Page loads with auth ─────────────────────────────────────────────────
test('GM1: /gpx-manager loads with auth and shows one section per trip', async ({ page }) => {
await page.goto('/gpx-manager');
const sections = page.locator('.gpx-trip');
await expect(sections.first()).toBeVisible({ timeout: 8000 });
const count = await sections.count();
expect(count, 'At least one trip section').toBeGreaterThan(0);
});
// ── GM2: Page without auth shows login form ───────────────────────────────────
test('GM2: /gpx-manager without auth renders inline login form', async ({ browser }) => {
const ctx = await browser.newContext({ storageState: { cookies: [], origins: [] } });
const page = await ctx.newPage();
await page.goto(`${BASE_URL}/gpx-manager`);
await expect(page.locator('#grav-login')).toBeVisible({ timeout: 8000 });
await ctx.close();
});
// ── GM3: File list settles (loading placeholder gone) ────────────────────────
test('GM3: file list resolves — loading placeholder is gone after API call', async ({ page }) => {
await page.goto('/gpx-manager');
const italySection = page.locator('.gpx-trip[data-route="/trips/italy-2026-demo"]');
await expect(italySection).toBeVisible({ timeout: 8000 });
// Wait for loading placeholder to disappear
await expect(italySection.locator('.gpx-loading')).toHaveCount(0, { timeout: 15000 });
});
// ── GM4: Upload test-route.gpx → appears in file list ────────────────────────
test('GM4: uploading test-route.gpx shows it in the file list', async ({ page }) => {
await page.goto('/gpx-manager');
const italySection = page.locator('.gpx-trip[data-route="/trips/italy-2026-demo"]');
await expect(italySection).toBeVisible({ timeout: 8000 });
await expect(italySection.locator('.gpx-loading')).toHaveCount(0, { timeout: 15000 });
const form = italySection.locator('.gpx-upload-form');
await form.locator('input[type=file]').setInputFiles({
name: 'test-route.gpx',
mimeType: 'application/gpx+xml',
buffer: GPX_FIXTURE_CONTENT,
});
await form.locator('.gpx-upload-btn').click();
// Wait for status to show "Uploaded!" and file list to refresh
await expect(form.locator('.gpx-status')).toContainText('Uploaded!', { timeout: 15000 });
await expect(italySection.locator('.gpx-table td', { hasText: 'test-route.gpx' })).toBeVisible({ timeout: 10000 });
uploaded.push('test-route.gpx');
});
// ── GM5: Filename with spaces/caps gets slugified ─────────────────────────────
test('GM5: filename with spaces and capitals is slugified before upload', async ({ page }) => {
await page.goto('/gpx-manager');
const italySection = page.locator('.gpx-trip[data-route="/trips/italy-2026-demo"]');
await expect(italySection).toBeVisible({ timeout: 8000 });
await expect(italySection.locator('.gpx-loading')).toHaveCount(0, { timeout: 15000 });
const form = italySection.locator('.gpx-upload-form');
await form.locator('input[type=file]').setInputFiles({
name: 'My Route 1.gpx',
mimeType: 'application/gpx+xml',
buffer: GPX_FIXTURE_CONTENT,
});
await form.locator('.gpx-upload-btn').click();
await expect(form.locator('.gpx-status')).toContainText('Uploaded!', { timeout: 15000 });
// The client-side slugify turns "My Route 1.gpx" → "my-route-1.gpx"
await expect(italySection.locator('.gpx-table td', { hasText: 'my-route-1.gpx' })).toBeVisible({ timeout: 10000 });
uploaded.push('my-route-1.gpx');
});
// ── GM6: Submit without file shows error message ──────────────────────────────
test('GM6: submitting upload form without a file shows "Choose a file first."', async ({ page }) => {
await page.goto('/gpx-manager');
const italySection = page.locator('.gpx-trip[data-route="/trips/italy-2026-demo"]');
await expect(italySection).toBeVisible({ timeout: 8000 });
const form = italySection.locator('.gpx-upload-form');
await form.locator('.gpx-upload-btn').click();
await expect(form.locator('.gpx-status')).toHaveText('Choose a file first.');
});
// ── GM7: Delete uploaded file removes it from list ───────────────────────────
test('GM7: deleting an uploaded file removes its row from the file list', async ({ page }) => {
// Upload a file first so we have something to delete
await page.goto('/gpx-manager');
const italySection = page.locator('.gpx-trip[data-route="/trips/italy-2026-demo"]');
await expect(italySection).toBeVisible({ timeout: 8000 });
await expect(italySection.locator('.gpx-loading')).toHaveCount(0, { timeout: 15000 });
const form = italySection.locator('.gpx-upload-form');
await form.locator('input[type=file]').setInputFiles({
name: 'to-delete.gpx',
mimeType: 'application/gpx+xml',
buffer: GPX_FIXTURE_CONTENT,
});
await form.locator('.gpx-upload-btn').click();
await expect(form.locator('.gpx-status')).toContainText('Uploaded!', { timeout: 15000 });
// Click delete for the uploaded file
page.once('dialog', dialog => dialog.accept());
await italySection.locator('.gpx-delete[data-filename="to-delete.gpx"]').click();
// Row must disappear
await expect(italySection.locator('.gpx-table td', { hasText: 'to-delete.gpx' }))
.toHaveCount(0, { timeout: 10000 });
// No cleanup needed — the test deleted it itself
});