fix(search): start postcode searches at half a mile
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 21s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 16s

New postcode searches, the near-me button and URLs without a radius now
use 0.5 miles. A postcode URL with no radius used to show "1 mile" in the
Distance control while the API applied its own 5-mile default; the page
and the map fetch now send the same default the control displays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
TudorandClaude Opus 5.5 committed 2026-10-01 17:40:44 +01:00
1 parent e9886361d2
commit 96deab7d58
6 files changed
+57 -6

No files matched your search

+3
View File
@@ -270,6 +270,9 @@ test('searching by name returns school results', async ({ page }) => {
test('searching by postcode returns nearby schools', async ({ page }) => { test('searching by postcode returns nearby schools', async ({ page }) => {
await searchByName(page, 'B1 1BB'); await searchByName(page, 'B1 1BB');
await expect(schoolLinks(page).first()).toBeVisible({ timeout: 15_000 }); await expect(schoolLinks(page).first()).toBeVisible({ timeout: 15_000 });
// A new postcode search starts at half a mile.
await expect(page).toHaveURL(/[?&]radius=0\.5(&|$)/);
await expect(page.getByRole('combobox', { name: 'Distance' })).toHaveValue('0.5');
}); });
test('the phase filter switches straight from secondary to primary', async ({ page }) => { test('the phase filter switches straight from secondary to primary', async ({ page }) => {
@@ -0,0 +1,41 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FilterBar } from '@/components/FilterBar';
const push = jest.fn();
let searchParams = new URLSearchParams();
jest.mock('next/navigation', () => ({
useRouter: () => ({ push, replace: jest.fn(), prefetch: jest.fn() }),
usePathname: () => '/',
useSearchParams: () => searchParams,
}));
const FILTERS = {
local_authorities: [], school_types: [], years: [], phases: [],
genders: [], admissions_policies: [],
};
beforeEach(() => {
push.mockClear();
searchParams = new URLSearchParams();
});
describe('FilterBar default distance', () => {
it('searches a new postcode within half a mile', async () => {
render(<FilterBar filters={FILTERS} />);
await userEvent.type(screen.getByPlaceholderText(/School name or postcode/i), 'SW19 6AR{Enter}');
await waitFor(() => expect(push).toHaveBeenCalledWith(expect.stringContaining('radius=0.5')));
});
it('shows half a mile when the URL carries a postcode but no radius', () => {
searchParams = new URLSearchParams('postcode=SW196AR');
render(<FilterBar filters={FILTERS} />);
expect(screen.getByRole('combobox', { name: 'Distance' })).toHaveValue('0.5');
});
it('keeps a distance the user already chose', async () => {
searchParams = new URLSearchParams('postcode=SW196AR&radius=3');
render(<FilterBar filters={FILTERS} />);
expect(screen.getByRole('combobox', { name: 'Distance' })).toHaveValue('3');
});
});
+4 -2
View File
@@ -6,7 +6,7 @@
import { absoluteUrl } from '@/lib/site'; import { absoluteUrl } from '@/lib/site';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { fetchSchools, fetchFilters, fetchDataInfo } from '@/lib/api'; import { fetchSchools, fetchFilters, fetchDataInfo } from '@/lib/api';
import { formatAcademicYear } from '@/lib/utils'; import { DEFAULT_RADIUS_MILES, formatAcademicYear } from '@/lib/utils';
import { HomeView } from '@/components/HomeView'; import { HomeView } from '@/components/HomeView';
import { getFlags } from '@/lib/flags'; import { getFlags } from '@/lib/flags';
import { HowItWorksSection } from '@/components/HowItWorksSection'; import { HowItWorksSection } from '@/components/HowItWorksSection';
@@ -71,7 +71,9 @@ export default async function HomePage({ searchParams }: HomePageProps) {
// Parse search params // Parse search params
const page = parseInt(params.page || '1'); const page = parseInt(params.page || '1');
const radius = params.radius ? parseFloat(params.radius) : undefined; // Without this the API applies its own 5-mile default while the Distance
// control shows DEFAULT_RADIUS_MILES.
const radius = params.radius ? parseFloat(params.radius) : DEFAULT_RADIUS_MILES;
// Check if user has performed a search // Check if user has performed a search
const hasSearchParams = !!( const hasSearchParams = !!(
+3 -3
View File
@@ -3,7 +3,7 @@
import { useState, useCallback, useTransition, useRef, useEffect } from "react"; import { useState, useCallback, useTransition, useRef, useEffect } from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useRouter, useSearchParams, usePathname } from "next/navigation"; import { useRouter, useSearchParams, usePathname } from "next/navigation";
import { isValidPostcode, schoolUrl } from "@/lib/utils"; import { DEFAULT_RADIUS_MILES, isValidPostcode, schoolUrl } from "@/lib/utils";
import { track } from "@/lib/analytics"; import { track } from "@/lib/analytics";
import { useSchoolSuggest } from "@/hooks/useSchoolSuggest"; import { useSchoolSuggest } from "@/hooks/useSchoolSuggest";
import { SuggestList, suggestOptionId } from "./SuggestList"; import { SuggestList, suggestOptionId } from "./SuggestList";
@@ -70,7 +70,7 @@ export function FilterBar({
const currentSearch = searchParams.get("search") || ""; const currentSearch = searchParams.get("search") || "";
const currentPostcode = searchParams.get("postcode") || ""; const currentPostcode = searchParams.get("postcode") || "";
const currentRadius = searchParams.get("radius") || "1"; const currentRadius = searchParams.get("radius") || String(DEFAULT_RADIUS_MILES);
const initialOmniValue = currentPostcode || currentSearch; const initialOmniValue = currentPostcode || currentSearch;
const [omniValue, setOmniValue] = useState(initialOmniValue); const [omniValue, setOmniValue] = useState(initialOmniValue);
@@ -269,7 +269,7 @@ export function FilterBar({
if (isPostcode) { if (isPostcode) {
updateURL({ updateURL({
postcode: cleaned.toUpperCase(), postcode: cleaned.toUpperCase(),
radius: currentRadius || "1", radius: currentRadius,
search: "", search: "",
}); });
} else { } else {
+3 -1
View File
@@ -17,6 +17,7 @@ import { useComparisonContext } from '@/context/ComparisonContext';
import { fetchSchools, fetchLAaverages, fetchNationalAverages } from '@/lib/api'; import { fetchSchools, fetchLAaverages, fetchNationalAverages } from '@/lib/api';
import type { SchoolsResponse, Filters, School } from '@/lib/types'; import type { SchoolsResponse, Filters, School } from '@/lib/types';
import { track } from '@/lib/analytics'; import { track } from '@/lib/analytics';
import { DEFAULT_RADIUS_MILES } from '@/lib/utils';
import styles from './HomeView.module.css'; import styles from './HomeView.module.css';
interface HomeViewProps { interface HomeViewProps {
@@ -340,6 +341,7 @@ export function HomeView({ initialSchools, filters, totalSchools, howItWorks, ed
setIsLoadingMap(true); setIsLoadingMap(true);
const params: Record<string, any> = {}; const params: Record<string, any> = {};
searchParams.forEach((value, key) => { params[key] = value; }); searchParams.forEach((value, key) => { params[key] = value; });
params.radius ??= DEFAULT_RADIUS_MILES;
params.page = 1; params.page = 1;
params.page_size = 500; params.page_size = 500;
fetchSchools(params, { cache: 'no-store', signal: controller.signal }) fetchSchools(params, { cache: 'no-store', signal: controller.signal })
@@ -419,7 +421,7 @@ export function HomeView({ initialSchools, filters, totalSchools, howItWorks, ed
setGeoState('idle'); setGeoState('idle');
track('near_me_used', { outcome: 'granted' }); track('near_me_used', { outcome: 'granted' });
track('search_submitted', { query: postcode, via: 'near_me', has_postcode: true, filters_active: '', filters_count: 0 }); track('search_submitted', { query: postcode, via: 'near_me', has_postcode: true, filters_active: '', filters_count: 0 });
router.push(`/?postcode=${encodeURIComponent(postcode)}&radius=1`); router.push(`/?postcode=${encodeURIComponent(postcode)}&radius=${DEFAULT_RADIUS_MILES}`);
} else { } else {
track('near_me_used', { outcome: 'no_postcode' }); track('near_me_used', { outcome: 'no_postcode' });
setGeoState('error'); setGeoState('error');
+3
View File
@@ -235,6 +235,9 @@ export function getVariabilityLabel(stdDev: number | null): string {
// Validation // Validation
// ============================================================================ // ============================================================================
/** Radius a postcode search uses until the user picks another. */
export const DEFAULT_RADIUS_MILES = 0.5;
/** /**
* Validate UK postcode format * Validate UK postcode format
*/ */