perf(detail): render school detail sections on the server #84

Merged
tudor merged 13 commits from perf/server-client-split into main 2026-08-02 21:03:33 +00:00
2 changed files with 349 additions and 0 deletions
Showing only changes of commit bcf3498086 - Show all commits
@@ -0,0 +1,332 @@
/**
* Fixtures for the school detail characterization tests.
*
* Four shapes, each pinning a branch the detail views take:
* primary — KS2 results, legacy OEIF Ofsted, 3 years of admissions
* (enough for the year/trend toggle)
* secondary — KS4 results, Report Card Ofsted, 1 admissions year
* (no toggle)
* allThrough — BOTH KS2 and KS4, so the primary content is gated back on
* special — a special school with an all-zero KS2 row, where the
* England comparison must be suppressed
*
* Field names come from lib/types.ts. If TypeScript rejects something here,
* the fixture is wrong — never widen the type to accommodate a fixture.
*/
import type {
School, SchoolResult, AbsenceData, OfstedInspection, SchoolCensus,
SchoolAdmissions, SchoolDeprivation, SchoolFinance, NationalAverages,
} from '@/lib/types';
// ---------------------------------------------------------------------------
// Builders — SchoolResult has 57 required nullable fields, so every fixture
// starts from an all-null row and overrides only what it is testing.
// ---------------------------------------------------------------------------
const emptyResult: SchoolResult = {
school_id: 1,
year: 2024,
total_pupils: null, eligible_pupils: null,
rwm_expected_pct: null, reading_expected_pct: null, writing_expected_pct: null,
maths_expected_pct: null, gps_expected_pct: null, science_expected_pct: null,
rwm_high_pct: null, reading_high_pct: null, writing_high_pct: null,
maths_high_pct: null, gps_high_pct: null,
reading_progress: null, writing_progress: null, maths_progress: null,
reading_avg_score: null, maths_avg_score: null, gps_avg_score: null,
disadvantaged_pct: null, eal_pct: null, sen_support_pct: null, sen_ehcp_pct: null,
stability_pct: null,
reading_absence_pct: null, gps_absence_pct: null, maths_absence_pct: null,
writing_absence_pct: null, science_absence_pct: null,
rwm_expected_boys_pct: null, rwm_expected_girls_pct: null,
rwm_high_boys_pct: null, rwm_high_girls_pct: null,
rwm_expected_disadvantaged_pct: null, rwm_expected_non_disadvantaged_pct: null,
disadvantaged_gap: null,
rwm_expected_3yr_pct: null, reading_avg_3yr: null, maths_avg_3yr: null,
attainment_8_score: null, progress_8_score: null,
progress_8_lower_ci: null, progress_8_upper_ci: null,
progress_8_english: null, progress_8_maths: null,
progress_8_ebacc: null, progress_8_open: null,
english_maths_strong_pass_pct: null, english_maths_standard_pass_pct: null,
ebacc_entry_pct: null, ebacc_strong_pass_pct: null, ebacc_standard_pass_pct: null,
ebacc_avg_score: null, gcse_grade_91_pct: null, prior_attainment_avg: null,
};
export function makeResult(overrides: Partial<SchoolResult> = {}): SchoolResult {
return { ...emptyResult, ...overrides };
}
function makeSchool(overrides: Partial<School> = {}): School {
return {
urn: 123456,
school_name: 'Test Primary School',
local_authority: 'Westshire',
local_authority_code: 900,
school_type: 'Community school',
school_type_code: 'CY',
religious_denomination: 'None',
age_range: '4-11',
address1: '1 Test Lane',
address2: null,
town: 'Testville',
postcode: 'TE1 1ST',
address: '1 Test Lane, Testville, TE1 1ST',
latitude: 51.5,
longitude: -0.12,
phase: 'Primary',
gender: 'Mixed',
...overrides,
};
}
const emptyOfsted: OfstedInspection = {
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,
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,
};
function makeOfsted(overrides: Partial<OfstedInspection> = {}): OfstedInspection {
return { ...emptyOfsted, ...overrides };
}
function makeAdmissions(overrides: Partial<SchoolAdmissions> = {}): SchoolAdmissions {
return {
year: 2024,
places_offered: 60,
total_applications: 210,
first_preference_applications: 90,
first_preference_offers: 54,
first_preference_offer_pct: 60,
oversubscribed: true,
...overrides,
};
}
const census: SchoolCensus = {
year: 2024,
total_pupils: 420,
female_pupils: 205,
male_pupils: 215,
fsm_pct: 18.4,
eal_pct: 22.1,
};
const absence: AbsenceData = {
overall_absence_rate: 5.2,
persistent_absence_rate: 14.8,
};
const deprivation: SchoolDeprivation = {
lsoa_code: 'E01000001',
idaci_score: 0.21,
idaci_decile: 4,
};
const finance: SchoolFinance = {
year: 2024,
per_pupil_spend: 5400,
staff_cost_pct: 72.5,
teacher_cost_pct: 51.2,
support_staff_cost_pct: 21.3,
premises_cost_pct: 6.1,
};
// ---------------------------------------------------------------------------
// National averages
// ---------------------------------------------------------------------------
export const nationalAveragesFixture: NationalAverages = {
year: 2024,
primary: { rwm_expected_pct: 61, rwm_high_pct: 8, reading_expected_pct: 74 },
secondary: { attainment_8_score: 45.9, progress_8_score: 0, ebacc_entry_pct: 39 },
by_year: [
{ year: 2023, primary: { rwm_expected_pct: 60 }, secondary: { attainment_8_score: 44.4 } },
{ year: 2024, primary: { rwm_expected_pct: 61 }, secondary: { attainment_8_score: 45.9 } },
],
};
// ---------------------------------------------------------------------------
// Fixture 1: ordinary primary — KS2 results, legacy OEIF, 3 admissions years
// ---------------------------------------------------------------------------
export const primaryFixture = {
schoolInfo: makeSchool(),
yearlyData: [
makeResult({ year: 2022, rwm_expected_pct: 54, reading_expected_pct: 68, writing_expected_pct: 62, maths_expected_pct: 65, total_pupils: 58 }),
makeResult({ year: 2023, rwm_expected_pct: 56, reading_expected_pct: 70, writing_expected_pct: 64, maths_expected_pct: 67, total_pupils: 60 }),
makeResult({
year: 2024,
rwm_expected_pct: 58, rwm_high_pct: 9,
reading_expected_pct: 72, writing_expected_pct: 66, maths_expected_pct: 69,
reading_progress: 0.4, writing_progress: -0.2, maths_progress: 1.1,
disadvantaged_pct: 24.5, eal_pct: 22.1, sen_support_pct: 13.2,
total_pupils: 60,
}),
],
absenceData: absence,
ofsted: makeOfsted({
framework: 'OEIF',
inspection_date: '2023-05-17',
inspection_type: 'Section 5',
overall_effectiveness: 2,
quality_of_education: 2,
behaviour_attitudes: 1,
personal_development: 2,
leadership_management: 2,
previous_overall: 3,
grade_source: 'graded',
}),
census,
admissions: makeAdmissions({ year: 2024 }),
admissionsHistory: [
makeAdmissions({ year: 2022, first_preference_offer_pct: 71 }),
makeAdmissions({ year: 2023, first_preference_offer_pct: 65 }),
makeAdmissions({ year: 2024, first_preference_offer_pct: 60 }),
],
deprivation,
finance,
};
// ---------------------------------------------------------------------------
// Fixture 2: secondary — KS4 results, Report Card Ofsted, single admissions year
// ---------------------------------------------------------------------------
export const secondaryFixture = {
schoolInfo: makeSchool({
urn: 234567,
school_name: 'Test Secondary School',
phase: 'Secondary',
age_range: '11-16',
school_type: 'Academy converter',
school_type_code: 'AC',
}),
yearlyData: [
makeResult({ year: 2023, attainment_8_score: 44.8, progress_8_score: 0.18 }),
makeResult({
year: 2024,
attainment_8_score: 46.2, progress_8_score: 0.31,
progress_8_banding: 'Above average',
english_maths_standard_pass_pct: 68.4,
english_maths_strong_pass_pct: 47.1,
ebacc_entry_pct: 41.2, ebacc_standard_pass_pct: 28.6,
ebacc_strong_pass_pct: 19.4, ebacc_avg_score: 4.31,
disadvantaged_pct: 27.8, eal_pct: 19.3, sen_support_pct: 11.7,
}),
],
absenceData: absence,
ofsted: makeOfsted({
framework: 'ReportCard',
inspection_date: '2025-11-20',
rc_inspection_date: '2025-11-20',
rc_safeguarding_met: true,
rc_inclusion: 2,
rc_curriculum_teaching: 2,
rc_achievement: 3,
rc_attendance_behaviour: 2,
rc_personal_development: 1,
rc_leadership_governance: 2,
}),
census,
admissions: makeAdmissions({ year: 2024, school_phase: 'Secondary' }),
admissionsHistory: [makeAdmissions({ year: 2024, school_phase: 'Secondary' })],
deprivation,
finance,
};
// ---------------------------------------------------------------------------
// Fixture 3: all-through — BOTH key stages on the same row.
// The highest-value fixture: isAllThrough gates the KS2 content back on for a
// school that also has KS4 data, and this is the case most likely to break.
// ---------------------------------------------------------------------------
export const allThroughFixture = {
schoolInfo: makeSchool({
urn: 345678,
school_name: 'Test All-Through School',
phase: 'All-through',
age_range: '4-18',
has_sixth_form: true,
}),
yearlyData: [
makeResult({ year: 2023, rwm_expected_pct: 55, attainment_8_score: 45.0 }),
makeResult({
year: 2024,
rwm_expected_pct: 63, reading_expected_pct: 76,
writing_expected_pct: 70, maths_expected_pct: 71,
attainment_8_score: 48.7, progress_8_score: 0.42,
english_maths_standard_pass_pct: 71.2,
disadvantaged_pct: 20.1, eal_pct: 17.5, sen_support_pct: 9.8,
}),
],
absenceData: absence,
ofsted: makeOfsted({
framework: 'OEIF',
inspection_date: '2022-10-04',
inspection_type: 'Section 5',
overall_effectiveness: 1,
quality_of_education: 1,
behaviour_attitudes: 1,
personal_development: 1,
leadership_management: 1,
grade_source: 'graded',
}),
census,
admissions: makeAdmissions({ year: 2024, school_phase: 'Secondary' }),
admissionsHistory: [
makeAdmissions({ year: 2023, school_phase: 'Secondary', first_preference_offer_pct: 58 }),
makeAdmissions({ year: 2024, school_phase: 'Secondary', first_preference_offer_pct: 52 }),
],
deprivation,
finance,
};
// ---------------------------------------------------------------------------
// Fixture 4: special school — all-zero KS2 row.
// Guards the PR #70 regression: special/PRU/AP schools must not be shown an
// England comparison, because their pupils sit the same tests but very few
// reach the mainstream "expected standard".
// ---------------------------------------------------------------------------
export const specialFixture = {
schoolInfo: makeSchool({
urn: 456789,
school_name: 'Test Special School',
school_type: 'Community special school',
school_type_code: 'CYS',
age_range: '2-19',
}),
yearlyData: [
makeResult({
year: 2024,
rwm_expected_pct: 0,
reading_expected_pct: 0,
writing_expected_pct: 0,
maths_expected_pct: 0,
total_pupils: 12,
}),
],
absenceData: absence,
ofsted: null,
census,
admissions: null,
admissionsHistory: [],
deprivation,
finance,
};
+17
View File
@@ -27,6 +27,23 @@ Object.defineProperty(window, 'matchMedia', {
})),
});
// jsdom implements neither of these; the school detail views use both
// (IntersectionObserver for the scroll-spy and the hero-CTA tracker,
// scrollTo for the "back to top" control).
global.IntersectionObserver = class {
constructor(callback) {
this.callback = callback;
}
observe() {}
unobserve() {}
disconnect() {}
takeRecords() {
return [];
}
};
window.scrollTo = jest.fn();
// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),