Add E2E tests for waypoint notes and nearby POI snap; archive waypoint-notes

- E2E: type a note, blur, verify it persists and appears in GPX <desc> on <wpt>
- E2E: mock Overpass, select waypoint, verify Nearby list, snap to POI,
  verify note prefix prepended
- Sync waypoint-notes delta spec → openspec/specs/waypoint-notes/spec.md
- Archive waypoint-notes change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-05-18 00:06:07 +02:00
parent 763aa475ea
commit 41217ef658
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
7 changed files with 149 additions and 2 deletions

View file

@ -469,4 +469,105 @@ test.describe("Planner", () => {
const panelText = await page.getByText("Drinking water").textContent();
expect(panelText).toBeTruthy();
});
test("waypoint note persists and appears in GPX export", async ({ page, request }) => {
const sessionResp = await request.post("/api/sessions", { data: {} });
const { url } = await sessionResp.json();
await mockBRouter(page);
await page.goto(`${url}?waypoints=${encodeURIComponent(JSON.stringify([
{ lat: 52.520, lon: 13.405, name: "Berlin" },
{ lat: 52.515, lon: 13.351, name: "Spandau" },
]))}`);
await expect(page.locator(".leaflet-container")).toBeVisible({ timeout: 10000 });
await expect(page.getByText("Connected")).toBeVisible({ timeout: 15000 });
await expect(page.getByText("Waypoints (2)")).toBeVisible({ timeout: 5000 });
const sidebar = page.locator("aside");
// Click on the note placeholder for the first waypoint
const firstRow = sidebar.locator("li").filter({ has: page.locator("span.rounded-full").first() }).first();
await firstRow.locator("p.italic").click();
// Type a note in the textarea
const textarea = firstRow.locator("textarea");
await expect(textarea).toBeVisible({ timeout: 3000 });
await textarea.fill("Refill water here");
// Blur to save
await textarea.blur();
// Note should now be displayed (no longer placeholder)
await expect(firstRow.getByText("Refill water here")).toBeVisible({ timeout: 3000 });
// Export GPX and verify <desc> appears inside <wpt>
const [download] = await Promise.all([
page.waitForEvent("download"),
page.getByRole("button", { name: "Export GPX" }).click(),
]);
const gpxStream = await download.createReadStream();
const chunks: Buffer[] = [];
for await (const chunk of gpxStream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
const gpxText = Buffer.concat(chunks).toString("utf-8");
// The <desc> should be inside the first <wpt> block
const wptBlock = gpxText.match(/<wpt[^>]*lat="52\.52[^"]*"[^>]*>[\s\S]*?<\/wpt>/)?.[0] ?? "";
expect(wptBlock).toContain("<desc>Refill water here</desc>");
});
test("nearby POI snap moves waypoint and prepends note prefix", async ({ page, request }) => {
const sessionResp = await request.post("/api/sessions", { data: {} });
const { url } = await sessionResp.json();
await mockBRouter(page);
// Mock the Overpass proxy to return a nearby POI
await page.route("**/api/overpass", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
elements: [
{
type: "node",
id: 99001,
lat: 52.521,
lon: 13.406,
tags: { amenity: "drinking_water", name: "Stadtbrunnen" },
},
],
}),
});
});
await page.goto(`${url}?waypoints=${encodeURIComponent(JSON.stringify([
{ lat: 52.520, lon: 13.405, name: "Start" },
{ lat: 52.515, lon: 13.351, name: "End" },
]))}`);
await expect(page.locator(".leaflet-container")).toBeVisible({ timeout: 10000 });
await expect(page.getByText("Connected")).toBeVisible({ timeout: 15000 });
await expect(page.getByText("Waypoints (2)")).toBeVisible({ timeout: 5000 });
const sidebar = page.locator("aside");
// Click the first waypoint row to select it (triggers nearby POI fetch)
const firstRow = sidebar.locator("li").filter({ has: page.locator("span.rounded-full").first() }).first();
await firstRow.click();
// Wait for the Nearby section to appear with the mock POI
await expect(sidebar.getByText("Nearby")).toBeVisible({ timeout: 5000 });
await expect(sidebar.getByText("Stadtbrunnen")).toBeVisible({ timeout: 5000 });
// Click the snap button for that POI
const snapButton = sidebar.locator("li").filter({ hasText: "Stadtbrunnen" }).getByRole("button", { name: /snap/i });
await snapButton.click();
// The waypoint note should now contain the POI prefix (water emoji + name)
await expect(firstRow.locator("p.italic")).not.toHaveText(/Add a note/);
const noteText = await firstRow.locator("p.italic").textContent();
expect(noteText).toContain("Stadtbrunnen");
});
});

View file

@ -29,7 +29,7 @@
## 6. Testing (Notes)
- [x] 6.1 Unit tests: per-waypoint `<desc>` in GPX generation; `<desc>` inside `<wpt>` parsed into `note`; verify no collision with `metadata > desc`
- [ ] 6.2 E2E test: type a note in sidebar, blur, verify note persists; verify GPX export contains `<desc>` inside `<wpt>`
- [x] 6.2 E2E test: type a note in sidebar, blur, verify note persists; verify GPX export contains `<desc>` inside `<wpt>`
## 7. POI Data Layer (per-waypoint)
@ -62,4 +62,4 @@
- [x] 11.2 POI cache unit tests — `poi-cache.test.ts` already covers this
- [x] 11.3 Unit tests for `fetchNearbyPois`: correct bbox from lat/lon/radius, category filtering
- [x] 11.4 Unit tests for snap: coords updated, name set, note prefix prepended, existing note preserved, all in one Yjs transaction
- [ ] 11.5 E2E test: mock Overpass via route handler, select a waypoint, verify Nearby list appears, click snap, verify waypoint moved and note prefixed
- [x] 11.5 E2E test: mock Overpass via route handler, select a waypoint, verify Nearby list appears, click snap, verify waypoint moved and note prefixed

View file

@ -0,0 +1,46 @@
## ADDED Requirements
### Requirement: Per-waypoint text notes
Each waypoint SHALL support an optional plain-text note synced via Yjs.
#### Scenario: Add note to waypoint
- **WHEN** a user clicks the note area under a waypoint in the sidebar and types text
- **THEN** the note is stored in the waypoint's Y.Map as a `note` string field
- **AND** auto-saves on blur
#### Scenario: Note syncs to participants
- **WHEN** a user adds or edits a waypoint note
- **THEN** all other participants see the update in real-time via Yjs
### Requirement: Map note indicators
Waypoint markers with notes SHALL show a visual indicator on the map.
#### Scenario: Note icon on marker
- **WHEN** a waypoint has a note
- **THEN** its map marker shows a small note icon
#### Scenario: Note tooltip
- **WHEN** a user hovers or taps a marker with a note
- **THEN** the note text appears in a tooltip
### Requirement: Notes in GPX export
Waypoint notes SHALL be exported as `<desc>` elements in GPX output.
#### Scenario: Export notes
- **WHEN** a user exports a plan with waypoint notes
- **THEN** each waypoint's note appears as a `<desc>` element in the GPX file
### Requirement: Nearby POI display
When a waypoint is selected, nearby POIs from OpenStreetMap SHALL be shown on the map and in the sidebar.
#### Scenario: POI lookup
- **WHEN** a user selects a waypoint
- **THEN** nearby POIs are fetched from the Overpass API and displayed as small markers on the map and as a list in the sidebar
### Requirement: Snap waypoint to POI
Users SHALL be able to move a waypoint to a nearby POI's exact coordinates.
#### Scenario: Snap to POI
- **WHEN** a user clicks a nearby POI
- **THEN** the waypoint moves to the POI's coordinates
- **AND** the POI's name and type are added as a note prefix (e.g., "Campsite - Waldcamp Fichtelberg")