202 lines
5.6 KiB
TypeScript
202 lines
5.6 KiB
TypeScript
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { useHexagonSelection } from './useHexagonSelection';
|
|
import type { FeatureMeta, HexagonStatsResponse, PostcodeGeometry } from '../types';
|
|
|
|
vi.mock('../lib/pocketbase', () => ({
|
|
default: { authStore: { isValid: false, token: '' } },
|
|
}));
|
|
|
|
vi.mock('../lib/analytics', () => ({
|
|
trackEvent: vi.fn(),
|
|
}));
|
|
|
|
const postcodeGeometry: PostcodeGeometry = {
|
|
type: 'Polygon',
|
|
coordinates: [
|
|
[
|
|
[-0.12, 51.5],
|
|
[-0.11, 51.5],
|
|
[-0.11, 51.51],
|
|
[-0.12, 51.51],
|
|
[-0.12, 51.5],
|
|
],
|
|
],
|
|
};
|
|
|
|
function stats(count: number): HexagonStatsResponse {
|
|
return {
|
|
count,
|
|
numeric_features: [],
|
|
enum_features: [],
|
|
central_postcode: 'SW1A 1AA',
|
|
};
|
|
}
|
|
|
|
function jsonResponse(body: unknown): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
describe('useHexagonSelection', () => {
|
|
const requests: string[] = [];
|
|
const features: FeatureMeta[] = [{ name: 'Price', type: 'numeric', min: 0, max: 100 }];
|
|
|
|
beforeEach(() => {
|
|
requests.length = 0;
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn((input: string | URL | Request) => {
|
|
const url = new URL(String(input), 'http://localhost');
|
|
requests.push(`${url.pathname}${url.search}`);
|
|
|
|
if (url.pathname === '/api/postcode-stats') {
|
|
const emptyPostcode = url.searchParams.get('postcode') === 'EMPTY 1AA';
|
|
return Promise.resolve(
|
|
jsonResponse(stats(url.searchParams.has('filters') || emptyPostcode ? 0 : 4))
|
|
);
|
|
}
|
|
|
|
if (url.pathname === '/api/hexagon-stats') {
|
|
return Promise.resolve(jsonResponse(stats(12)));
|
|
}
|
|
|
|
return Promise.resolve(new Response(null, { status: 404 }));
|
|
})
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it('keeps a postcode search selected when filters exclude its properties', async () => {
|
|
const { result } = renderHook(() =>
|
|
useHexagonSelection({
|
|
filters: { Price: [0, 50] },
|
|
features,
|
|
hexagonData: [],
|
|
resolution: 9,
|
|
usePostcodeView: true,
|
|
travelTimeEntries: [],
|
|
})
|
|
);
|
|
|
|
act(() => {
|
|
result.current.handleLocationSearch('SW1A 1AA', postcodeGeometry, 51.505, -0.115);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.selectedHexagon).toEqual({
|
|
id: 'SW1A 1AA',
|
|
type: 'postcode',
|
|
resolution: 9,
|
|
lockedResolution: true,
|
|
});
|
|
});
|
|
|
|
expect(result.current.selectedPostcodeGeometry).toBe(postcodeGeometry);
|
|
await waitFor(() => {
|
|
expect(result.current.areaStats?.count).toBe(0);
|
|
expect(result.current.unfilteredAreaCount).toBe(4);
|
|
});
|
|
expect(requests.some((url) => url.startsWith('/api/hexagon-stats'))).toBe(false);
|
|
});
|
|
|
|
it('keeps a postcode search selected when stats are based on all properties', async () => {
|
|
const { result } = renderHook(() =>
|
|
useHexagonSelection({
|
|
filters: { Price: [0, 50] },
|
|
features,
|
|
hexagonData: [],
|
|
resolution: 9,
|
|
usePostcodeView: true,
|
|
travelTimeEntries: [],
|
|
})
|
|
);
|
|
|
|
act(() => {
|
|
result.current.setAreaStatsUseFilters(false);
|
|
});
|
|
act(() => {
|
|
result.current.handleLocationSearch('SW1A 1AA', postcodeGeometry, 51.505, -0.115);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.selectedHexagon).toEqual({
|
|
id: 'SW1A 1AA',
|
|
type: 'postcode',
|
|
resolution: 9,
|
|
lockedResolution: true,
|
|
});
|
|
});
|
|
|
|
expect(result.current.areaStats?.count).toBe(4);
|
|
expect(result.current.unfilteredAreaCount).toBeNull();
|
|
expect(requests.some((url) => url.startsWith('/api/hexagon-stats'))).toBe(false);
|
|
});
|
|
|
|
it('keeps an empty postcode search selected instead of widening to hexagons', async () => {
|
|
const { result } = renderHook(() =>
|
|
useHexagonSelection({
|
|
filters: {},
|
|
features,
|
|
hexagonData: [],
|
|
resolution: 9,
|
|
usePostcodeView: true,
|
|
travelTimeEntries: [],
|
|
})
|
|
);
|
|
|
|
act(() => {
|
|
result.current.handleLocationSearch('EMPTY 1AA', postcodeGeometry, 51.505, -0.115);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.areaStats?.count).toBe(0);
|
|
});
|
|
|
|
expect(result.current.selectedHexagon).toEqual({
|
|
id: 'EMPTY 1AA',
|
|
type: 'postcode',
|
|
resolution: 9,
|
|
lockedResolution: true,
|
|
});
|
|
expect(result.current.selectedPostcodeGeometry).toBe(postcodeGeometry);
|
|
expect(requests.some((url) => url.startsWith('/api/hexagon-stats'))).toBe(false);
|
|
});
|
|
|
|
it('does not convert a searched postcode back to a hexagon while the map reaches postcode zoom', async () => {
|
|
const { result } = renderHook(() =>
|
|
useHexagonSelection({
|
|
filters: {},
|
|
features,
|
|
hexagonData: [],
|
|
resolution: 9,
|
|
usePostcodeView: false,
|
|
travelTimeEntries: [],
|
|
})
|
|
);
|
|
|
|
act(() => {
|
|
result.current.handleLocationSearch('SW1A 1AA', postcodeGeometry, 51.505, -0.115);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.areaStats?.count).toBe(4);
|
|
});
|
|
|
|
expect(result.current.selectedHexagon).toEqual({
|
|
id: 'SW1A 1AA',
|
|
type: 'postcode',
|
|
resolution: 9,
|
|
lockedResolution: true,
|
|
});
|
|
expect(result.current.selectedPostcodeGeometry).toBe(postcodeGeometry);
|
|
expect(requests.some((url) => url.startsWith('/api/postcode/'))).toBe(false);
|
|
expect(requests.some((url) => url.startsWith('/api/hexagon-stats'))).toBe(false);
|
|
});
|
|
});
|