PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m4s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 46s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m8s
The secondary admissions section words the absence of a cut-off distance:
"<LA> has not published a cut-off distance for this school." That sentence
is true when the authority publishes nothing. It is false when the
authority does publish and the admission_distance flag is simply off — and
off is the current state, so every secondary page with an EES admissions
row has been making a claim about a council on our behalf.
The backend already draws the distinction the copy needs. /api/schools/{urn}
omits the admission_distance key entirely while the flag is dark rather than
sending null, precisely so that "we are not publishing cut-offs" stays
distinguishable from "this school has no cut-off"; lib/types.ts says so in
as many words. The page then collapsed the two with `?? null` before the
section ever saw them.
So stop collapsing it: thread the raw field to SecondarySchoolSections and
word the absence only when the feature is on. Null still gets the sentence
naming the authority — that case is unchanged and still tested.
Primary pages are unaffected: AdmissionsSection carries no absence copy and
renders nothing when there is no figure. DistanceSection already treated
absent and null alike; only its type widens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FuPUioHpxtaiDNagQvjxyM
203 lines
8.6 KiB
TypeScript
203 lines
8.6 KiB
TypeScript
/**
|
|
* 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.
|
|
*/
|
|
|
|
import { screen, fireEvent } from '@testing-library/react';
|
|
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');
|
|
});
|
|
|
|
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/);
|
|
});
|
|
|
|
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();
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|
|
|
|
// ── The Distance section ───────────────────────────────────────────────
|
|
|
|
describe('Distance section', () => {
|
|
it('appears for a school with a figure and coordinates', () => {
|
|
const { container } = renderSchoolDetail({
|
|
...primaryFixture,
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
|
admissionDistance: cutoff({ distance_m: 700, year: 2026 }),
|
|
});
|
|
|
|
expect(container.querySelector('#distance')).toBeInTheDocument();
|
|
expect(screen.getByText('How far away are you?')).toBeInTheDocument();
|
|
});
|
|
|
|
it('stays away when the school has no coordinates to measure from', () => {
|
|
const { container } = renderSchoolDetail({
|
|
...primaryFixture,
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: null, longitude: null },
|
|
admissionDistance: cutoff({ distance_m: 700, year: 2026 }),
|
|
});
|
|
|
|
expect(container.querySelector('#distance')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('stays away when no figure has been published', () => {
|
|
const { container } = renderSchoolDetail({
|
|
...primaryFixture,
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
|
admissionDistance: null,
|
|
});
|
|
|
|
expect(container.querySelector('#distance')).not.toBeInTheDocument();
|
|
});
|
|
|
|
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.
|
|
renderSchoolDetail({
|
|
...primaryFixture,
|
|
schoolInfo: { ...primaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
|
admissionDistance: cutoff({ distance_m: 700, year: 2026 }),
|
|
});
|
|
|
|
// 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();
|
|
});
|
|
});
|
|
|
|
describe('secondary Distance section', () => {
|
|
it('renders on the secondary template too', () => {
|
|
const { container } = renderSecondarySchoolDetail({
|
|
...secondaryFixture,
|
|
schoolInfo: { ...secondaryFixture.schoolInfo, latitude: 51.5, longitude: -0.12 },
|
|
admissionDistance: cutoff({ distance_m: 3472.96, year: 2026 }),
|
|
});
|
|
|
|
expect(container.querySelector('#distance')).toBeInTheDocument();
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|