Implements the journal-elevation-profile change (specs/journal-elevation-profile):
- gpx: `elevationSeries(tracks)` → { d, e, lat, lng }[] with cumulative distance,
downsampled (keeps first/last), empty when <2 points carry elevation.
- ElevationProfile: read-only SVG area chart (vertical gradient fill), highest/
lowest summary + a hover readout. Reports hovered index (onActive) and clicked
index (onSeek); draws a marker at the active index. Renders nothing for an
empty series.
- RouteMapThumbnail: ActiveMarker (CircleMarker at the chart's active point),
HoverTracker (route hover → nearest sample → onHoverIndex), Recenter (panTo on
chart click). Props forwarded through ClientMap.
- Wired into the activity + route detail pages via a shared activeIndex/centerOn
state; loaders expose the series (activity reuses the moving-time parse).
- i18n journal.elevation.{highest,lowest} in en + de.
Ascent/descent stay in the stat row (#532); the chart summary shows highest/
lowest to avoid duplication.
Tests: elevationSeries unit; ElevationProfile component (jsdom); e2e creates an
activity from an elevation GPX and asserts the chart renders. typecheck + lint +
unit (gpx 67, journal 315) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
26 lines
1.2 KiB
TypeScript
26 lines
1.2 KiB
TypeScript
import { test, expect, gotoHydrated } from "./fixtures/test";
|
|
import { setupVirtualAuthenticator, registerUser } from "./helpers/auth";
|
|
|
|
// A GPX fixture that carries trackpoint elevation (10 points).
|
|
const ELEVATION_GPX = "packages/fit/__fixtures__/alpine.gpx";
|
|
|
|
test.describe("Elevation profile", () => {
|
|
test("an activity with elevation shows the profile chart on its detail page", async ({ page }) => {
|
|
const cdp = await page.context().newCDPSession(page);
|
|
await setupVirtualAuthenticator(cdp);
|
|
|
|
const stamp = Date.now();
|
|
await registerUser(page, `elev-${stamp}@example.com`, `elev${stamp}`);
|
|
|
|
await gotoHydrated(page, "/activities/new");
|
|
await page.getByLabel("Name").fill("Alpine outing");
|
|
await page.locator('input[name="gpx"]').setInputFiles(ELEVATION_GPX);
|
|
await page.getByRole("button", { name: "Create Activity" }).click();
|
|
|
|
await expect(page).toHaveURL(/\/activities\/[0-9a-f-]+$/, { timeout: 10000 });
|
|
|
|
// The elevation profile chart (svg role=img) and its summary render.
|
|
await expect(page.getByRole("img", { name: "Elevation profile" })).toBeVisible();
|
|
await expect(page.getByText("Highest")).toBeVisible();
|
|
});
|
|
});
|