2026-08-15 22:48:30 +01:00
|
|
|
/**
|
|
|
|
|
* Last distance offered, rendered on both detail templates.
|
|
|
|
|
*
|
|
|
|
|
* The figure is the one number on these pages that a parent may act on — it is
|
|
|
|
|
* easy to read as "we live inside the catchment, we will get a place". These
|
|
|
|
|
* tests pin the things that stop it being read that way: the year is always
|
|
|
|
|
* present, the caveat is always present, and the blanket "not available"
|
|
|
|
|
* sentence appears only when it is actually true.
|
|
|
|
|
*/
|
|
|
|
|
|
2026-08-16 13:42:06 +01:00
|
|
|
import { screen, fireEvent } from '@testing-library/react';
|
2026-08-15 22:48:30 +01:00
|
|
|
import { renderSchoolDetail, renderSecondarySchoolDetail } from '../support/renderSchoolDetail';
|
|
|
|
|
import { primaryFixture, secondaryFixture } from '../support/schoolFixtures';
|
|
|
|
|
import type { SchoolAdmissionDistance } from '@/lib/types';
|
|
|
|
|
|
|
|
|
|
const cutoff = (over: Partial<SchoolAdmissionDistance> = {}): SchoolAdmissionDistance => ({
|
|
|
|
|
year: 2025,
|
|
|
|
|
distance_m: 500,
|
|
|
|
|
route_count: 1,
|
|
|
|
|
la_name: 'Camden',
|
|
|
|
|
distance_unit_raw: 'miles',
|
|
|
|
|
...over,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('primary detail page', () => {
|
|
|
|
|
it('shows the figure with the year it belongs to', () => {
|
|
|
|
|
renderSchoolDetail({ ...primaryFixture, admissionDistance: cutoff({ distance_m: 772.49, year: 2024 }) });
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('0.48 miles')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText(/Last distance offered/)).toHaveTextContent('September 2024');
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-20 21:01:28 +01:00
|
|
|
it('keeps the figure and its metric support readable as two numbers', () => {
|
|
|
|
|
// They are flex children with a CSS gap and nothing between them in the
|
|
|
|
|
// text layer, which read as "0.48 miles770 m" to a screen reader and to any
|
|
|
|
|
// text matcher. Cheap to lose again, so pinned.
|
|
|
|
|
renderSchoolDetail({ ...primaryFixture, admissionDistance: cutoff({ distance_m: 772.49 }) });
|
|
|
|
|
|
|
|
|
|
const tile = document.querySelector('[class*="admissionsTileDistance"]')!;
|
|
|
|
|
expect(tile.textContent).toMatch(/0\.48 miles\s+770 m/);
|
|
|
|
|
expect(tile.textContent).not.toMatch(/miles\d/);
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-15 22:48:30 +01:00
|
|
|
it('never shows the figure without saying it is not a catchment', () => {
|
|
|
|
|
renderSchoolDetail({ ...primaryFixture, admissionDistance: cutoff() });
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText(/not a fixed catchment/)).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText(/moves every year/)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('flags that a banded school\'s figure is the widest of several routes', () => {
|
|
|
|
|
renderSchoolDetail({ ...primaryFixture, admissionDistance: cutoff({ route_count: 4 }) });
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText(/4 admission routes/)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders nothing distance-related when the LA publishes none', () => {
|
|
|
|
|
renderSchoolDetail({ ...primaryFixture, admissionDistance: null });
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByText(/Last distance offered/)).not.toBeInTheDocument();
|
|
|
|
|
expect(screen.queryByText(/not a fixed catchment/)).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('carries the figure even with no EES admissions row', () => {
|
|
|
|
|
// The two sources are independent; this school has a cut-off and no
|
|
|
|
|
// admissions figures. Before this feature the section did not render at all.
|
|
|
|
|
renderSchoolDetail({
|
|
|
|
|
...primaryFixture,
|
|
|
|
|
admissions: null,
|
|
|
|
|
admissionsHistory: [],
|
|
|
|
|
admissionDistance: cutoff({ distance_m: 1421.05 }),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('0.88 miles')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('secondary detail page', () => {
|
|
|
|
|
it('shows the figure with the year it belongs to', () => {
|
|
|
|
|
renderSecondarySchoolDetail({ ...secondaryFixture, admissionDistance: cutoff({ distance_m: 3472.96 }) });
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText('2.16 miles')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText(/Last distance offered/)).toHaveTextContent('September 2025');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('drops the blanket "not available" line once a distance exists', () => {
|
|
|
|
|
renderSecondarySchoolDetail({ ...secondaryFixture, admissionDistance: cutoff() });
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByText(/has not published a cut-off distance/)).not.toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText(/not a fixed catchment/)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('names the authority that would hold the data when there is none', () => {
|
|
|
|
|
// The old copy asserted "Historical distance cut-off data is not available
|
|
|
|
|
// for this school" on every secondary page, including the ones whose
|
|
|
|
|
// council does publish it.
|
|
|
|
|
renderSecondarySchoolDetail({ ...secondaryFixture, admissionDistance: null });
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText(/has not published a cut-off distance/)).toBeInTheDocument();
|
|
|
|
|
});
|
2026-08-27 21:17:16 +01:00
|
|
|
|
|
|
|
|
it('makes no claim about publication when the feature is switched off', () => {
|
|
|
|
|
// Absent, not null. The API omits the key entirely while the
|
|
|
|
|
// admission_distance flag is off, and "Islington has not published a
|
|
|
|
|
// cut-off distance" is then a statement about us, not about Islington —
|
|
|
|
|
// false wherever the authority does publish one.
|
|
|
|
|
renderSecondarySchoolDetail({ ...secondaryFixture, admissionDistance: undefined });
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByText(/has not published a cut-off distance/)).not.toBeInTheDocument();
|
|
|
|
|
expect(screen.queryByText(/Contact the admissions authority/)).not.toBeInTheDocument();
|
|
|
|
|
});
|
2026-08-15 22:48:30 +01:00
|
|
|
});
|
2026-08-16 13:42:06 +01:00
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
// ── The Distance section ───────────────────────────────────────────────
|
2026-08-16 13:42:06 +01:00
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
describe('Distance section', () => {
|
|
|
|
|
it('appears for a school with a figure and coordinates', () => {
|
2026-08-20 14:54:02 +01:00
|
|
|
const { container } = renderSchoolDetail({
|
2026-08-16 13:42:06 +01:00
|
|
|
...primaryFixture,
|
2026-08-20 18:44:57 +01:00
|
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
|
|
|
|
admissionDistance: cutoff({ distance_m: 700, year: 2026 }),
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 14:54:02 +01:00
|
|
|
expect(container.querySelector('#distance')).toBeInTheDocument();
|
2026-08-20 18:44:57 +01:00
|
|
|
expect(screen.getByText('How far away are you?')).toBeInTheDocument();
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
it('stays away when the school has no coordinates to measure from', () => {
|
2026-08-20 14:54:02 +01:00
|
|
|
const { container } = renderSchoolDetail({
|
2026-08-16 13:42:06 +01:00
|
|
|
...primaryFixture,
|
2026-08-20 18:44:57 +01:00
|
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: null, longitude: null },
|
|
|
|
|
admissionDistance: cutoff({ distance_m: 700, year: 2026 }),
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 14:54:02 +01:00
|
|
|
expect(container.querySelector('#distance')).not.toBeInTheDocument();
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
it('stays away when no figure has been published', () => {
|
|
|
|
|
const { container } = renderSchoolDetail({
|
2026-08-16 13:42:06 +01:00
|
|
|
...primaryFixture,
|
2026-08-20 18:44:57 +01:00
|
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
|
|
|
|
admissionDistance: null,
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
expect(container.querySelector('#distance')).not.toBeInTheDocument();
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
it('shows no year-by-year record — that is held back as a paid feature', () => {
|
|
|
|
|
// The page must not leak the history through a table, a chart or a strip of
|
|
|
|
|
// per-year verdicts. The API no longer sends it either; this guards the
|
|
|
|
|
// render side so a future component cannot quietly put it back.
|
2026-08-20 14:54:02 +01:00
|
|
|
renderSchoolDetail({
|
|
|
|
|
...primaryFixture,
|
|
|
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
2026-08-20 18:44:57 +01:00
|
|
|
admissionDistance: cutoff({ distance_m: 700, year: 2026 }),
|
2026-08-20 14:54:02 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
// Scoped to the section: the page has other tables (the history section's).
|
|
|
|
|
const section = document.querySelector('#distance')!;
|
|
|
|
|
expect(section.querySelector('table')).toBeNull();
|
|
|
|
|
expect(screen.queryByText(/Last distance offered, by year/)).not.toBeInTheDocument();
|
|
|
|
|
expect(screen.queryByText(/Not published/)).not.toBeInTheDocument();
|
|
|
|
|
expect(screen.queryByText(/too few to read as a trend/)).not.toBeInTheDocument();
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-20 14:54:02 +01:00
|
|
|
describe('secondary Distance section', () => {
|
2026-08-20 18:44:57 +01:00
|
|
|
it('renders on the secondary template too', () => {
|
|
|
|
|
const { container } = renderSecondarySchoolDetail({
|
2026-08-16 13:42:06 +01:00
|
|
|
...secondaryFixture,
|
2026-08-20 18:44:57 +01:00
|
|
|
schoolInfo: { ...secondaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
|
|
|
|
admissionDistance: cutoff({ distance_m: 3472.96, year: 2026 }),
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
2026-08-20 18:44:57 +01:00
|
|
|
expect(container.querySelector('#distance')).toBeInTheDocument();
|
2026-08-16 13:42:06 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('explains a selective school by how it admits rather than as missing data', () => {
|
|
|
|
|
renderSecondarySchoolDetail({
|
|
|
|
|
...secondaryFixture,
|
|
|
|
|
schoolInfo: { ...secondaryFixture.schoolInfo, admissions_policy: 'Selective' },
|
|
|
|
|
admissionDistance: null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText(/ranked by the entrance test/)).toBeInTheDocument();
|
|
|
|
|
expect(screen.queryByText(/has not published a cut-off distance/)).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reads a consistently undersubscribed school as good news', () => {
|
|
|
|
|
renderSecondarySchoolDetail({
|
|
|
|
|
...secondaryFixture,
|
|
|
|
|
admissionDistance: null,
|
|
|
|
|
admissionsHistory: [
|
|
|
|
|
{ year: 2022, oversubscribed: false },
|
|
|
|
|
{ year: 2023, oversubscribed: false },
|
|
|
|
|
{ year: 2024, oversubscribed: false },
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText(/has not needed a distance cut-off/)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|