/** * Last distance offered — formatting and the caveats attached to the figure. * * The assertions about the route note and the year are not cosmetic. A cut-off * shown without its year, or a banded school's widest cut-off shown as if it * were the only one, tells a parent something false about their chances of a * place — so both are pinned here rather than left to the component. */ import { formatCutoffDistance, formatMiles, formatEntryYear } from '@/lib/utils'; import { describeCutoff, describeCutoffAbsence, compareToCutoff, CUTOFF_UNCERTAINTY_M, } from '@/components/school/lastDistanceOffered'; import type { SchoolAdmissionDistance } from '@/lib/types'; const distance = (over: Partial = {}): SchoolAdmissionDistance => ({ year: 2025, distance_m: 500, route_count: 1, la_name: 'Camden', distance_unit_raw: 'miles', ...over, }); describe('formatCutoffDistance', () => { it('leads with miles, the unit councils publish in', () => { expect(formatCutoffDistance(500)).toEqual({ primary: '0.31 miles', secondary: '500 m' }); expect(formatCutoffDistance(1609.344)).toEqual({ primary: '1.00 miles', secondary: '1.6 km' }); }); it('switches to kilometres for the support figure above a kilometre', () => { expect(formatCutoffDistance(3472.96)!.secondary).toBe('3.5 km'); }); it('stays in miles at short range, where it used to swap to metres', () => { // The swap made a single number easier to read and a comparison harder: // "69 m away — inside the cut-off of 0.17 miles" asked the reader to // convert between units to check a claim we had already made for them. expect(formatCutoffDistance(27)).toEqual({ primary: '0.02 miles', secondary: '30 m' }); expect(formatCutoffDistance(69)!.primary).toMatch(/miles$/); }); it('describes a distance too short for two decimal places', () => { // Rather than a flat "0.00 miles", which reads as no distance at all. expect(formatMiles(5)).toBe('under 0.01 miles'); expect(formatMiles(0)).toBe('under 0.01 miles'); expect(formatMiles(20)).toBe('0.01 miles'); }); it('returns null rather than a zero cut-off', () => { // 0.0 miles appears in the source where a school filled on a higher // criterion. Rendered as "0.00 miles" it would read as the opposite. expect(formatCutoffDistance(0)).toBeNull(); expect(formatCutoffDistance(null)).toBeNull(); expect(formatCutoffDistance(undefined)).toBeNull(); expect(formatCutoffDistance(Number.NaN)).toBeNull(); }); }); describe('formatEntryYear', () => { it('names the intake, not the academic year', () => { // formatAcademicYear would render 2025 as "2025/26", which reads as a // school year rather than the September a child started. expect(formatEntryYear(2025)).toBe('September 2025'); expect(formatEntryYear(null)).toBe(''); }); }); describe('describeCutoff', () => { it('always carries the entry year alongside the figure', () => { const d = describeCutoff(distance({ distance_m: 772.49, year: 2024 })); expect(d).not.toBeNull(); expect(d!.primary).toBe('0.48 miles'); expect(d!.entryYear).toBe('September 2024'); }); it('says nothing about routes for a school with one', () => { expect(describeCutoff(distance({ route_count: 1 }))!.routeNote).toBeNull(); expect(describeCutoff(distance({ route_count: null }))!.routeNote).toBeNull(); }); it('warns that a banded school\'s figure is the widest of several', () => { const note = describeCutoff(distance({ route_count: 4 }))!.routeNote; expect(note).toContain('4 admission routes'); expect(note).toContain('shorter cut-off'); }); it('is null when there is nothing publishable', () => { expect(describeCutoff(null)).toBeNull(); expect(describeCutoff(undefined)).toBeNull(); expect(describeCutoff(distance({ distance_m: null }))).toBeNull(); }); }); // ── "Would we have got in?" ──────────────────────────────────────────── describe('compareToCutoff', () => { it('never states the two figures in different units', () => { /* * The reported defect: "69 m away — inside the September 2026 cut-off of * 0.17 miles". Both numbers are correct and the sentence is still useless, * because checking it means converting one of them. * * Swept across the range where the old formatter switched units, so a * future readability tweak to one figure cannot reintroduce the mismatch * in the other. */ const mixed: string[] = []; for (const homeM of [0, 5, 27, 69, 99, 100, 260, 800, 1609, 5000]) { for (const cutoffM of [30, 69, 100, 270, 1000, 3500]) { const { headline } = compareToCutoff(homeM, cutoffM, 2026); const hasMetres = /\d\s?m\b/.test(headline); const milesCount = (headline.match(/miles/g) ?? []).length; // Two figures, both in miles, and no metric reading anywhere near them. if (hasMetres || milesCount !== 2) { mixed.push(`home=${homeM}m cutoff=${cutoffM}m -> ${headline}`); } } } expect(mixed).toEqual([]); }); it('reads back the reported case in one unit', () => { expect(compareToCutoff(69, 270, 2026).headline) .toBe('0.04 miles away, inside the September 2026 cut-off of 0.17 miles.'); }); it('calls a clearly nearer home inside, and names the year', () => { const r = compareToCutoff(300, 800, 2026); expect(r.verdict).toBe('inside'); expect(r.headline).toContain('September 2026'); }); it('calls a clearly further home beyond', () => { expect(compareToCutoff(4000, 800, 2026).verdict).toBe('outside'); }); it('refuses to call a result inside the measurement error, either way', () => { // A postcode centroid covers several addresses, so a margin this fine is // noise. With one published year there is no other year to fall back on, // which makes this band the only thing standing between a parent and a // place they do not have. expect(compareToCutoff(800 - CUTOFF_UNCERTAINTY_M / 2, 800, 2026).verdict).toBe('too-close'); expect(compareToCutoff(800 + CUTOFF_UNCERTAINTY_M / 2, 800, 2026).verdict).toBe('too-close'); expect(compareToCutoff(800, 800, 2026).detail).toContain('measurement error'); // The explanation is not welded to the headline, so it does not run at // headline weight in the result block. expect(compareToCutoff(800, 800, 2026).headline).not.toContain('measurement error'); expect(compareToCutoff(300, 800, 2026).detail).toBeNull(); }); it('treats the band as exclusive at its edge', () => { // Exactly on the boundary is still too close; one metre past it is not. expect(compareToCutoff(800 - CUTOFF_UNCERTAINTY_M, 800, 2026).verdict).toBe('too-close'); expect(compareToCutoff(800 - CUTOFF_UNCERTAINTY_M - 1, 800, 2026).verdict).toBe('inside'); }); }); describe('describeCutoffAbsence', () => { it('explains a selective school by how it admits, not as missing data', () => { const s = describeCutoffAbsence({ localAuthority: 'Kent', admissionsPolicy: 'Selective' }); expect(s).toContain('entrance test'); expect(s).not.toContain('has not published'); }); it('reads a consistently undersubscribed school as good news', () => { const s = describeCutoffAbsence({ localAuthority: 'Camden', admissionsHistory: [ { year: 2022, oversubscribed: false }, { year: 2023, oversubscribed: false }, { year: 2024, oversubscribed: false }, ], }); expect(s).toContain('has not needed a distance cut-off'); }); it('otherwise names the authority that would hold the figure', () => { expect(describeCutoffAbsence({ localAuthority: 'Camden' })) .toContain('Camden has not published'); }); });