mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 15:36:33 +00:00
c1d55c463c
The Atlas country check timed out. It asked Dawarich for a year of visited cities in one request, and Dawarich computes that over every point in the window with nothing cached, so a dense recorder never got under the 15 s the server allows. The year is now asked for a month at a time, one request after another, and folded back together. The windows sit a second apart because Dawarich reads the bounds as inclusive. The recorded route only appeared after a reload. Every Dawarich call from the browser carried the shared 8 s timeout, so the browser gave up while the server carried on and cached the answer for the next page load. Calls that make the server talk to Dawarich now wait two minutes, the Atlas four. On the GL renderers the draw also gave up whenever isStyleLoaded() was false, which it is while any tile is still loading, and only retried on style.load. It now waits for idle. The hazard overlay had the same guard and gets the same wait. The pill shows a ring while the route loads, so a slow first fetch no longer looks like "done, nothing there". A collapsed day now takes its recorded route off the map, following the same folds the planner already applies to places, the road trip sidebar's included.
108 lines
3.9 KiB
TypeScript
108 lines
3.9 KiB
TypeScript
/**
|
|
* FE-ROADTRIP-HAZARDGL-001 to FE-ROADTRIP-HAZARDGL-003: when the hazard overlay
|
|
* on the GL renderer actually gets drawn.
|
|
*
|
|
* It shares a failure with the recorded Dawarich route: `isStyleLoaded()` is
|
|
* false while any source still has tiles in flight, not only before the first
|
|
* style. A draw that gave up there and waited for `style.load` alone stayed off
|
|
* the map until the style was rebuilt, which in practice meant a reload. These
|
|
* cases pin the wait for `idle` that replaced it, and that the wait does not
|
|
* outlive the component.
|
|
*/
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import { renderHook } from '@testing-library/react'
|
|
import type { Map as MapboxMap, Popup } from 'mapbox-gl'
|
|
import type { RoadtripHazard } from '@trek/shared'
|
|
import { useHazardLayerGL } from './useHazardLayerGL'
|
|
|
|
vi.mock('../../i18n/TranslationContext', () => ({ useTranslation: () => ({ t: (key: string) => key }) }))
|
|
|
|
const SOURCE = 'roadtrip-hazards'
|
|
|
|
const HAZARD: RoadtripHazard = {
|
|
id: 'dwd-1',
|
|
source: 'DWD',
|
|
title: 'Sturmboeen',
|
|
description: 'Boeen bis 85 km/h',
|
|
updatedAt: '2026-09-14T10:00:00.000Z',
|
|
validUntil: null,
|
|
url: 'https://www.dwd.de/warnungen',
|
|
geometry: { type: 'Point', coordinates: [13.4, 52.5] },
|
|
}
|
|
|
|
function fakeGlMap(styleLoaded: boolean) {
|
|
const sources = new Set<string>()
|
|
const layers = new Set<string>()
|
|
const once = new Map<string, Set<() => void>>()
|
|
const state = { styleLoaded }
|
|
const map = {
|
|
isStyleLoaded: vi.fn(() => state.styleLoaded),
|
|
getSource: vi.fn((id: string) => (sources.has(id) ? {} : undefined)),
|
|
addSource: vi.fn((id: string) => { sources.add(id) }),
|
|
removeSource: vi.fn((id: string) => { sources.delete(id) }),
|
|
getLayer: vi.fn((id: string) => (layers.has(id) ? {} : undefined)),
|
|
addLayer: vi.fn((spec: { id: string }) => { layers.add(spec.id) }),
|
|
removeLayer: vi.fn((id: string) => { layers.delete(id) }),
|
|
on: vi.fn(),
|
|
once: vi.fn((type: string, fn: () => void) => {
|
|
const set = once.get(type) ?? new Set<() => void>()
|
|
set.add(fn)
|
|
once.set(type, set)
|
|
}),
|
|
off: vi.fn((type: string, fn: () => void) => { once.get(type)?.delete(fn) }),
|
|
}
|
|
return {
|
|
map: map as unknown as MapboxMap,
|
|
spies: map,
|
|
state,
|
|
sources,
|
|
idleListeners: () => once.get('idle')?.size ?? 0,
|
|
emitIdle: () => {
|
|
const pending = [...(once.get('idle') ?? [])]
|
|
once.delete('idle')
|
|
pending.forEach(fn => fn())
|
|
},
|
|
}
|
|
}
|
|
|
|
const popup = () => ({}) as Popup
|
|
|
|
describe('useHazardLayerGL', () => {
|
|
it('FE-ROADTRIP-HAZARDGL-001: draws straight away into a style that is ready', () => {
|
|
const gl = fakeGlMap(true)
|
|
renderHook(() => useHazardLayerGL(gl.map, true, [HAZARD], popup))
|
|
|
|
expect(gl.spies.addSource).toHaveBeenCalledWith(SOURCE, expect.objectContaining({ type: 'geojson' }))
|
|
expect(gl.spies.addLayer).toHaveBeenCalledTimes(3)
|
|
expect(gl.idleListeners()).toBe(0)
|
|
})
|
|
|
|
it('FE-ROADTRIP-HAZARDGL-002: hazards that arrive while tiles are loading are drawn once the map is idle', () => {
|
|
const gl = fakeGlMap(false)
|
|
renderHook(() => useHazardLayerGL(gl.map, true, [HAZARD], popup))
|
|
expect(gl.spies.addSource).not.toHaveBeenCalled()
|
|
expect(gl.idleListeners()).toBe(1)
|
|
|
|
// Still busy at the first idle: wait again, and only once.
|
|
gl.emitIdle()
|
|
expect(gl.spies.addSource).not.toHaveBeenCalled()
|
|
expect(gl.idleListeners()).toBe(1)
|
|
|
|
gl.state.styleLoaded = true
|
|
gl.emitIdle()
|
|
expect(gl.spies.addSource).toHaveBeenCalledTimes(1)
|
|
expect(gl.idleListeners()).toBe(0)
|
|
})
|
|
|
|
it('FE-ROADTRIP-HAZARDGL-003: an unmount while waiting takes the wait with it', () => {
|
|
const gl = fakeGlMap(false)
|
|
const view = renderHook(() => useHazardLayerGL(gl.map, true, [HAZARD], popup))
|
|
|
|
view.unmount()
|
|
|
|
expect(gl.idleListeners()).toBe(0)
|
|
expect(gl.spies.off).toHaveBeenCalledWith('idle', expect.any(Function))
|
|
expect(gl.sources.size).toBe(0)
|
|
})
|
|
})
|