Files
star-map/e2e/search-navigation.spec.ts
T
SenrokaiandClaude Fable 5 4a1cc5240a Dock the HUD: every tool and readout on one rail along the bottom
The overlay had grown by accretion: a search box floating top-centre, a
readout panel bottom-left, a range readout bottom-right, and nothing that
said these were parts of one instrument. This puts them on one rail across
the bottom of the viewport — the dock — with a tab strip pinned to the bottom
edge and whichever panel is open growing upward from it. The top of the
screen keeps only the scale ladder and the nameplate, so the map itself is
what fills the frame.

Three tabs. SEARCH is the old search, with its field pinned to the bottom of
the panel and the results growing upward above it, so the thing being typed
into never moves while the list grows. READOUT is the old bottom-left panel.
DISPLAY is new: five layer toggles — labels, orbits, grid, deep sky, sky —
each a real scene object switched by visibility, except the ones the galaxy
crossfade already rewrites every frame, whose toggles fold into that
crossfade instead of fighting it. The range readout sits on the strip itself,
so it is readable whatever is open.

Behaviour worth stating: choosing a search result hands the panel straight
back to the readout, since the thing to look at is now the scene. `/` opens
the search from anywhere. Below `sm` the dock is the strip alone; a tap opens
a panel as a sheet, a tap on the scene folds it away. The body-detail page
gets the same dock with only the search — the info panel is its reading.

Two things found on the way. CSS2DRenderer gives every label its own
z-index for depth order, and the label host created no stacking context, so
labels painted over every HUD panel; `isolate` on the host keeps them under.
And starmap-hud's readout tests were really tests of the panel that moved,
so they moved with it.

Verified: build clean, 535/535 unit, 6/6 end-to-end, design detector clean,
screenshots at 1440×900 and 390×844 across galaxy, galactic, system,
body-detail, all three tabs and the layers-off state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
2026-08-18 20:57:10 +02:00

55 lines
2.4 KiB
TypeScript

import { expect, test } from '@playwright/test';
import { openSearch } from './support/open-search';
import { backButtonLocator } from './support/wait-for-back-button';
test.describe('Search-driven navigation', () => {
test('selecting a star result flies into that system and shows the back-to-galaxy control', async ({ page }) => {
await page.goto('/?stars=4000');
const searchInput = await openSearch(page);
await searchInput.fill('Proxima Centauri');
const result = page.getByRole('button', { name: /Proxima Centauri/ });
await expect(result).toBeVisible();
await result.click();
// Selecting a result hands the dock straight back to the readout (before the flight even
// starts): the thing to look at is now the scene, and the search panel folds away with it.
await expect(page.getByRole('tab', { name: 'Readout' })).toHaveAttribute('aria-selected', 'true');
await expect(searchInput).toHaveCount(0);
await expect(backButtonLocator(page)).toBeVisible({ timeout: 15_000 });
});
test('selecting a body result navigates straight to its detail route and shows real NASA data', async ({ page }) => {
await page.goto('/?stars=4000');
const searchInput = await openSearch(page);
await searchInput.fill('Earth');
const result = page.getByText('Earth', { exact: true });
await expect(result).toBeVisible();
await result.click();
await expect(page).toHaveURL(/\/body\/earth$/);
// The body-detail route is lazy-loaded; the first navigation to it in a dev-server session
// can take a few seconds to bundle/compile, so this allows more time than the default 5s.
await expect(page.getByRole('heading', { name: 'Earth' })).toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Planet · Sol')).toBeVisible();
await expect(page.getByText('Radius')).toBeVisible();
});
test('typing fewer than two characters shows no results, and Escape clears the query', async ({ page }) => {
await page.goto('/?stars=4000');
const searchInput = await openSearch(page);
await searchInput.fill('E');
await expect(page.getByTestId('search-results')).toHaveCount(0);
await searchInput.fill('Earth');
await expect(page.getByTestId('search-results').locator('li')).not.toHaveCount(0);
await searchInput.press('Escape');
await expect(searchInput).toHaveValue('');
await expect(page.getByTestId('search-results')).toHaveCount(0);
});
});