This commit is contained in:
Andras Schmelczer 2026-05-13 08:00:12 +01:00
parent 63713c3a2b
commit bd6b511f16
17 changed files with 544 additions and 377 deletions

View file

@ -126,11 +126,7 @@ const DS_KEYS: Record<string, [string, string, string]> = {
'learnPage.dsGreenspaceOrigin',
'learnPage.dsGreenspaceUse',
],
'forest-research-tow': [
'learnPage.dsTowName',
'learnPage.dsTowOrigin',
'learnPage.dsTowUse',
],
'forest-research-tow': ['learnPage.dsTowName', 'learnPage.dsTowOrigin', 'learnPage.dsTowUse'],
naptan: ['learnPage.dsNaptanName', 'learnPage.dsNaptanOrigin', 'learnPage.dsNaptanUse'],
noise: ['learnPage.dsNoiseName', 'learnPage.dsNoiseOrigin', 'learnPage.dsNoiseUse'],
ofsted: ['learnPage.dsOfstedName', 'learnPage.dsOfstedOrigin', 'learnPage.dsOfstedUse'],

View file

@ -17,7 +17,7 @@ import { travelFieldKey, useTravelTime } from '../../hooks/useTravelTime';
import { apiUrl, authHeaders } from '../../lib/api';
import { useFilterCounts } from '../../hooks/useFilterCounts';
import { trackEvent } from '../../lib/analytics';
import { INITIAL_VIEW_STATE } from '../../lib/consts';
import { INITIAL_VIEW_STATE, POSTCODE_SEARCH_ZOOM } from '../../lib/consts';
import { useLicense } from '../../hooks/useLicense';
import {
AreaPane,
@ -51,6 +51,8 @@ import type { MapFlyTo, MapPageProps } from './map-page/types';
export type { ExportState } from './map-page/types';
type PendingFlyTo = { lat: number; lng: number; zoom: number };
export default function MapPage({
features,
poiCategoryGroups,
@ -150,6 +152,7 @@ export default function MapPage({
const mapFlyToRef = useRef<MapFlyTo | null>(null);
const pendingCurrentLocationFlyToRef = useRef<{ lat: number; lng: number } | null>(null);
const pendingLocationSearchFlyToRef = useRef<PendingFlyTo | null>(null);
const mobileDrawerPanelRectRef = useRef<DOMRectReadOnly | null>(null);
const mapData = useMapData({
@ -296,11 +299,27 @@ export default function MapPage({
journeyDest,
});
const consumePendingLocationSearchFlyTo = useCallback((rect?: DOMRectReadOnly | null) => {
const pending = pendingLocationSearchFlyToRef.current;
const panelRect = rect ?? mobileDrawerPanelRectRef.current;
if (!pending || !panelRect) return;
const bottomInset = Math.max(0, window.innerHeight - panelRect.top);
const flyTo = mapFlyToRef.current;
if (!flyTo) return;
flyTo(pending.lat, pending.lng, pending.zoom, {
visibleViewportArea: { bottom: bottomInset },
});
pendingLocationSearchFlyToRef.current = null;
}, []);
const handleLocationSearchResult = useCallback(
(result: SearchedLocation | null) => {
if (result) {
if (result.markerLatitude != null && result.markerLongitude != null) {
setCurrentLocation({ lat: result.markerLatitude, lng: result.markerLongitude });
const markerLat = result.markerLatitude;
const markerLng = result.markerLongitude;
if (markerLat != null && markerLng != null) {
setCurrentLocation({ lat: markerLat, lng: markerLng });
} else {
setCurrentLocation(null);
}
@ -312,13 +331,22 @@ export default function MapPage({
result.openProperties,
result.focusAddress
);
if (isMobile) setMobileDrawerOpen(true);
if (isMobile) {
pendingLocationSearchFlyToRef.current = {
lat: markerLat ?? result.latitude,
lng: markerLng ?? result.longitude,
zoom: result.openProperties ? 17 : POSTCODE_SEARCH_ZOOM,
};
setMobileDrawerOpen(true);
consumePendingLocationSearchFlyTo();
}
} else {
setCurrentLocation(null);
pendingLocationSearchFlyToRef.current = null;
handleCloseSelection();
}
},
[handleCloseSelection, handleLocationSearch, isMobile]
[consumePendingLocationSearchFlyTo, handleCloseSelection, handleLocationSearch, isMobile]
);
const consumePendingCurrentLocationFlyTo = useCallback((rect?: DOMRectReadOnly | null) => {
@ -327,7 +355,9 @@ export default function MapPage({
if (!pending || !panelRect) return;
const bottomInset = Math.max(0, window.innerHeight - panelRect.top);
mapFlyToRef.current?.(pending.lat, pending.lng, 17, {
const flyTo = mapFlyToRef.current;
if (!flyTo) return;
flyTo(pending.lat, pending.lng, 17, {
visibleViewportArea: { bottom: bottomInset },
});
pendingCurrentLocationFlyToRef.current = null;
@ -352,12 +382,14 @@ export default function MapPage({
(rect: DOMRectReadOnly) => {
mobileDrawerPanelRectRef.current = rect;
consumePendingCurrentLocationFlyTo(rect);
consumePendingLocationSearchFlyTo(rect);
},
[consumePendingCurrentLocationFlyTo]
[consumePendingCurrentLocationFlyTo, consumePendingLocationSearchFlyTo]
);
const handleMobileDrawerClose = useCallback(() => {
pendingCurrentLocationFlyToRef.current = null;
pendingLocationSearchFlyToRef.current = null;
mobileDrawerPanelRectRef.current = null;
setMobileDrawerOpen(false);
}, []);
@ -387,7 +419,11 @@ export default function MapPage({
isMobile,
flyTo: mapFlyToRef,
onLocationSearch: handleLocationSearch,
onOpenMobileDrawer: () => setMobileDrawerOpen(true),
onOpenMobileDrawer: (target) => {
pendingLocationSearchFlyToRef.current = target;
setMobileDrawerOpen(true);
consumePendingLocationSearchFlyTo();
},
});
useHorizontalSwipeNavigationGuard();
useMobileBackNavigationGuard(isMobile);

View file

@ -4,6 +4,7 @@ import type { MutableRefObject } from 'react';
import type { PostcodeGeometry, ViewState } from '../../../types';
import type { useMapData } from '../../../hooks/useMapData';
import { authHeaders } from '../../../lib/api';
import { POSTCODE_SEARCH_ZOOM } from '../../../lib/consts';
import { canWheelScrollInsideTarget } from '../../../lib/dom-scroll';
import type { MapFlyTo } from './types';
@ -32,7 +33,7 @@ interface UseInitialPostcodeSelectionOptions {
lat?: number,
lng?: number
) => void;
onOpenMobileDrawer: () => void;
onOpenMobileDrawer: (target: { lat: number; lng: number; zoom: number }) => void;
}
export function useInitialPostcodeSelection({
@ -62,9 +63,15 @@ export function useInitialPostcodeSelection({
longitude: number;
geometry: PostcodeGeometry;
}) => {
flyTo.current?.(data.latitude, data.longitude, 16);
flyTo.current?.(data.latitude, data.longitude, POSTCODE_SEARCH_ZOOM);
onLocationSearch(data.postcode, data.geometry, data.latitude, data.longitude);
if (isMobile) onOpenMobileDrawer();
if (isMobile) {
onOpenMobileDrawer({
lat: data.latitude,
lng: data.longitude,
zoom: POSTCODE_SEARCH_ZOOM,
});
}
}
)
.catch(() => {

View file

@ -54,7 +54,10 @@ describe('useHexagonSelection', () => {
requests.push(`${url.pathname}${url.search}`);
if (url.pathname === '/api/postcode-stats') {
return Promise.resolve(jsonResponse(stats(url.searchParams.has('filters') ? 0 : 4)));
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') {
@ -91,12 +94,15 @@ describe('useHexagonSelection', () => {
id: 'SW1A 1AA',
type: 'postcode',
resolution: 9,
lockedResolution: true,
});
});
expect(result.current.selectedPostcodeGeometry).toBe(postcodeGeometry);
expect(result.current.areaStats?.count).toBe(0);
expect(result.current.unfilteredAreaCount).toBe(4);
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);
});
@ -124,6 +130,7 @@ describe('useHexagonSelection', () => {
id: 'SW1A 1AA',
type: 'postcode',
resolution: 9,
lockedResolution: true,
});
});
@ -131,4 +138,65 @@ describe('useHexagonSelection', () => {
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);
});
});

View file

@ -426,7 +426,7 @@ export function useHexagonSelection({
selection.type === 'hexagon' &&
!selection.lockedResolution &&
areaStats?.central_postcode != null) ||
(!usePostcodeView && selection.type === 'postcode') ||
(!usePostcodeView && selection.type === 'postcode' && !selection.lockedResolution) ||
(!usePostcodeView &&
selection.type === 'hexagon' &&
!selection.lockedResolution &&
@ -628,103 +628,38 @@ export function useHexagonSelection({
(
postcode: string,
geometry: PostcodeGeometry,
lat?: number,
lng?: number,
_lat?: number,
_lng?: number,
openProperties = false,
focusAddress?: string
) => {
const requestId = invalidateAreaRequests();
invalidatePropertyRequests();
const selection = {
id: postcode,
type: 'postcode' as const,
resolution,
lockedResolution: true,
};
trackEvent(openProperties ? 'Address Search' : 'Postcode Search');
setSelectedHexagon(selection);
setSelectedPostcodeGeometry(geometry);
setProperties([]);
setPropertiesTotal(0);
setPropertiesOffset(0);
setAreaStats(null);
setUnfilteredAreaCount(null);
setRightPaneTab(openProperties ? 'properties' : 'area');
setLoadingAreaStats(true);
// First try the postcode; if it only has no matches because of active filters,
// keep the searched postcode selected instead of widening to nearby hexagons.
fetchPostcodeStats(postcode, undefined, areaStatsUseFilters)
.then(async (stats) => {
.then((stats) => {
if (!isCurrentAreaRequest(requestId)) return;
if (stats.count > 0) {
const selection = { id: postcode, type: 'postcode' as const, resolution };
setSelectedHexagon(selection);
setSelectedPostcodeGeometry(geometry);
setAreaStats(stats);
refreshUnfilteredAreaCount(selection, stats.count, areaStatsUseFilters);
if (openProperties) {
fetchPostcodeProperties(postcode, 0, focusAddress);
}
return;
setAreaStats(stats);
refreshUnfilteredAreaCount(selection, stats.count, areaStatsUseFilters);
if (openProperties && stats.count > 0) {
fetchPostcodeProperties(postcode, 0, focusAddress);
}
if (areaStatsUseFilters && hasStatsFilters) {
const unfilteredStats = await fetchPostcodeStats(postcode, undefined, false);
if (!isCurrentAreaRequest(requestId)) return;
if (unfilteredStats.count > 0) {
const selection = { id: postcode, type: 'postcode' as const, resolution };
setSelectedHexagon(selection);
setSelectedPostcodeGeometry(geometry);
setAreaStats(stats);
setUnfilteredAreaCount(unfilteredStats.count);
setRightPaneTab(openProperties ? 'properties' : 'area');
return;
}
}
// No properties in this postcode — fall back to hexagons
if (lat == null || lng == null) {
// No coordinates available, show empty postcode anyway
const selection = { id: postcode, type: 'postcode' as const, resolution };
setSelectedHexagon(selection);
setSelectedPostcodeGeometry(geometry);
setAreaStats(stats);
refreshUnfilteredAreaCount(selection, stats.count, areaStatsUseFilters);
setRightPaneTab('area');
return;
}
// Try progressively coarser H3 resolutions until we find >1 property
const resolutions = [9, 8, 7, 6, 5];
for (const res of resolutions) {
const h3 = latLngToCell(lat, lng, res);
const hexStats = await fetchHexagonStats(
h3,
res,
undefined,
undefined,
areaStatsUseFilters
);
if (!isCurrentAreaRequest(requestId)) return;
if (hexStats.count > 1) {
const selection = { id: h3, type: 'hexagon' as const, resolution: res };
setSelectedHexagon(selection);
setSelectedPostcodeGeometry(null);
setAreaStats(hexStats);
refreshUnfilteredAreaCount(selection, hexStats.count, areaStatsUseFilters);
setRightPaneTab('area');
return;
}
}
// Even the coarsest hexagon has ≤1 property — show whatever the finest has
const h3 = latLngToCell(lat, lng, 9);
const fallbackStats = await fetchHexagonStats(
h3,
9,
undefined,
undefined,
areaStatsUseFilters
);
if (!isCurrentAreaRequest(requestId)) return;
const selection = { id: h3, type: 'hexagon' as const, resolution: 9 };
setSelectedHexagon(selection);
setSelectedPostcodeGeometry(null);
setAreaStats(fallbackStats);
refreshUnfilteredAreaCount(selection, fallbackStats.count, areaStatsUseFilters);
setRightPaneTab('area');
})
.catch((error) => logNonAbortError('Failed to fetch postcode stats', error))
.finally(() => {
@ -734,9 +669,7 @@ export function useHexagonSelection({
[
resolution,
areaStatsUseFilters,
hasStatsFilters,
fetchPostcodeStats,
fetchHexagonStats,
fetchPostcodeProperties,
invalidateAreaRequests,
invalidatePropertyRequests,

View file

@ -413,15 +413,14 @@ const en = {
heroEyebrow: 'Find where to look first',
heroTitle1: 'Stop searching',
heroTitle2: 'the wrong places',
heroTitle3: 'Before listings take over.',
heroSubtitle:
'Find postcodes where your budget, commute, and daily life line up.',
heroTitle3: 'Before listings narrow your search.',
heroSubtitle: 'Find postcodes where your budget, commute, and daily life line up.',
heroDescription:
'Perfect Postcode shows where to look before you start chasing viewings.',
'Perfect Postcode filters every postcode first, so you only chase viewings in places that work.',
exploreTheMap: 'Show me where to look',
seeTheDifference: 'Watch demo',
productDemoLabel: 'Watch the postcode shortlist demo',
playProductDemo: 'Play the postcode shortlist demo',
productDemoLabel: 'See how to find where to look first',
playProductDemo: 'Play the where-to-look demo',
scrollToProductDemo: 'Scroll to product demo',
showcaseHeader: 'How it works',
showcaseContext: 'How Perfect Postcode works',
@ -446,26 +445,26 @@ const en = {
showcaseScoutBullet2: 'Test the commute from a real front door, not a borough name.',
showcaseScoutBullet3: 'Compare viewings with evidence already saved.',
showcaseStep1Tab: 'Filter',
showcaseStep1Title: 'Turn your needs into clear search filters',
showcaseStep1Title: 'Set what has to work',
showcaseStep1Body:
'Set what matters and see how many unsuitable postcodes each requirement removes.',
'Add budget, commute, schools, safety, noise, and local details. Watch the wrong postcodes drop out.',
showcaseStep1Chip1: 'Quiet streets',
showcaseStep1Chip2: 'Good primaries nearby',
showcaseStep1Chip3: 'Under £500k',
showcaseStep1VennCenter: 'Postcodes that meet all three',
showcaseStep2Tab: 'Match',
showcaseStep2Title: 'Find places you would never have known to search',
showcaseStep2Title: 'See the places left standing',
showcaseStep2Body:
'Search by what you need, not by area name. The map shows suitable postcode clusters before listing sites narrow the search.',
'Search by practical checks, not familiar names. The map shows postcode clusters worth checking first.',
showcaseStep2Region: 'Greater London',
showcaseStep2Sources: 'Land Registry · ONS · Ofsted · DfT',
showcaseStep2ClustersLabel: 'Matching clusters',
showcaseStep3Tab: 'Inspect',
showcaseStep3Title: 'See why a postcode matches',
showcaseStep3Title: 'Check the evidence',
showcaseStep3Body:
'Open any matching area and check prices, safety, schools, broadband, and trade-offs in one pane before you spend a weekend there.',
'Open a postcode and see the price, commute, schools, crime, broadband, and trade-offs before you visit.',
showcaseStep3HeaderArea: 'Shortlisted postcode',
showcaseStep3HeaderFit: 'Why it matches',
showcaseStep3HeaderFit: 'What works',
showcaseStep3Stat1Label: 'Sold price trend',
showcaseStep3Stat2Label: 'Crime rate',
showcaseStep3Stat2Value: 'Below borough avg.',
@ -475,9 +474,9 @@ const en = {
showcaseStep3Stat5Label: 'Primary schools',
showcaseStep3Stat5Value: '3 Outstanding within 1 mile',
showcaseStep4Tab: 'Scout',
showcaseStep4Title: 'Take the strongest areas into the real world',
showcaseStep4Title: 'Take the shortlist to the streets',
showcaseStep4Body:
'Export suggested postcodes to visit. Walk the streets, test the commute, and compare viewings with the data you saved.',
'Export the postcodes worth checking, test the commute, walk the roads, and compare viewings with context saved.',
showcaseStep4FileName: 'areas-to-scout.xlsx',
showcaseStep4ExportLabel: 'Export to Excel',
showcaseStep4ColPostcode: 'Postcode',
@ -489,20 +488,19 @@ const en = {
statFilters: 'ways to narrow the map',
statEvery: 'Every',
statPostcodeInEngland: 'active postcode in England',
ourPhilosophy: 'Start with needs. End with postcodes.',
ourPhilosophy: 'Stop starting with towns you already know.',
philosophyP1:
'Listing sites force you to pick a town, borough, or postcode before you know which places can work. That means the search is limited by memory, recommendations, and whatever happens to be for sale this week.',
'Most searches start with a place name, then hope the right homes appear. That skips the harder question: which places are actually worth searching?',
philosophyP2:
'Perfect Postcode starts with your requirements instead. Tell the map your budget, commute, school, safety, noise, broadband, and local-context needs, then inspect the postcodes that match before you open listings.',
'Perfect Postcode starts before the listing site. Set the things a place must support, then see the postcodes that deserve your attention first.',
streetTitle: 'Places change street by street',
streetIntro:
'Area names hide the details that matter: the station side, the road noise, the school mix, the exact commute, and what similar homes actually sold for.',
streetCard1Title: 'Find places you may have missed',
streetCard1Body:
'Search postcode-level data by your requirements instead of relying on familiar names, friend recommendations, or “up-and-coming” hype.',
streetCard2Title: 'Check the trade-offs before viewings',
'The right side of a station, a noisy road, or one school catchment can change the search. Area names flatten all of that.',
streetCard1Title: 'Escape the familiar-name trap',
streetCard1Body: 'Find postcode-level matches outside the places already on your list.',
streetCard2Title: 'Know the trade-offs before you go',
streetCard2Body:
'Compare price, space, commute, safety, schools, broadband, noise, energy ratings, parks, and local amenities before you spend weekends travelling between viewings.',
'Check price, commute, noise, schools, safety, broadband, and nearby amenities before booking viewings.',
othersVs: 'Other tools vs',
checkMyPostcode: 'Listing sites',
areaGuides: 'Postcode checkers',
@ -512,11 +510,11 @@ const en = {
compAreaDataSub: '(crime, schools, noise, broadband, amenities)',
compPropertyData: 'Street-level property context',
compPropertyDataSub: '(sold prices, EPC, floor area, estimated value)',
compFilters: 'All your requirements working together',
compFilters: 'Budget, commute, schools, safety, and local data together',
compFiltersSub: '(budget + commute + schools + safety + local context)',
ctaTitle: 'Do the area research before you book the viewing.',
ctaTitle: 'Find where to look before you book viewings.',
ctaDescription:
'Build a postcode shortlist from price, commute, schools, safety, noise, broadband, amenities, and sold-price evidence, then verify the streets in person.',
'Build a postcode shortlist from the things that matter, then check the streets in person.',
},
// ── Pricing Page ───────────────────────────────────

View file

@ -27,7 +27,10 @@ describe('poi-distance-filter', () => {
expect(
getPoiFilterFeatureOptions(features, POI_DISTANCE_FILTER_NAME).map((f) => f.name)
).toEqual(['Distance to nearest amenity (Cafe) (km)', 'Distance to nearest amenity (Park) (km)']);
).toEqual([
'Distance to nearest amenity (Cafe) (km)',
'Distance to nearest amenity (Park) (km)',
]);
expect(
getPoiFilterFeatureOptions(features, TRANSPORT_DISTANCE_FILTER_NAME).map((f) => f.name)
).toEqual([
@ -54,11 +57,4 @@ describe('poi-distance-filter', () => {
);
expect(getPoiFilterName('Number of amenities (Bus stop) within 2km')).toBeNull();
});
it('recognizes the old static park distance name for URL migration only', () => {
expect(getPoiFilterName('Distance to nearest park (km)')).toBe(POI_DISTANCE_FILTER_NAME);
expect(
getPoiFilterFeatureOptions([numeric('Distance to nearest park (km)')], POI_DISTANCE_FILTER_NAME)
).toEqual([]);
});
});

View file

@ -225,14 +225,14 @@ describe('url-state', () => {
});
it('round-trips repeated amenity distance filters with dedicated URL params', () => {
const park = createPoiDistanceFilterKey('Distance to nearest park (km)', 3);
const grocery = createPoiDistanceFilterKey('Distance to nearest grocery store (km)', 4);
const park = createPoiDistanceFilterKey('Distance to nearest amenity (Park) (km)', 3);
const cafe = createPoiDistanceFilterKey('Distance to nearest amenity (Café) (km)', 4);
const params = stateToParams(
null,
{
[park]: [0, 0.4],
[grocery]: [0, 1.5],
[cafe]: [0, 1.5],
},
[],
new Set(),
@ -240,8 +240,8 @@ describe('url-state', () => {
);
expect(params.getAll('amenityDistance')).toEqual([
'Distance%20to%20nearest%20park%20(km):0:0.4',
'Distance%20to%20nearest%20grocery%20store%20(km):0:1.5',
'Distance%20to%20nearest%20amenity%20(Park)%20(km):0:0.4',
'Distance%20to%20nearest%20amenity%20(Caf%C3%A9)%20(km):0:1.5',
]);
expect(params.getAll('filter')).toEqual([]);
@ -249,8 +249,8 @@ describe('url-state', () => {
const state = parseUrlState();
expect(state.filters).toEqual({
[createPoiDistanceFilterKey('Distance to nearest park (km)', 0)]: [0, 0.4],
[createPoiDistanceFilterKey('Distance to nearest grocery store (km)', 1)]: [0, 1.5],
[createPoiDistanceFilterKey('Distance to nearest amenity (Park) (km)', 0)]: [0, 0.4],
[createPoiDistanceFilterKey('Distance to nearest amenity (Café) (km)', 1)]: [0, 1.5],
});
});
@ -289,24 +289,6 @@ describe('url-state', () => {
});
});
it('migrates legacy transport distance amenity params into transport filters', () => {
window.history.replaceState(
{},
'',
'/?amenityDistance=Distance%20to%20nearest%20amenity%20(Bus%20stop)%20(km):0:0.3'
);
const state = parseUrlState();
expect(state.filters).toEqual({
[createPoiFilterKey(
TRANSPORT_DISTANCE_FILTER_NAME,
'Distance to nearest amenity (Bus stop) (km)',
0
)]: [0, 0.3],
});
});
it('round-trips amenity count filters with dedicated URL params', () => {
const cafes = createPoiFilterKey(
POI_COUNT_2KM_FILTER_NAME,