Add GPX file import to planner + archive change

Two import entry points:
- Home page: "Import GPX" button next to "Start Planning"
- In-session: drag-and-drop GPX onto the map (with confirmation)

Parses GPX client-side, extracts waypoints (Douglas-Peucker) and
no-go areas from extensions. Non-GPX files show error toast.

Also:
- Fix spec drift: non-GPX drop now shows error toast (was silent)
- Add E2E tests for import button, invalid GPX, and session creation
- Archive gpx-import-planner change, sync specs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-03 17:31:44 +01:00
parent 516b86aa3d
commit a9f8ee61f0
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
16 changed files with 394 additions and 175 deletions

View file

@ -190,4 +190,49 @@ test.describe("Planner", () => {
const response = await page.goto("/session/nonexistent-id");
expect(response?.status()).toBe(404);
});
test("home page has Import GPX button", async ({ page }) => {
await page.goto("/");
await expect(page.getByText("Import GPX")).toBeVisible();
});
test("import invalid GPX shows error", async ({ page }) => {
await page.goto("/");
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByText("Import GPX").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles({
name: "broken.gpx",
mimeType: "application/gpx+xml",
buffer: Buffer.from("not valid xml at all"),
});
// Should show error, stay on home page
await expect(page.getByText(/Could not read|konnte nicht/)).toBeVisible({ timeout: 5000 });
await expect(page).toHaveURL(/^\/$|\/$/);
});
test("import GPX from home page creates session with waypoints", async ({ page }) => {
await page.goto("/");
const gpx = `<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" xmlns="http://www.topografix.com/GPX/1/1">
<trk><trkseg>
<trkpt lat="52.52" lon="13.405"><ele>34</ele></trkpt>
<trkpt lat="51.05" lon="13.74"><ele>113</ele></trkpt>
<trkpt lat="48.137" lon="11.576"><ele>519</ele></trkpt>
</trkseg></trk>
</gpx>`;
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByText("Import GPX").click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles({
name: "test-route.gpx",
mimeType: "application/gpx+xml",
buffer: Buffer.from(gpx),
});
// Should redirect to a session
await expect(page).toHaveURL(/\/session\//, { timeout: 10000 });
await expect(page.getByText("Connected")).toBeVisible({ timeout: 15000 });
});
});