fix(compare): expert sign-off must-fixes — phase-matched admissions, Ofsted sentinel codes, selective-school copy
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m4s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 17s
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 2m53s
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m4s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 17s
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 2m53s
M1: admissions rounds are now selected by the active phase tab (admissionsForPhase) — an all-through school's Year 7 round no longer masquerades as Reception odds beside pure primaries; honest per-cell and section fallbacks name the round (Reception / Year 7). M2: Ofsted sentinel codes (9 = not applicable) never render as judgement chips, and the sixth-form judgement — previously dropped — now renders for schools that have one. M3: 'What this means' is phase- and type-aware: selective schools get entrance-test framing, secondary faith schools a faith-criteria note, and the primaries' distance template never appears on the secondary tab (admissions_policy now exposed in compare school_info). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Getting a place — phase and school-type correctness (expert sign-off
|
||||
* must-fixes M1/M3):
|
||||
* - an all-through school's Year 7 round must never render on the primary
|
||||
* tab as if it were Reception odds;
|
||||
* - selective schools get entrance-test framing, and the secondary tab
|
||||
* never shows the primaries' distance template.
|
||||
*/
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { CompareAdmissions } from '@/components/compare/CompareAdmissions';
|
||||
import type { ComparisonData, School, SchoolAdmissions } from '@/lib/types';
|
||||
|
||||
function school(urn: number, name: string, extra: Partial<School> = {}): School {
|
||||
return { urn, school_name: name, ...extra } as School;
|
||||
}
|
||||
|
||||
function admissions(partial: Partial<SchoolAdmissions>): SchoolAdmissions {
|
||||
return {
|
||||
year: 202627,
|
||||
school_phase: 'Secondary',
|
||||
places_offered: 173,
|
||||
total_applications: 433,
|
||||
first_preference_offer_pct: 83,
|
||||
oversubscribed: true,
|
||||
...partial,
|
||||
} as SchoolAdmissions;
|
||||
}
|
||||
|
||||
function entry(info: School, a: SchoolAdmissions | null): ComparisonData {
|
||||
return {
|
||||
school_info: info,
|
||||
yearly_data: [],
|
||||
ofsted: null,
|
||||
census: null,
|
||||
admissions: a,
|
||||
admissions_history: a ? [a] : [],
|
||||
deprivation: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe('CompareAdmissions', () => {
|
||||
it("does not show an all-through school's Year 7 round on the primary tab", () => {
|
||||
// The real M1 scenario: an all-through school (Year 7 round only) beside
|
||||
// a primary with a Reception round.
|
||||
const allThrough = school(137306, 'Hessle High and Penshurst Primary');
|
||||
const primary = school(138690, 'Barclay Primary School');
|
||||
const data = {
|
||||
'137306': entry(allThrough, admissions({ school_phase: 'Secondary' })),
|
||||
'138690': entry(
|
||||
primary,
|
||||
admissions({
|
||||
school_phase: 'Primary',
|
||||
total_applications: 300,
|
||||
places_offered: 120,
|
||||
first_preference_offer_pct: 96,
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
render(<CompareAdmissions schools={[allThrough, primary]} data={data} isSecondary={false} />);
|
||||
|
||||
// Hessle's Year 7 figures must not appear…
|
||||
expect(screen.queryByText('433')).toBeNull();
|
||||
expect(
|
||||
screen.getByText(/We don't hold Reception admissions data for this school/),
|
||||
).toBeInTheDocument();
|
||||
// …while Barclay's Reception round renders normally.
|
||||
expect(screen.getByText('300')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('phase-labels the section empty state when no matching round exists at all', () => {
|
||||
const allThrough = school(137306, 'Hessle High and Penshurst Primary');
|
||||
const data = { '137306': entry(allThrough, admissions({ school_phase: 'Secondary' })) };
|
||||
|
||||
render(<CompareAdmissions schools={[allThrough]} data={data} isSecondary={false} />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/No Reception admissions data is available for these schools yet/),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('433')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the Year 7 round on the secondary tab', () => {
|
||||
const allThrough = school(137306, 'Hessle High and Penshurst Primary');
|
||||
const data = { '137306': entry(allThrough, admissions({ school_phase: 'Secondary' })) };
|
||||
|
||||
render(<CompareAdmissions schools={[allThrough]} data={data} isSecondary={true} />);
|
||||
|
||||
expect(screen.getByText('433')).toBeInTheDocument();
|
||||
expect(screen.getByText('173')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('gives selective schools entrance-test framing, never the distance template', () => {
|
||||
const grammar = school(136276, 'Watford Grammar School for Boys', {
|
||||
admissions_policy: 'Selective',
|
||||
religious_denomination: 'Church of England',
|
||||
});
|
||||
const data = {
|
||||
'136276': entry(grammar, admissions({ first_preference_offer_pct: 43.7 })),
|
||||
};
|
||||
|
||||
render(<CompareAdmissions schools={[grammar]} data={data} isSecondary={true} />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/Entry is by entrance test — the school is selective/),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText(/non-faith primaries/)).toBeNull();
|
||||
});
|
||||
|
||||
it('secondary faith school gets faith-aware copy, not the primaries template', () => {
|
||||
const faithSchool = school(102052, "Bishop Stopford's School", {
|
||||
admissions_policy: 'Non-selective',
|
||||
religious_denomination: 'Church of England',
|
||||
});
|
||||
const data = {
|
||||
'102052': entry(faithSchool, admissions({ first_preference_offer_pct: 68 })),
|
||||
};
|
||||
|
||||
render(<CompareAdmissions schools={[faithSchool]} data={data} isSecondary={true} />);
|
||||
|
||||
expect(screen.getByText(/faith-based criteria may apply/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/non-faith primaries/)).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the reviewed distance copy for oversubscribed non-faith primaries', () => {
|
||||
const primary = school(100140, 'Plumcroft Primary School');
|
||||
const data = {
|
||||
'100140': entry(
|
||||
primary,
|
||||
admissions({ school_phase: 'Primary', first_preference_offer_pct: 73.4 }),
|
||||
),
|
||||
};
|
||||
|
||||
render(<CompareAdmissions schools={[primary]} data={data} isSecondary={false} />);
|
||||
|
||||
expect(screen.getByText(/for most non-faith primaries, distance decides/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -96,6 +96,31 @@ describe('CompareOfsted', () => {
|
||||
expect(links[0]).toHaveAttribute('href', 'https://reports.ofsted.gov.uk/provider/21/1');
|
||||
});
|
||||
|
||||
it('never renders Ofsted sentinel codes (9 = not applicable) as judgement chips', () => {
|
||||
const sentinelSchool = school(6, 'Sentinel School');
|
||||
const sentinelData: Record<string, ComparisonData> = {
|
||||
'6': {
|
||||
school_info: sentinelSchool,
|
||||
yearly_data: [],
|
||||
ofsted: ofsted({
|
||||
overall_effectiveness: 2,
|
||||
grade_source: 'graded',
|
||||
quality_of_education: 1,
|
||||
early_years_provision: 9,
|
||||
sixth_form_provision: 2,
|
||||
}),
|
||||
},
|
||||
};
|
||||
render(<CompareOfsted schools={[sentinelSchool]} data={sentinelData} />);
|
||||
// Real grades render…
|
||||
expect(screen.getByText('Quality of education')).toBeInTheDocument();
|
||||
// …the applicable sixth-form judgement renders (was previously dropped)…
|
||||
expect(screen.getByText('Sixth form provision')).toBeInTheDocument();
|
||||
// …and the not-applicable sentinel never appears, neither as area nor code.
|
||||
expect(screen.queryByText('Early years provision')).toBeNull();
|
||||
expect(screen.queryByText('9')).toBeNull();
|
||||
});
|
||||
|
||||
it('dates a report card with the report-card inspection date, never the legacy date', () => {
|
||||
const cardSchool = school(4, 'Dated Card School');
|
||||
const cardData: Record<string, ComparisonData> = {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import {
|
||||
OFSTED_LEGACY_GRADES,
|
||||
admissionsForPhase,
|
||||
ofstedDisplay,
|
||||
progressBand,
|
||||
rcAreaLabel,
|
||||
@@ -188,6 +189,44 @@ describe('summariseAdmissions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('admissionsForPhase', () => {
|
||||
const row = (year: number, school_phase: string | null): SchoolAdmissions =>
|
||||
({ year, school_phase, places_offered: 100, total_applications: 200, first_preference_offer_pct: 80 }) as SchoolAdmissions;
|
||||
|
||||
it('returns the latest round matching the active phase', () => {
|
||||
const data = {
|
||||
admissions: row(202627, 'Secondary'),
|
||||
admissions_history: [row(202526, 'Secondary'), row(202526, 'Primary'), row(202425, 'Primary')],
|
||||
};
|
||||
expect(admissionsForPhase(data, true)?.year).toBe(202627);
|
||||
expect(admissionsForPhase(data, false)?.year).toBe(202526);
|
||||
expect(admissionsForPhase(data, false)?.school_phase).toBe('Primary');
|
||||
});
|
||||
|
||||
it("never substitutes the other phase's round (all-through with Year 7 data only)", () => {
|
||||
const data = {
|
||||
admissions: row(202627, 'Secondary'),
|
||||
admissions_history: [row(202526, 'Secondary')],
|
||||
};
|
||||
expect(admissionsForPhase(data, false)).toBeNull();
|
||||
expect(admissionsForPhase(data, true)?.year).toBe(202627);
|
||||
});
|
||||
|
||||
it('uses untagged legacy rows only when no row carries a phase', () => {
|
||||
const untagged = { admissions: row(202627, null), admissions_history: [row(202526, null)] };
|
||||
expect(admissionsForPhase(untagged, false)?.year).toBe(202627);
|
||||
expect(admissionsForPhase(untagged, true)?.year).toBe(202627);
|
||||
|
||||
const mixed = { admissions: row(202627, 'Secondary'), admissions_history: [row(202526, null)] };
|
||||
expect(admissionsForPhase(mixed, false)).toBeNull();
|
||||
});
|
||||
|
||||
it('handles missing data', () => {
|
||||
expect(admissionsForPhase(null, false)).toBeNull();
|
||||
expect(admissionsForPhase({ admissions: null, admissions_history: [] }, true)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('progressBand', () => {
|
||||
it('CI entirely above zero → above', () => {
|
||||
expect(progressBand(1.2, 0.4, 2.0)).toBe('above');
|
||||
|
||||
Reference in New Issue
Block a user