da236643f2
Post-M2 self-walkthrough polish, batched into one commit. Map / heat: - fix heat-layer white-screen crash after login (add layer to map before setLatLngs; an off-map leaflet.heat layer has a null _map and throws) - normalize each heat layer to the densest pixel cell visible in the CURRENT viewport (maxZoom:0 so intensity factor f=1) and recompute on moveend/zoomend, so sparse poo data reaches red and stays normalized at any zoom level - dark CARTO basemap tiles when the color scheme is dark UI: - dark-mode toggle in the top-right, beside the settings gear - switch top-right nav (records / theme / settings / logout) to Feather icons with hover tooltips - home: Grafana-style quick time-range presets + back/forward shift buttons, placed between the From/To pickers and Apply; fix Select/tooltip z-index (Leaflet stacking) and the shift-button height alignment API client: - stop flooding GET /api/session with 401s: the session probe and the login endpoint own their 401s (no global redirect), which fixes the logout hang and the spinning login page Compose: - rename docker-compose.override.yml -> docker-compose.dev.yml as an explicit, non-auto-layered dev stack (8001, -dev container names, prod-copy ./data DB); update tests/test_deployment.py (read dev.yml, tolerate the !override tag) and the README "Docker Compose" section Tests: - pixel-grid peak counter, time-range presets, heat-layer ordering regression, and 401-redirect regression
70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
/**
|
|
* Tests for the quick-range preset + window-shift helpers (Grafana-style).
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest'
|
|
import { TIME_PRESETS, presetRange, shiftRange } from './mapUtils'
|
|
|
|
describe('TIME_PRESETS', () => {
|
|
it('exposes the 7 expected quick ranges in order', () => {
|
|
expect(TIME_PRESETS.map((p) => p.value)).toEqual([
|
|
'24h',
|
|
'1w',
|
|
'2w',
|
|
'1mo',
|
|
'6mo',
|
|
'1y',
|
|
'5y',
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('presetRange', () => {
|
|
const now = new Date('2026-06-13T12:00:00Z')
|
|
|
|
it('ends at now and spans the given duration (24h)', () => {
|
|
const { start, end } = presetRange(24 * 3_600_000, now)
|
|
expect(end).toBe('2026-06-13T12:00:00Z')
|
|
expect(start).toBe('2026-06-12T12:00:00Z')
|
|
})
|
|
|
|
it('spans a week', () => {
|
|
const { start, end } = presetRange(7 * 24 * 3_600_000, now)
|
|
expect(end).toBe('2026-06-13T12:00:00Z')
|
|
expect(start).toBe('2026-06-06T12:00:00Z')
|
|
})
|
|
|
|
it('emits second-precision ISO with no milliseconds', () => {
|
|
const { start, end } = presetRange(3_600_000, now)
|
|
expect(start).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/)
|
|
expect(end).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/)
|
|
})
|
|
})
|
|
|
|
describe('shiftRange', () => {
|
|
it('moves a 24h window back by 24h when direction = -1', () => {
|
|
const { start, end } = shiftRange('2026-06-12T12:00:00Z', '2026-06-13T12:00:00Z', -1)
|
|
expect(start).toBe('2026-06-11T12:00:00Z')
|
|
expect(end).toBe('2026-06-12T12:00:00Z')
|
|
})
|
|
|
|
it('moves a 24h window forward by 24h when direction = +1', () => {
|
|
const { start, end } = shiftRange('2026-06-12T12:00:00Z', '2026-06-13T12:00:00Z', 1)
|
|
expect(start).toBe('2026-06-13T12:00:00Z')
|
|
expect(end).toBe('2026-06-14T12:00:00Z')
|
|
})
|
|
|
|
it('shifts by the window OWN span (a 1-week window moves a week)', () => {
|
|
const { start, end } = shiftRange('2026-06-06T12:00:00Z', '2026-06-13T12:00:00Z', -1)
|
|
expect(start).toBe('2026-05-30T12:00:00Z')
|
|
expect(end).toBe('2026-06-06T12:00:00Z')
|
|
})
|
|
|
|
it('is reversible: shift back then forward returns to the original window', () => {
|
|
const orig = { start: '2026-06-06T12:00:00Z', end: '2026-06-13T12:00:00Z' }
|
|
const back = shiftRange(orig.start, orig.end, -1)
|
|
const fwd = shiftRange(back.start, back.end, 1)
|
|
expect(fwd).toEqual(orig)
|
|
})
|
|
})
|