fix: show the Ofsted grade still in force and the latest visit (C1/M1/M2, part 2 of 2) #184
No files matched your search
@@ -0,0 +1,84 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { OfstedSection } from '@/components/school/OfstedSection';
|
||||
import { ofstedLegacyAreas } from '@/lib/utils';
|
||||
import type { OfstedInspection } from '@/lib/types';
|
||||
|
||||
const empty = {
|
||||
framework: null, inspection_date: null, inspection_type: null, overall_effectiveness: null,
|
||||
quality_of_education: null, behaviour_attitudes: null, personal_development: null,
|
||||
leadership_management: null, early_years_provision: null, sixth_form_provision: null,
|
||||
previous_overall: null, rc_safeguarding_met: null, rc_inclusion: null, rc_curriculum_teaching: null,
|
||||
rc_achievement: null, rc_attendance_behaviour: null, rc_personal_development: null,
|
||||
rc_leadership_governance: null, rc_early_years: null, rc_sixth_form: null, report_url: null,
|
||||
report_card: {},
|
||||
} as OfstedInspection;
|
||||
|
||||
// `page` mimics how each school page used to mount the section; the secondary
|
||||
// page's branch hard-coded four areas and dropped the sixth form (audit M2).
|
||||
function renderSection(o: Partial<OfstedInspection>, page: Record<string, unknown> = {}) {
|
||||
const ofsted = { ...empty, ...o } as OfstedInspection;
|
||||
render(
|
||||
<OfstedSection
|
||||
{...page}
|
||||
ofsted={ofsted}
|
||||
urn={1}
|
||||
isReportCard={false}
|
||||
ofstedInspectedDate={ofsted.latest_visit?.date ?? null}
|
||||
oeifAllSameGrade={false}
|
||||
oeifAreas={ofstedLegacyAreas(ofsted)}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
it('shows no overall grade, not an older one, after a no-grade inspection (Rabbsfarm)', () => {
|
||||
renderSection({
|
||||
inspection_date: '2025-06-17', quality_of_education: 3, behaviour_attitudes: 3,
|
||||
personal_development: 2, leadership_management: 3, early_years_provision: 2,
|
||||
current_grade: null, latest_visit: { date: '2025-06-17', kind: 'graded', outcome: null },
|
||||
});
|
||||
expect(screen.getByText(/Inspected 17 June 2025/)).toBeInTheDocument();
|
||||
expect(screen.getByText('No overall grade')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Good', { selector: 'span' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Not rated')).not.toBeInTheDocument();
|
||||
expect(screen.getAllByText('Requires Improvement')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('keeps the sixth-form judgement when there is no overall grade (audit M2)', () => {
|
||||
renderSection({
|
||||
inspection_date: '2025-04-01', quality_of_education: 1, behaviour_attitudes: 1,
|
||||
personal_development: 1, leadership_management: 1, sixth_form_provision: 2,
|
||||
current_grade: null, latest_visit: { date: '2025-04-01', kind: 'graded', outcome: null },
|
||||
}, { variant: 'secondary' });
|
||||
expect(screen.getByText(/sixth form/i)).toBeInTheDocument();
|
||||
expect(screen.getByText('Good')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('dates a grade by its source and prints a later visit separately (Washwood Heath)', () => {
|
||||
renderSection({
|
||||
inspection_date: '2020-03-03', overall_effectiveness: 2, quality_of_education: 2,
|
||||
current_grade: { grade: 2, date: '2020-03-03', basis: 'graded' },
|
||||
latest_visit: { date: '2025-05-21', kind: 'ungraded', outcome: 'Standards maintained' },
|
||||
});
|
||||
expect(screen.getByText(/Inspected 21 May 2025/)).toBeInTheDocument();
|
||||
expect(screen.getByText('Graded inspection, 3 March 2020')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Latest visit: Ungraded inspection, 21 May 2025: Standards maintained/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('says a grade was confirmed at an ungraded visit (Robins Lane)', () => {
|
||||
renderSection({
|
||||
inspection_date: '2020-01-07', overall_effectiveness: 2,
|
||||
current_grade: { grade: 2, date: '2024-07-18', basis: 'confirmed' },
|
||||
latest_visit: { date: '2024-07-18', kind: 'ungraded', outcome: 'School remains Good' },
|
||||
});
|
||||
expect(screen.getByText('Confirmed at an ungraded inspection, 18 July 2024')).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Latest visit/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('prints an ungraded outcome when there is no grade (Oakgrove)', () => {
|
||||
renderSection({
|
||||
current_grade: null,
|
||||
latest_visit: { date: '2024-11-13', kind: 'ungraded', outcome: 'Standards maintained' },
|
||||
});
|
||||
expect(screen.getByText('No overall grade')).toBeInTheDocument();
|
||||
expect(screen.getByText(/Ungraded inspection, 13 November 2024: Standards maintained/)).toBeInTheDocument();
|
||||
});
|
||||
@@ -191,7 +191,8 @@ export const primaryFixture = {
|
||||
personal_development: 2,
|
||||
leadership_management: 2,
|
||||
previous_overall: 3,
|
||||
grade_source: 'graded',
|
||||
current_grade: { grade: 2, date: '2023-05-17', basis: 'graded' },
|
||||
latest_visit: { date: '2023-05-17', kind: 'graded', outcome: null },
|
||||
}),
|
||||
census,
|
||||
admissions: makeAdmissions({ year: 2024 }),
|
||||
@@ -295,7 +296,8 @@ export const allThroughFixture = {
|
||||
behaviour_attitudes: 1,
|
||||
personal_development: 1,
|
||||
leadership_management: 1,
|
||||
grade_source: 'graded',
|
||||
current_grade: { grade: 1, date: '2022-10-04', basis: 'graded' },
|
||||
latest_visit: { date: '2022-10-04', kind: 'graded', outcome: null },
|
||||
}),
|
||||
census,
|
||||
admissions: makeAdmissions({ year: 2024, school_phase: 'Secondary' }),
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
/**
|
||||
* OfstedSection — shared between the primary and secondary detail pages.
|
||||
*
|
||||
* The two versions were ~80% identical, but that figure masked a real fork in
|
||||
* the no-overall-grade case: the primary page shows a "Not rated" badge, while
|
||||
* the secondary page shows a four-area OEIF panel. The disclaimer copy also
|
||||
* differs slightly. Both are preserved exactly via the `variant` prop rather
|
||||
* than reconciled, because this refactor must not change either page. Merging
|
||||
* them is a follow-up decision for a human, not a side effect of a move.
|
||||
* The headline is the school's current Ofsted status (backend:
|
||||
* fact_ofsted_latest): the report card; or the overall grade still in force,
|
||||
* with the inspection that awarded or confirmed it; or "No overall grade".
|
||||
* A later visit that is not the grade's source gets its own line. Both pages
|
||||
* render the same branches, so neither drops an area judgement (audit M2).
|
||||
* Rule: docs/superpowers/specs/2026-10-05-ofsted-current-status-design.md
|
||||
*
|
||||
* Server component.
|
||||
*/
|
||||
|
||||
import { ofstedDisplay } from '@/lib/compareLogic';
|
||||
import { formatOfstedDate, gradeSourceLine, latestVisitLine, showLatestVisitLine } from '@/lib/ofstedStatus';
|
||||
import type { OfstedInspection } from '@/lib/types';
|
||||
import { Section, sectionStyles as styles } from './sectionShared';
|
||||
|
||||
@@ -40,14 +42,13 @@ export interface OfstedSectionProps {
|
||||
ofstedInspectedDate: string | null;
|
||||
oeifAllSameGrade: boolean;
|
||||
oeifAreas: { label: string; value: number }[];
|
||||
variant?: 'primary' | 'secondary';
|
||||
}
|
||||
|
||||
export function OfstedSection({
|
||||
ofsted, urn, isReportCard, ofstedInspectedDate,
|
||||
oeifAllSameGrade, oeifAreas, variant = 'primary',
|
||||
oeifAllSameGrade, oeifAreas,
|
||||
}: OfstedSectionProps) {
|
||||
const isSecondary = variant === 'secondary';
|
||||
const display = ofstedDisplay(ofsted);
|
||||
|
||||
return (
|
||||
<Section id="ofsted">
|
||||
@@ -55,7 +56,7 @@ export function OfstedSection({
|
||||
{isReportCard ? 'Ofsted Report Card' : 'Ofsted Rating'}
|
||||
{ofstedInspectedDate && (
|
||||
<span className={styles.ofstedDate}>
|
||||
{isSecondary && ' '}Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
Inspected {new Date(ofstedInspectedDate).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' })}
|
||||
</span>
|
||||
)}
|
||||
<a
|
||||
@@ -98,65 +99,55 @@ export function OfstedSection({
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
) : (!isSecondary || ofsted.overall_effectiveness) ? (
|
||||
/* ── Old OEIF layout ── */
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.ofstedHeader}>
|
||||
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${ofsted.overall_effectiveness}`]}`}>
|
||||
{ofsted.overall_effectiveness ? OFSTED_LABELS[ofsted.overall_effectiveness] : 'Not rated'}
|
||||
</span>
|
||||
{ofsted.previous_overall != null &&
|
||||
ofsted.previous_overall !== ofsted.overall_effectiveness && (
|
||||
<span className={styles.ofstedPrevious}>
|
||||
Previously: {OFSTED_LABELS[ofsted.previous_overall]}
|
||||
</span>
|
||||
{display.kind === 'graded' || display.kind === 'confirmed' ? (
|
||||
<>
|
||||
<span className={`${styles.ofstedGrade} ${styles[`ofstedGrade${display.grade}`]}`}>
|
||||
{display.gradeLabel}
|
||||
</span>
|
||||
{ofsted.previous_overall != null && ofsted.previous_overall !== display.grade && (
|
||||
<span className={styles.ofstedPrevious}>
|
||||
Previously: {OFSTED_LABELS[ofsted.previous_overall]}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span className={styles.ofstedGrade}>No overall grade</span>
|
||||
)}
|
||||
</div>
|
||||
<p className={styles.ofstedDisclaimer}>
|
||||
{ofsted.grade_source === 'ungraded_carried_forward'
|
||||
? 'This overall grade is carried forward from an earlier inspection. Ofsted has since visited without issuing a new overall grade. From September 2024, Ofsted no longer makes an overall effectiveness judgement.'
|
||||
: isSecondary
|
||||
? 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections.'
|
||||
: 'From September 2024, Ofsted no longer makes an overall effectiveness judgement in inspections of state-funded schools.'}
|
||||
{ofsted.current_grade
|
||||
? gradeSourceLine(ofsted.current_grade)
|
||||
: 'Ofsted stopped giving overall grades in September 2024.'}
|
||||
</p>
|
||||
{oeifAllSameGrade ? (
|
||||
<p className={styles.ofstedAllSame}>
|
||||
Rated <strong>{OFSTED_LABELS[ofsted.overall_effectiveness!]}</strong> across all inspected areas: Quality of Teaching, Behaviour, Pupils' Development and Leadership.
|
||||
</p>
|
||||
) : (
|
||||
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
|
||||
{oeifAreas.map(({ label, value }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
{OFSTED_LABELS[value]}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{showLatestVisitLine(ofsted) && ofsted.latest_visit && (
|
||||
<p className={styles.ofstedDisclaimer}>Latest visit: {latestVisitLine(ofsted.latest_visit)}</p>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
/* ── Secondary only: inspected since Sept 2024, no overall grade ── */
|
||||
<>
|
||||
<p className={styles.sectionSubtitle}>
|
||||
From September 2024, Ofsted no longer gives a single overall grade.
|
||||
</p>
|
||||
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
|
||||
{[
|
||||
{ label: 'Quality of Education', value: ofsted.quality_of_education },
|
||||
{ label: 'Behaviour & Attitudes', value: ofsted.behaviour_attitudes },
|
||||
{ label: 'Personal Development', value: ofsted.personal_development },
|
||||
{ label: 'Leadership & Management', value: ofsted.leadership_management },
|
||||
].filter(({ value }) => value != null).map(({ label, value }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
{OFSTED_LABELS[value!]}
|
||||
</div>
|
||||
{oeifAllSameGrade && (display.kind === 'graded' || display.kind === 'confirmed') ? (
|
||||
<p className={styles.ofstedAllSame}>
|
||||
Rated <strong>{display.gradeLabel}</strong> across all inspected areas: Quality of Teaching, Behaviour, Pupils' Development and Leadership.
|
||||
</p>
|
||||
) : oeifAreas.length > 0 ? (
|
||||
<>
|
||||
{ofsted.inspection_date && ofsted.inspection_date !== ofsted.latest_visit?.date && (
|
||||
<p className={styles.ofstedDisclaimer}>
|
||||
Area judgements from the graded inspection, {formatOfstedDate(ofsted.inspection_date)}.
|
||||
</p>
|
||||
)}
|
||||
<div className={`${styles.metricsGrid} ${styles.gradeGrid}`}>
|
||||
{oeifAreas.map(({ label, value }) => (
|
||||
<div key={label} className={styles.metricCard}>
|
||||
<div className={styles.metricLabel}>{label}</div>
|
||||
<div className={`${styles.metricValue} ${styles[`ofstedGrade${value}`]}`}>
|
||||
{OFSTED_LABELS[value]}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@@ -55,15 +55,17 @@ export function PrimarySchoolSections({
|
||||
const secondaryAvg = nationalAvg?.secondary ?? {};
|
||||
|
||||
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
|
||||
// Report cards are dated by their own inspection (rc_inspection_date), never
|
||||
// the legacy inspection_date (report cards exist only from Nov 2025).
|
||||
// Report cards are dated by their own inspection (rc_inspection_date);
|
||||
// anything else by the school's latest visit, never the older inspection a
|
||||
// grade may come from.
|
||||
const ofstedInspectedDate = isReportCard
|
||||
? ofsted?.rc_inspection_date ?? null
|
||||
: ofsted?.inspection_date ?? null;
|
||||
: ofsted?.latest_visit?.date ?? null;
|
||||
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
|
||||
const oeifAllSameGrade =
|
||||
!!ofsted &&
|
||||
!isReportCard &&
|
||||
ofsted.overall_effectiveness != null &&
|
||||
oeifAreas.length >= 3 &&
|
||||
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
|
||||
|
||||
@@ -77,7 +79,6 @@ export function PrimarySchoolSections({
|
||||
ofstedInspectedDate={ofstedInspectedDate}
|
||||
oeifAllSameGrade={oeifAllSameGrade}
|
||||
oeifAreas={oeifAreas}
|
||||
variant="primary"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -63,11 +63,12 @@ export function SecondarySchoolSections({
|
||||
const isReportCard = !!(ofsted?.report_card && Object.keys(ofsted.report_card).length > 0);
|
||||
const ofstedInspectedDate = isReportCard
|
||||
? ofsted?.rc_inspection_date ?? null
|
||||
: ofsted?.inspection_date ?? null;
|
||||
: ofsted?.latest_visit?.date ?? null;
|
||||
const oeifAreas = ofsted ? ofstedLegacyAreas(ofsted) : [];
|
||||
const oeifAllSameGrade =
|
||||
!!ofsted &&
|
||||
!isReportCard &&
|
||||
ofsted.overall_effectiveness != null &&
|
||||
oeifAreas.length >= 3 &&
|
||||
oeifAreas.every((a) => a.value === ofsted.overall_effectiveness);
|
||||
|
||||
@@ -81,7 +82,6 @@ export function SecondarySchoolSections({
|
||||
ofstedInspectedDate={ofstedInspectedDate}
|
||||
oeifAllSameGrade={oeifAllSameGrade}
|
||||
oeifAreas={oeifAreas}
|
||||
variant="secondary"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user