feat: v0.4.0 — self-host OFM styles/fonts/sprites (tiles stay remote) + top-bar style switcher defaulting to Fiord

This commit is contained in:
2026-09-17 08:24:58 +00:00
parent b011953087
commit 501c452377
789 changed files with 5513 additions and 42 deletions
+77 -7
View File
@@ -72,13 +72,11 @@ test('clicking an airport marker opens a popup with name and ICAO', async ({ pag
});
test('wheel zoom is clamped between min and max zoom', async ({ page }) => {
// Zoom clamping is pure camera behavior, so block basemap tile fetches (the
// style JSON still loads): it keeps the headless main thread idle and makes the
// test fast and independent of tile-server latency.
await page.route('**://*.openfreemap.org/**', (route) => {
if (route.request().url().includes('/styles/')) return route.continue();
return route.abort();
});
// Zoom clamping is pure camera behavior, so block basemap tile fetches: it keeps
// the headless main thread idle and makes the test fast and independent of
// tile-server latency. Styles/fonts/sprites are self-hosted now — anything from
// openfreemap.org is a tile request, so abort all of them.
await page.route('**://*.openfreemap.org/**', (route) => route.abort());
await page.goto(APP_URL);
const getZoom = () => page.evaluate(() => (window as any).__flightMap?.getZoom() ?? -1);
@@ -144,3 +142,75 @@ test('wheel zoom is clamped between min and max zoom', async ({ page }) => {
expect(zoom).toBeGreaterThanOrEqual(MIN_ZOOM);
expect(zoom).toBeLessThanOrEqual(MAX_ZOOM);
});
test('style switcher defaults to Fiord, switches styles, and keeps the airports layer', async ({ page }) => {
// Styles/fonts/sprites are self-hosted; only tiles come from openfreemap — abort
// them for speed (the GeoJSON airport layer renders without basemap tiles).
await page.route('**://*.openfreemap.org/**', (route) => route.abort());
await page.goto(APP_URL);
const select = page.getByTestId('style-switch');
await expect(select).toBeVisible();
await expect(select.locator('option')).toHaveCount(5);
// Fiord is the default style.
await expect(select).toHaveValue('fiord');
await waitForAirportCount(page);
const renderedCircles = () =>
page.evaluate(
() =>
(window as any).__flightMap?.queryRenderedFeatures(undefined, {
layers: ['airports-circles'],
})?.length ?? 0,
);
await expect.poll(renderedCircles, { timeout: 15_000 }).toBeGreaterThan(0);
const layerIds = () =>
page.evaluate(() => (window as any).__flightMap?.getStyle()?.layers?.map((l: { id: string }) => l.id) ?? []);
const before = await layerIds();
expect(before.length).toBeGreaterThan(0);
// Switch to Dark.
await select.selectOption('dark');
await expect(select).toHaveValue('dark');
// The applied style must actually change (each OFM style has a different layer set).
await expect.poll(layerIds, { timeout: 15_000 }).not.toEqual(before);
// Regression guard for the setStyle-wipes-layers bug: map.setStyle() removes every
// source/layer not in the new style, so the airports layer must be re-established —
// count still shown and markers rendered again.
await expect(page.getByTestId('airport-count')).toContainText(/EU airports/);
await expect.poll(renderedCircles, { timeout: 15_000 }).toBeGreaterThan(0);
// Clicking a marker after the switch opens exactly one popup (the click handler is
// re-wired idempotently — no duplicate listeners).
const pt = await page.evaluate(() => {
const map = (window as any).__flightMap;
const p = map.project([30.894699, 50.345001]); // UKBB Boryspil
const rect = map.getContainer().getBoundingClientRect();
return { x: rect.left + p.x, y: rect.top + p.y };
});
await page.mouse.click(pt.x, pt.y);
const popups = page.getByTestId('airport-popup');
await expect(popups.first()).toBeVisible();
await expect(popups).toHaveCount(1);
});
test('style choice persists across reloads via localStorage', async ({ page }) => {
await page.route('**://*.openfreemap.org/**', (route) => route.abort());
await page.goto(APP_URL);
const select = page.getByTestId('style-switch');
await expect(select).toHaveValue('fiord');
await select.selectOption('positron');
const stored = await page.evaluate(() => localStorage.getItem('flight-ops-style'));
expect(stored).toBe('positron');
// Reload: the persisted choice is restored after mount (SSR-safe path — server and
// first paint use the default, then the effect applies the saved style).
await page.reload();
await expect(select).toHaveValue('positron');
});