mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
fbba8f96b7
The three planner view switches lived in sessionStorage only, so a new tab, another browser or another device opened every trip on the plain view again. They are now kept per trip in one user setting, planner_trip_views, validated against a contract in @trek/shared. The server keeps only switches that are on and only trips the user can still open, so a deleted trip or one the user left drops out on the next write. The tab copy in sessionStorage stays and answers until the settings have loaded. Refs #2639
224 lines
9.8 KiB
TypeScript
224 lines
9.8 KiB
TypeScript
// FE-TP-MAPVIEW-001 to FE-TP-MAPVIEW-013
|
|
//
|
|
// The planner's map view switches and its first fit, driven straight through
|
|
// usePlannerMapView. Every toggle is called here, together with the storage each one
|
|
// remembers its state in: the planner tests only ever read these values.
|
|
import { act, renderHook } from '@testing-library/react'
|
|
import { resetAllStores, seedStore } from '../../../tests/helpers/store'
|
|
import { buildPlace, buildSettings, buildTrip } from '../../../tests/helpers/factories'
|
|
import { useSettingsStore } from '../../store/settingsStore'
|
|
import type { Place, Trip } from '../../types'
|
|
import { usePlannerMapView } from './usePlannerMapView'
|
|
|
|
interface Props {
|
|
tripId: number
|
|
trip: Trip | null
|
|
places: Place[]
|
|
selectedDayId: number | null
|
|
isMobile: boolean
|
|
}
|
|
|
|
function renderMapView(initial: Partial<Props> = {}) {
|
|
const initialProps: Props = { tripId: 7, trip: null, places: [], selectedDayId: null, isMobile: false, ...initial }
|
|
return renderHook((props: Props) => usePlannerMapView(props), { initialProps })
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks()
|
|
resetAllStores()
|
|
})
|
|
|
|
describe('usePlannerMapView', () => {
|
|
it('FE-TP-MAPVIEW-001: a trip nobody has looked at opens with every layer off and the map free', () => {
|
|
const { result } = renderMapView()
|
|
|
|
expect(result.current.routeShown).toBe(false)
|
|
expect(result.current.transitRoutesShown).toBe(false)
|
|
expect(result.current.routeProfile).toBe('driving')
|
|
expect(result.current.overviewShown).toBe(false)
|
|
expect(result.current.mapLocked).toBe(false)
|
|
expect(result.current.mapLockedRef.current).toBe(false)
|
|
expect(result.current.dawarichTrailShown).toBe(false)
|
|
expect(result.current.dawarichTrail.status).toBe('idle')
|
|
expect(result.current.fitKey).toBe(0)
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-002: the day route remembers an explicit choice per trip, given as a value or an updater', () => {
|
|
const { result, rerender } = renderMapView()
|
|
|
|
act(() => { result.current.setRouteShown(true) })
|
|
expect(result.current.routeShown).toBe(true)
|
|
expect(localStorage.getItem('trek:day-route:7')).toBe('true')
|
|
// No day selected: the transit lines stay off whatever the toggle says (#2019).
|
|
expect(result.current.transitRoutesShown).toBe(false)
|
|
|
|
rerender({ tripId: 7, trip: null, places: [], selectedDayId: 3, isMobile: false })
|
|
expect(result.current.transitRoutesShown).toBe(true)
|
|
|
|
act(() => { result.current.setRouteShown(prev => !prev) })
|
|
expect(result.current.routeShown).toBe(false)
|
|
expect(localStorage.getItem('trek:day-route:7')).toBe('false')
|
|
expect(result.current.transitRoutesShown).toBe(false)
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-003: a stored choice comes back with the trip, and anything else counts as no choice', () => {
|
|
localStorage.setItem('trek:day-route:7', 'true')
|
|
localStorage.setItem('trek:day-route:8', 'false')
|
|
localStorage.setItem('trek:day-route:9', 'maybe')
|
|
|
|
expect(renderMapView({ tripId: 7 }).result.current.routeShown).toBe(true)
|
|
expect(renderMapView({ tripId: 8 }).result.current.routeShown).toBe(false)
|
|
|
|
const unknown = renderMapView({ tripId: 9 }).result
|
|
expect(unknown.current.routeShown).toBe(false)
|
|
act(() => { unknown.current.autoShowRoute() })
|
|
expect(unknown.current.routeShown).toBe(true)
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-004: the automatic route is a default that never overrides an explicit off or reaches storage', () => {
|
|
const fresh = renderMapView().result
|
|
act(() => { fresh.current.autoShowRoute() })
|
|
expect(fresh.current.routeShown).toBe(true)
|
|
expect(localStorage.getItem('trek:day-route:7')).toBeNull()
|
|
|
|
localStorage.setItem('trek:day-route:8', 'false')
|
|
const declined = renderMapView({ tripId: 8 }).result
|
|
act(() => { declined.current.autoShowRoute() })
|
|
expect(declined.current.routeShown).toBe(false)
|
|
expect(localStorage.getItem('trek:day-route:8')).toBe('false')
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-005: without a trip id the route toggle works but is remembered nowhere', () => {
|
|
const setItem = vi.spyOn(Storage.prototype, 'setItem')
|
|
const { result } = renderMapView({ tripId: Number.NaN })
|
|
|
|
act(() => { result.current.setRouteShown(true) })
|
|
|
|
expect(result.current.routeShown).toBe(true)
|
|
expect(setItem).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-006: the route profile is a plain per-session choice', () => {
|
|
const { result } = renderMapView()
|
|
act(() => { result.current.setRouteProfile('plugin:bikes/gravel') })
|
|
expect(result.current.routeProfile).toBe('plugin:bikes/gravel')
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-007: the whole-trip overview flips and is kept per trip, in the tab and on the account', () => {
|
|
seedStore(useSettingsStore, { settings: buildSettings(), isLoaded: true })
|
|
const { result } = renderMapView()
|
|
|
|
act(() => { result.current.toggleOverview() })
|
|
expect(result.current.overviewShown).toBe(true)
|
|
expect(sessionStorage.getItem('trip-overview-7')).toBe('1')
|
|
expect(useSettingsStore.getState().settings.planner_trip_views).toEqual({ '7': { overview: true } })
|
|
expect(renderMapView().result.current.overviewShown).toBe(true)
|
|
expect(renderMapView({ tripId: 8 }).result.current.overviewShown).toBe(false)
|
|
|
|
act(() => { result.current.toggleOverview() })
|
|
expect(result.current.overviewShown).toBe(false)
|
|
expect(sessionStorage.getItem('trip-overview-7')).toBe('0')
|
|
expect(useSettingsStore.getState().settings.planner_trip_views).toEqual({})
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-008: the map lock flips, is read through its ref and is remembered by the browser', () => {
|
|
const { result } = renderMapView()
|
|
|
|
act(() => { result.current.toggleMapLocked() })
|
|
expect(result.current.mapLocked).toBe(true)
|
|
expect(result.current.mapLockedRef.current).toBe(true)
|
|
expect(localStorage.getItem('trek:map-locked')).toBe('1')
|
|
// A browser-wide choice, so another trip opens locked too.
|
|
expect(renderMapView({ tripId: 8 }).result.current.mapLocked).toBe(true)
|
|
|
|
act(() => { result.current.toggleMapLocked() })
|
|
expect(result.current.mapLocked).toBe(false)
|
|
expect(result.current.mapLockedRef.current).toBe(false)
|
|
expect(localStorage.getItem('trek:map-locked')).toBe('0')
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-009: a storage that refuses the lock still leaves a working toggle', () => {
|
|
const realGet = Storage.prototype.getItem
|
|
const realSet = Storage.prototype.setItem
|
|
vi.spyOn(Storage.prototype, 'getItem').mockImplementation(function (this: Storage, key: string) {
|
|
if (key === 'trek:map-locked') throw new DOMException('denied', 'SecurityError')
|
|
return realGet.call(this, key)
|
|
})
|
|
vi.spyOn(Storage.prototype, 'setItem').mockImplementation(function (this: Storage, key: string, value: string) {
|
|
if (key === 'trek:map-locked') throw new DOMException('denied', 'SecurityError')
|
|
realSet.call(this, key, value)
|
|
})
|
|
|
|
const { result } = renderMapView()
|
|
expect(result.current.mapLocked).toBe(false)
|
|
|
|
act(() => { result.current.toggleMapLocked() })
|
|
expect(result.current.mapLocked).toBe(true)
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-010: the phone flag is read through a ref that follows every render', () => {
|
|
const { result, rerender } = renderMapView()
|
|
expect(result.current.isMobileRef.current).toBe(false)
|
|
|
|
rerender({ tripId: 7, trip: null, places: [], selectedDayId: null, isMobile: true })
|
|
expect(result.current.isMobileRef.current).toBe(true)
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-011: the recorded trail flips and is kept per trip, in the tab and on the account', () => {
|
|
seedStore(useSettingsStore, { settings: buildSettings(), isLoaded: true })
|
|
const { result } = renderMapView()
|
|
|
|
act(() => { result.current.toggleDawarichTrail() })
|
|
expect(result.current.dawarichTrailShown).toBe(true)
|
|
expect(sessionStorage.getItem('trip-dawarich-7')).toBe('1')
|
|
expect(useSettingsStore.getState().settings.planner_trip_views).toEqual({ '7': { dawarich: true } })
|
|
// The addon is off in this store, so switching the layer on asks nothing of the network.
|
|
expect(result.current.dawarichTrail.status).toBe('idle')
|
|
expect(renderMapView().result.current.dawarichTrailShown).toBe(true)
|
|
|
|
act(() => { result.current.toggleDawarichTrail() })
|
|
expect(result.current.dawarichTrailShown).toBe(false)
|
|
expect(sessionStorage.getItem('trip-dawarich-7')).toBe('0')
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-013: the overview and the trail switched on in another browser are on here too (#2639)', () => {
|
|
// A fresh tab: nothing in sessionStorage, only the account's settings.
|
|
seedStore(useSettingsStore, {
|
|
settings: buildSettings({ planner_trip_views: { '7': { overview: true, dawarich: true } } }),
|
|
isLoaded: true,
|
|
})
|
|
|
|
const { result } = renderMapView()
|
|
expect(result.current.overviewShown).toBe(true)
|
|
expect(result.current.dawarichTrailShown).toBe(true)
|
|
expect(renderMapView({ tripId: 8 }).result.current.overviewShown).toBe(false)
|
|
})
|
|
|
|
it('FE-TP-MAPVIEW-012: each trip gets one automatic fit, once it has a place to fit to', () => {
|
|
const tripA = buildTrip({ id: 1 })
|
|
const tripB = buildTrip({ id: 2 })
|
|
const unplaced = buildPlace({ lat: null, lng: null })
|
|
const placed = buildPlace({ lat: 46.95, lng: 7.45 })
|
|
const { result, rerender } = renderMapView({ places: [placed] })
|
|
const show = (over: Partial<Props>) => rerender({ tripId: 7, trip: null, places: [], selectedDayId: null, isMobile: false, ...over })
|
|
|
|
// No trip loaded yet: nothing to fit.
|
|
expect(result.current.fitKey).toBe(0)
|
|
|
|
show({ trip: tripA, places: [unplaced] })
|
|
expect(result.current.fitKey).toBe(0)
|
|
|
|
show({ trip: tripA, places: [unplaced, placed] })
|
|
expect(result.current.fitKey).toBe(1)
|
|
|
|
show({ trip: tripA, places: [placed] })
|
|
expect(result.current.fitKey).toBe(1)
|
|
|
|
show({ trip: tripB, places: [placed] })
|
|
expect(result.current.fitKey).toBe(2)
|
|
|
|
act(() => { result.current.setFitKey(k => k + 1) })
|
|
expect(result.current.fitKey).toBe(3)
|
|
})
|
|
})
|