Files
TREK/client/src/components/Roadtrip/serviceMarker.test.ts
T
Maurice 65be8db122 test(roadtrip): cover the surface the domain shipped without
The domain arrived with 557 lines, a service test, and nothing else. No
controller test, no e2e, and no MCP test — so the guard chain, the trip scoping
on every route, the cross-trip track check and the day_edit parity were all
unasserted, and the whole domain sat in the 80 catch-all where it could shed
twenty points against the headroom of the domains beside it.

The e2e is the one that earns its place. `@RequireAddon` is metadata and does
nothing on its own, and AddonGuard is not global, so the ordering the
controller's own comment calls load-bearing is exactly the kind of thing a later
change removes without noticing — it had already happened once. Deleting the
guard now turns three cases red, which I checked by deleting it.

The MCP tools had never executed at all: the shared harness seeds the addon
disabled, so `when: roadtripAddonOn` kept all five from attaching. They now run
against the same checks the REST routes make, including the demo account and a
member without day_edit, because the repo requires the two surfaces to move
together.

Two client modules that had no test get one: the overlay builder, where two
decisions live that are not cosmetic — which offered road is drawn as the one you
are on, and that a label hangs where its own route is unlike every other, rather
than at a midpoint that can fall on a stretch two routes share — and the service
marker, where the thing worth pinning is that an ordinary place gets nothing
back, and that the anchor arithmetic includes the border.

The domain carries its own coverage floor now instead of hiding in the catch-all.
Pinned under the measured figure on purpose; the exact ratchet gets regenerated
with the repo's own script after a full run.

Along the way, roadtrip_vias and roadtrip_day_tracks join the reset list every
other domain's tables are on. Without that a via written by one case was still
there for the next one, which is how the first version of the MCP suite passed
an assertion it should have failed.
2026-09-07 18:36:54 +02:00

76 lines
3.5 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { serviceMarkerHtml, serviceMarkerOuter, SERVICE_MARKER_SIZE, SERVICE_MARKER_SIZE_SELECTED } from './serviceMarker'
import { serviceColor } from './roadtripModel'
/**
* FE-SVCMARK-001..007 — the disc a service stop gets on the map.
*
* Both renderers need the same string: one hands it to a Leaflet divIcon, the
* other to an element it attaches itself. What is worth pinning is that an
* ordinary place gets nothing at all — a petrol station is something the drive
* passes through, and giving it a place-sized marker made a day look like it had
* twice the stops it has — and that the anchor arithmetic includes the border,
* or the pin sits a couple of pixels off the coordinate it marks.
*/
describe('serviceMarkerHtml', () => {
it('FE-SVCMARK-001: an ordinary place gets no service marker', () => {
// Null is the signal to fall back to the place marker, so this is what keeps
// a destination from being drawn as a fuel stop.
expect(serviceMarkerHtml(null, false)).toBeNull()
expect(serviceMarkerHtml(undefined, false)).toBeNull()
expect(serviceMarkerHtml('', false)).toBeNull()
})
it('FE-SVCMARK-002: each kind of stop gets a disc in its own colour', () => {
// All six, not only the four the corridor picker offers: a meal and a
// viewpoint are pauses on the way too, and the map draws them the same way.
for (const kind of ['fuel', 'charging', 'rest_area', 'campsite', 'restaurant', 'sights']) {
const html = serviceMarkerHtml(kind, false)
expect(html, kind).toBeTruthy()
expect(html, kind).toContain(serviceColor(kind))
expect(html, kind).toContain('border-radius:50%')
}
})
it('FE-SVCMARK-003: an unknown service kind is still not drawn as one', () => {
// isServiceStopType is the gate; a value the picker never offers must not
// reach the renderer and produce a disc in an undefined colour.
expect(serviceMarkerHtml('banana', false)).toBeNull()
})
it('FE-SVCMARK-004: selecting one grows it, the way a place marker confirms a click', () => {
const plain = serviceMarkerHtml('fuel', false)!
const selected = serviceMarkerHtml('fuel', true)!
expect(plain).toContain(`width:${SERVICE_MARKER_SIZE}px`)
expect(selected).toContain(`width:${SERVICE_MARKER_SIZE_SELECTED}px`)
expect(SERVICE_MARKER_SIZE_SELECTED).toBeGreaterThan(SERVICE_MARKER_SIZE)
})
it('FE-SVCMARK-005: selected carries the heavier ring, unselected the plain shadow', () => {
expect(serviceMarkerHtml('fuel', true)!).toContain('0 0 0 3px')
expect(serviceMarkerHtml('fuel', false)!).not.toContain('0 0 0 3px')
})
it('FE-SVCMARK-006: the icon is drawn white and inline, never a logo or a photo', () => {
// A fuel stop used to come back from the place search carrying the
// operator's logo, so the map showed a brand roundel where every other
// road-trip surface showed a pump.
const html = serviceMarkerHtml('fuel', false)!
expect(html).toContain('<svg')
expect(html).toContain('#fff')
expect(html).not.toContain('<img')
expect(html).not.toContain('background-image')
})
})
describe('serviceMarkerOuter', () => {
it('FE-SVCMARK-007: the box an anchor centres on includes the border', () => {
// content-box plus a 2px border on each side. Measuring the inner size would
// leave every service pin two pixels off the coordinate it marks.
expect(serviceMarkerOuter(false)).toBe(SERVICE_MARKER_SIZE + 4)
expect(serviceMarkerOuter(true)).toBe(SERVICE_MARKER_SIZE_SELECTED + 4)
})
})