/** * 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, formatEntryYear } from '@/lib/utils'; import { describeCutoff, cutoffYearRows, cutoffTrendSummary, cutoffCoverageNote, describeCutoffAbsence, compareToCutoffs, entryYearOf, 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('leads with metres below 100 m, where a miles figure carries nothing', () => { expect(formatCutoffDistance(27)).toEqual({ primary: '27 m', secondary: '0.02 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(); }); }); // ── Year-by-year history ─────────────────────────────────────────────── const d = (year: number, distance_m: number | null, route_count = 1): SchoolAdmissionDistance => ({ year, distance_m, route_count, la_name: 'Camden', distance_unit_raw: 'miles', }); describe('entryYearOf', () => { it('reduces EES academic codes and plain entry years to one key', () => { // Without this the two histories never join and every year looks unpublished. expect(entryYearOf(202425)).toBe(2024); expect(entryYearOf(2024)).toBe(2024); }); }); describe('cutoffYearRows', () => { it('emits a row for every year in the span, including the empty ones', () => { const rows = cutoffYearRows([d(2021, 900), d(2024, 700)]); expect(rows.map((r) => r.year)).toEqual([2024, 2023, 2022, 2021]); expect(rows.map((r) => r.status)).toEqual([ 'published', 'not-published', 'not-published', 'published', ]); }); it('separates "nothing published" from "was not oversubscribed"', () => { // The two look identical in the distance data and mean opposite things. const rows = cutoffYearRows( [d(2021, 900), d(2023, 700)], [{ year: 2022, oversubscribed: false }], ); expect(rows.find((r) => r.year === 2022)!.status).toBe('not-oversubscribed'); }); it('matches EES six-digit years against plain cut-off years', () => { const rows = cutoffYearRows( [d(2023, 700)], [{ year: 202324, places_offered: 60, oversubscribed: true }], ); expect(rows.find((r) => r.year === 2023)!.placesOffered).toBe(60); }); it('does not stretch the span back over admissions years with no cut-offs', () => { // EES reaches back further than councils publish; padding the chart with a // decade of blanks would bury the years that carry a figure. const rows = cutoffYearRows( [d(2024, 700)], [{ year: 2015, oversubscribed: true }, { year: 2024, oversubscribed: true }], ); expect(rows.map((r) => r.year)).toEqual([2024]); }); it('is safe when the backend does not send the field at all', () => { expect(cutoffYearRows(undefined as never)).toEqual([]); expect(cutoffYearRows([], [])).toEqual([]); }); }); describe('cutoffTrendSummary', () => { const rowsFor = (pts: [number, number][]) => cutoffYearRows(pts.map(([y, m]) => d(y, m))); it('says nothing below four published points', () => { expect(cutoffTrendSummary(rowsFor([[2022, 900], [2023, 800], [2024, 700]]))).toBeNull(); }); it('names both endpoints and their years rather than passing a verdict', () => { const s = cutoffTrendSummary(rowsFor([ [2021, 1000], [2022, 900], [2023, 800], [2024, 500], ])); expect(s).toContain('2021'); expect(s).toContain('2024'); expect(s).toContain('tightened'); }); it('does not call a small wobble a direction', () => { const s = cutoffTrendSummary(rowsFor([ [2021, 1000], [2022, 1010], [2023, 990], [2024, 1030], ])); expect(s).toContain('stayed broadly the same'); }); }); describe('cutoffCoverageNote', () => { it('warns while the record is too thin to read as a trend', () => { expect(cutoffCoverageNote(cutoffYearRows([d(2024, 700)]))).toContain('Only 1 year'); expect(cutoffCoverageNote(cutoffYearRows([ d(2021, 1000), d(2022, 900), d(2023, 800), d(2024, 700), ]))).toBeNull(); }); }); describe('describeCutoffAbsence', () => { it('explains a selective school by how it admits, not by 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'); }); }); describe('compareToCutoffs', () => { const rows = cutoffYearRows( [d(2022, 1000), d(2024, 800)], [{ year: 2023, oversubscribed: false }], ); it('marks a clearly nearer home as inside every comparable year', () => { const r = compareToCutoffs(300, rows); expect(r.insideCount).toBe(2); expect(r.comparableCount).toBe(2); expect(r.headline).toContain('inside the cut-off in all 2 years'); }); it('marks a clearly further home as outside', () => { const r = compareToCutoffs(4000, rows); expect(r.insideCount).toBe(0); expect(r.headline).toContain('outside the cut-off in every year'); }); it('refuses to call a result inside the measurement error', () => { // A postcode centroid covers several addresses; claiming a place on a 20 m // margin would be inventing precision the inputs do not have. const r = compareToCutoffs(800 - CUTOFF_UNCERTAINTY_M / 2, rows); expect(r.years.find((y) => y.year === 2024)!.verdict).toBe('too-close'); expect(r.detail).toContain('too close to call'); }); it('never counts an unpublished year as a pass or a fail', () => { const r = compareToCutoffs(300, rows); expect(r.years.find((y) => y.year === 2023)!.verdict).toBe('unknown'); expect(r.detail).toContain('no published figure'); }); });