292 lines
8.6 KiB
TypeScript
292 lines
8.6 KiB
TypeScript
import { beforeEach, describe, expect, it } from 'vitest';
|
|
|
|
import type { FeatureMeta } from '../types';
|
|
import { parseUrlState, stateToParams } from './url-state';
|
|
import { INITIAL_VIEW_STATE } from './consts';
|
|
import { createSchoolFilterKey } from './school-filter';
|
|
import { createSpecificCrimeFilterKey } from './crime-filter';
|
|
import { createElectionVoteShareFilterKey } from './election-filter';
|
|
import { createEthnicityFilterKey } from './ethnicity-filter';
|
|
import {
|
|
POI_COUNT_2KM_FILTER_NAME,
|
|
createPoiDistanceFilterKey,
|
|
createPoiFilterKey,
|
|
} from './poi-distance-filter';
|
|
|
|
describe('url-state', () => {
|
|
beforeEach(() => {
|
|
window.history.replaceState({}, '', '/');
|
|
});
|
|
|
|
it('parses view, filters, POIs, tab, postcode, and travel-time params', () => {
|
|
window.history.replaceState(
|
|
{},
|
|
'',
|
|
'/?lat=51.5074&lon=-0.1278&zoom=12.5&filter=Last%20known%20price:100000:500000&filter=Property%20type:Flat|House&poi=supermarket&tab=properties&pc=SW1A%201AA&tt=transit:kings-cross:Kings%20Cross:b:0:30'
|
|
);
|
|
|
|
const state = parseUrlState();
|
|
|
|
expect(state.viewState).toEqual({
|
|
latitude: 51.5074,
|
|
longitude: -0.1278,
|
|
zoom: 12.5,
|
|
pitch: 0,
|
|
});
|
|
expect(state.filters).toEqual({
|
|
'Last known price': [100000, 500000],
|
|
'Property type': ['Flat', 'House'],
|
|
});
|
|
expect(state.poiCategories).toEqual(new Set(['supermarket']));
|
|
expect(state.tab).toBe('properties');
|
|
expect(state.postcode).toBe('SW1A 1AA');
|
|
expect(state.travelTime?.entries).toEqual([
|
|
{
|
|
mode: 'transit',
|
|
slug: 'kings-cross',
|
|
label: 'Kings Cross',
|
|
timeRange: [0, 30],
|
|
useBest: true,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('leaves POIs unselected when URL params are omitted', () => {
|
|
const state = parseUrlState();
|
|
|
|
expect(state.viewState).toEqual(INITIAL_VIEW_STATE);
|
|
expect(state.filters).toEqual({});
|
|
expect(state.poiCategories).toEqual(new Set());
|
|
expect(state.tab).toBe('area');
|
|
});
|
|
|
|
it('serializes map state and active filters into stable URL params', () => {
|
|
const features: FeatureMeta[] = [
|
|
{ name: 'Last known price', type: 'numeric' },
|
|
{ name: 'Property type', type: 'enum', values: ['Flat', 'House'] },
|
|
];
|
|
|
|
const params = stateToParams(
|
|
{ latitude: 51.50742, longitude: -0.12781, zoom: 12.47 },
|
|
{
|
|
'Last known price': [100000, 500000],
|
|
'Property type': ['Flat', 'House'],
|
|
},
|
|
features,
|
|
new Set(['supermarket']),
|
|
'properties',
|
|
[
|
|
{
|
|
mode: 'bicycle',
|
|
slug: 'bank',
|
|
label: 'Bank',
|
|
useBest: false,
|
|
timeRange: [5, 25],
|
|
},
|
|
]
|
|
);
|
|
|
|
expect(params.get('lat')).toBe('51.5074');
|
|
expect(params.get('lon')).toBe('-0.1278');
|
|
expect(params.get('zoom')).toBe('12.5');
|
|
expect(params.getAll('filter')).toEqual([
|
|
'Last known price:100000:500000',
|
|
'Property type:Flat|House',
|
|
]);
|
|
expect(params.getAll('poi')).toEqual(['supermarket']);
|
|
expect(params.get('tab')).toBe('properties');
|
|
expect(params.getAll('tt')).toEqual(['bicycle:bank:Bank:5:25']);
|
|
});
|
|
|
|
it('round-trips an explicitly empty POI selection', () => {
|
|
const params = stateToParams(null, {}, [], new Set(), 'area');
|
|
|
|
expect(params.getAll('poi')).toEqual(['__none']);
|
|
|
|
window.history.replaceState({}, '', `/?${params.toString()}`);
|
|
const state = parseUrlState();
|
|
|
|
expect(state.poiCategories).toEqual(new Set());
|
|
});
|
|
|
|
it('round-trips repeated school filters with dedicated URL params', () => {
|
|
const schoolOne = createSchoolFilterKey('primary', 'good', 2, 1);
|
|
const schoolTwo = createSchoolFilterKey('secondary', 'outstanding', 5, 2);
|
|
|
|
const params = stateToParams(
|
|
null,
|
|
{
|
|
[schoolOne]: [1, 10],
|
|
[schoolTwo]: [2, 15],
|
|
},
|
|
[],
|
|
new Set(),
|
|
'area'
|
|
);
|
|
|
|
expect(params.getAll('school')).toEqual([
|
|
'primary:good:2:1:10',
|
|
'secondary:outstanding:5:2:15',
|
|
]);
|
|
expect(params.getAll('filter')).toEqual([]);
|
|
|
|
window.history.replaceState({}, '', `/?${params.toString()}`);
|
|
const state = parseUrlState();
|
|
|
|
expect(state.filters).toEqual({
|
|
[createSchoolFilterKey('primary', 'good', 2, 0)]: [1, 10],
|
|
[createSchoolFilterKey('secondary', 'outstanding', 5, 1)]: [2, 15],
|
|
});
|
|
});
|
|
|
|
it('round-trips repeated specific crime filters with dedicated URL params', () => {
|
|
const burglary = createSpecificCrimeFilterKey('Burglary (avg/yr)', 1);
|
|
const vehicleCrime = createSpecificCrimeFilterKey('Vehicle crime (avg/yr)', 2);
|
|
|
|
const params = stateToParams(
|
|
null,
|
|
{
|
|
[burglary]: [0, 5],
|
|
[vehicleCrime]: [1, 10],
|
|
},
|
|
[],
|
|
new Set(),
|
|
'area'
|
|
);
|
|
|
|
expect(params.getAll('crime')).toEqual([
|
|
'Burglary (avg/yr):0:5',
|
|
'Vehicle crime (avg/yr):1:10',
|
|
]);
|
|
expect(params.getAll('filter')).toEqual([]);
|
|
|
|
window.history.replaceState({}, '', `/?${params.toString()}`);
|
|
const state = parseUrlState();
|
|
|
|
expect(state.filters).toEqual({
|
|
[createSpecificCrimeFilterKey('Burglary (avg/yr)', 0)]: [0, 5],
|
|
[createSpecificCrimeFilterKey('Vehicle crime (avg/yr)', 1)]: [1, 10],
|
|
});
|
|
});
|
|
|
|
it('round-trips repeated election vote-share filters with dedicated URL params', () => {
|
|
const labour = createElectionVoteShareFilterKey('% Labour', 1);
|
|
const conservative = createElectionVoteShareFilterKey('% Conservative', 2);
|
|
|
|
const params = stateToParams(
|
|
null,
|
|
{
|
|
[labour]: [30, 55],
|
|
[conservative]: [10, 35],
|
|
},
|
|
[],
|
|
new Set(),
|
|
'area'
|
|
);
|
|
|
|
expect(params.getAll('voteShare')).toEqual(['% Labour:30:55', '% Conservative:10:35']);
|
|
expect(params.getAll('filter')).toEqual([]);
|
|
|
|
window.history.replaceState({}, '', `/?${params.toString()}`);
|
|
const state = parseUrlState();
|
|
|
|
expect(state.filters).toEqual({
|
|
[createElectionVoteShareFilterKey('% Labour', 0)]: [30, 55],
|
|
[createElectionVoteShareFilterKey('% Conservative', 1)]: [10, 35],
|
|
});
|
|
});
|
|
|
|
it('round-trips repeated ethnicity filters with dedicated URL params', () => {
|
|
const white = createEthnicityFilterKey('% White', 3);
|
|
const southAsian = createEthnicityFilterKey('% South Asian', 4);
|
|
|
|
const params = stateToParams(
|
|
null,
|
|
{
|
|
[white]: [10, 80],
|
|
[southAsian]: [5, 35],
|
|
},
|
|
[],
|
|
new Set(),
|
|
'area'
|
|
);
|
|
|
|
expect(params.getAll('ethnicity')).toEqual(['% White:10:80', '% South Asian:5:35']);
|
|
expect(params.getAll('filter')).toEqual([]);
|
|
|
|
window.history.replaceState({}, '', `/?${params.toString()}`);
|
|
const state = parseUrlState();
|
|
|
|
expect(state.filters).toEqual({
|
|
[createEthnicityFilterKey('% White', 0)]: [10, 80],
|
|
[createEthnicityFilterKey('% South Asian', 1)]: [5, 35],
|
|
});
|
|
});
|
|
|
|
it('round-trips repeated amenity distance filters with dedicated URL params', () => {
|
|
const park = createPoiDistanceFilterKey('Distance to nearest park (km)', 3);
|
|
const tesco = createPoiDistanceFilterKey('Distance to nearest Tesco (km)', 4);
|
|
|
|
const params = stateToParams(
|
|
null,
|
|
{
|
|
[park]: [0, 0.4],
|
|
[tesco]: [0, 1.5],
|
|
},
|
|
[],
|
|
new Set(),
|
|
'area'
|
|
);
|
|
|
|
expect(params.getAll('amenityDistance')).toEqual([
|
|
'Distance%20to%20nearest%20park%20(km):0:0.4',
|
|
'Distance%20to%20nearest%20Tesco%20(km):0:1.5',
|
|
]);
|
|
expect(params.getAll('filter')).toEqual([]);
|
|
|
|
window.history.replaceState({}, '', `/?${params.toString()}`);
|
|
const state = parseUrlState();
|
|
|
|
expect(state.filters).toEqual({
|
|
[createPoiDistanceFilterKey('Distance to nearest park (km)', 0)]: [0, 0.4],
|
|
[createPoiDistanceFilterKey('Distance to nearest Tesco (km)', 1)]: [0, 1.5],
|
|
});
|
|
});
|
|
|
|
it('round-trips amenity count filters with dedicated URL params', () => {
|
|
const cafes = createPoiFilterKey(
|
|
POI_COUNT_2KM_FILTER_NAME,
|
|
'Number of amenities (Cafe) within 2km',
|
|
3
|
|
);
|
|
|
|
const params = stateToParams(
|
|
null,
|
|
{
|
|
[cafes]: [2, 8],
|
|
},
|
|
[],
|
|
new Set(),
|
|
'area'
|
|
);
|
|
|
|
expect(params.getAll('amenityCount2km')).toEqual([
|
|
'Number%20of%20amenities%20(Cafe)%20within%202km:2:8',
|
|
]);
|
|
expect(params.getAll('filter')).toEqual([]);
|
|
|
|
window.history.replaceState({}, '', `/?${params.toString()}`);
|
|
const state = parseUrlState();
|
|
|
|
expect(state.filters).toEqual({
|
|
[createPoiFilterKey(POI_COUNT_2KM_FILTER_NAME, 'Number of amenities (Cafe) within 2km', 0)]:
|
|
[2, 8],
|
|
});
|
|
});
|
|
|
|
it('omits the default area tab', () => {
|
|
const params = stateToParams(null, {}, [], new Set(), 'area');
|
|
|
|
expect(params.has('tab')).toBe(false);
|
|
});
|
|
});
|