This commit is contained in:
Andras Schmelczer 2026-05-14 22:07:14 +01:00
parent 084117cea8
commit a8de0a614d
36 changed files with 1329 additions and 522 deletions

View file

@ -2,7 +2,8 @@ import { act, renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useMapData } from './useMapData';
import type { ApiResponse, Bounds, ViewChangeParams } from '../types';
import type { TravelTimeEntry } from './useTravelTime';
import type { ApiResponse, Bounds, FeatureMeta, ViewChangeParams } from '../types';
vi.mock('../lib/pocketbase', () => ({
default: { authStore: { isValid: false, token: '' } },
@ -32,6 +33,7 @@ async function flushPromises() {
describe('useMapData', () => {
const requests: Array<{ url: string; resolve: (response: Response) => void }> = [];
const noTravelTimeEntries: TravelTimeEntry[] = [];
beforeEach(() => {
vi.useFakeTimers();
@ -59,7 +61,7 @@ describe('useMapData', () => {
viewFeature: null,
activeFeature: null,
pinnedFeature: null,
travelTimeEntries: [],
travelTimeEntries: noTravelTimeEntries,
})
);
@ -93,4 +95,264 @@ describe('useMapData', () => {
expect(result.current.data).toEqual([{ h3: 'new', count: 7, lat: 3.5, lon: 3.5 }]);
});
it('stores the visible map center separately from the rendered map center', async () => {
const { result } = renderHook(() =>
useMapData({
filters: {},
features: [],
viewFeature: null,
activeFeature: null,
pinnedFeature: null,
travelTimeEntries: noTravelTimeEntries,
})
);
await act(async () => {
result.current.handleViewChange({
...viewChange({ south: 1, west: 1, north: 2, east: 2 }),
latitude: 51.5,
longitude: -0.1,
visibleLatitude: 51.6,
visibleLongitude: -0.2,
});
});
expect(result.current.currentView).toEqual({ latitude: 51.5, longitude: -0.1, zoom: 10 });
expect(result.current.currentVisibleView).toEqual({
latitude: 51.6,
longitude: -0.2,
zoom: 10,
});
});
it('resets the colour range to drag preview data while a slider is active', async () => {
const bounds = { south: 1, west: 1, north: 2, east: 2 };
const features: FeatureMeta[] = [
{
name: 'price',
type: 'numeric',
min: 0,
max: 100,
},
];
const filters = { price: [20, 80] as [number, number] };
const { result, rerender } = renderHook(
({ activeFeature }: { activeFeature: string | null }) =>
useMapData({
filters,
features,
viewFeature: 'price',
activeFeature,
pinnedFeature: null,
travelTimeEntries: noTravelTimeEntries,
}),
{ initialProps: { activeFeature: null as string | null } }
);
await act(async () => {
result.current.handleViewChange(viewChange(bounds));
});
await act(async () => {
vi.advanceTimersByTime(150);
});
await act(async () => {
requests[0].resolve(
response([
{ h3: 'committed-low', count: 1, lat: 1.25, lon: 1.25, avg_price: 20 },
{ h3: 'committed-high', count: 1, lat: 1.75, lon: 1.75, avg_price: 80 },
])
);
await flushPromises();
});
expect(result.current.colorRange?.[0]).toBeCloseTo(23);
expect(result.current.colorRange?.[1]).toBeCloseTo(77);
await act(async () => {
rerender({ activeFeature: 'price' });
await flushPromises();
});
expect(requests).toHaveLength(2);
await act(async () => {
requests[1].resolve(
response([
{ h3: 'preview-low', count: 1, lat: 1.25, lon: 1.25, avg_price: 0 },
{ h3: 'preview-high', count: 1, lat: 1.75, lon: 1.75, avg_price: 100 },
])
);
await flushPromises();
});
expect(result.current.data).toEqual([
{ h3: 'preview-low', count: 1, lat: 1.25, lon: 1.25, avg_price: 0 },
{ h3: 'preview-high', count: 1, lat: 1.75, lon: 1.75, avg_price: 100 },
]);
expect(result.current.colorRange?.[0]).toBeCloseTo(5);
expect(result.current.colorRange?.[1]).toBeCloseTo(95);
});
it('does not reuse cached drag preview data when the drag request changes', async () => {
const bounds = { south: 1, west: 1, north: 2, east: 2 };
const features: FeatureMeta[] = [
{
name: 'price',
type: 'numeric',
min: 0,
max: 100,
},
];
const committedData = [
{ h3: 'committed-low', count: 1, lat: 1.25, lon: 1.25, avg_price: 20 },
{ h3: 'committed-high', count: 1, lat: 1.75, lon: 1.75, avg_price: 80 },
];
const previewData = [
{ h3: 'preview-low', count: 1, lat: 1.25, lon: 1.25, avg_price: 0 },
{ h3: 'preview-high', count: 1, lat: 1.75, lon: 1.75, avg_price: 100 },
];
const { result, rerender } = renderHook(
({
filters,
activeFeature,
}: {
filters: Record<string, [number, number]>;
activeFeature: string | null;
}) =>
useMapData({
filters,
features,
viewFeature: 'price',
activeFeature,
pinnedFeature: null,
travelTimeEntries: noTravelTimeEntries,
}),
{
initialProps: {
filters: { price: [20, 80] as [number, number] },
activeFeature: null as string | null,
},
}
);
await act(async () => {
result.current.handleViewChange(viewChange(bounds));
});
await act(async () => {
vi.advanceTimersByTime(150);
});
await act(async () => {
requests[0].resolve(response(committedData));
await flushPromises();
});
await act(async () => {
rerender({
filters: { price: [20, 80] },
activeFeature: 'price',
});
await flushPromises();
});
await act(async () => {
requests[1].resolve(response(previewData));
await flushPromises();
});
expect(result.current.data).toEqual(previewData);
await act(async () => {
rerender({
filters: { price: [10, 90] },
activeFeature: 'price',
});
});
expect(result.current.data).toEqual(committedData);
});
it('resets a pinned colour range after a slider commits a new filter', async () => {
const bounds = { south: 1, west: 1, north: 2, east: 2 };
const features: FeatureMeta[] = [
{
name: 'price',
type: 'numeric',
min: 0,
max: 100,
},
];
const { result, rerender } = renderHook(
({
filters,
activeFeature,
}: {
filters: Record<string, [number, number]>;
activeFeature: string | null;
}) =>
useMapData({
filters,
features,
viewFeature: 'price',
activeFeature,
pinnedFeature: 'price',
travelTimeEntries: noTravelTimeEntries,
}),
{
initialProps: {
filters: { price: [20, 80] as [number, number] },
activeFeature: null as string | null,
},
}
);
await act(async () => {
result.current.handleViewChange(viewChange(bounds));
});
await act(async () => {
vi.advanceTimersByTime(150);
});
await act(async () => {
requests[0].resolve(
response([
{ h3: 'old-low', count: 1, lat: 1.25, lon: 1.25, avg_price: 20 },
{ h3: 'old-high', count: 1, lat: 1.75, lon: 1.75, avg_price: 80 },
])
);
await flushPromises();
});
expect(result.current.colorRange?.[0]).toBeCloseTo(23);
expect(result.current.colorRange?.[1]).toBeCloseTo(77);
await act(async () => {
rerender({
filters: { price: [20, 80] },
activeFeature: 'price',
});
await flushPromises();
});
await act(async () => {
rerender({
filters: { price: [10, 90] },
activeFeature: null,
});
});
await act(async () => {
vi.advanceTimersByTime(150);
});
const committedRequest = requests[requests.length - 1];
await act(async () => {
committedRequest.resolve(
response([
{ h3: 'new-low', count: 1, lat: 1.25, lon: 1.25, avg_price: 0 },
{ h3: 'new-high', count: 1, lat: 1.75, lon: 1.75, avg_price: 100 },
])
);
await flushPromises();
});
expect(result.current.colorRange?.[0]).toBeCloseTo(5);
expect(result.current.colorRange?.[1]).toBeCloseTo(95);
});
});