From b0c5b6bb57c879477da12c37b15cff950c29ebd3 Mon Sep 17 00:00:00 2001 From: Tudor Date: Sat, 18 Jul 2026 08:26:59 +0100 Subject: [PATCH] fix(compare): anchor grade-5 & EBacc rows; restore secondary-button outline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SF1 (expert sign-off): the secondary academics 'Grade 5+ in English & maths' and 'EBacc entry' rows rendered as bare numbers — the one place the 'every number anchored against the England average' promise broke. Both now show the official England anchor (already in the payload) + an Above/Close/Below verdict chip, matching Attainment 8 (which also gains a chip for consistency). Button affordance: globals.css has a duplicate .btn block whose 'border: none' overrode the base '1px solid transparent', so .btn-secondary/.btn-active's 'border-color' had no width — every outline button (the modal's '+ Compare', search-result '+ Compare', 'Comparing') rendered as borderless teal text. Give the outline variants the full 'border' shorthand so the outline renders regardless of the clobbered base. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0146VHeLAWjDVE2B5uU67jCB --- .../components/CompareAcademics.test.tsx | 65 +++++++++++++++++++ nextjs-app/app/globals.css | 11 +++- .../components/compare/CompareAcademics.tsx | 46 ++++++++++++- 3 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 nextjs-app/__tests__/components/CompareAcademics.test.tsx diff --git a/nextjs-app/__tests__/components/CompareAcademics.test.tsx b/nextjs-app/__tests__/components/CompareAcademics.test.tsx new file mode 100644 index 0000000..b748755 --- /dev/null +++ b/nextjs-app/__tests__/components/CompareAcademics.test.tsx @@ -0,0 +1,65 @@ +/** + * Secondary academics: every headline number carries its England anchor and a + * verdict chip (expert sign-off SF1 — the grade-5 and EBacc rows previously + * rendered as bare numbers, breaking the "anchored against England" promise). + */ + +import { render, screen, within } from '@testing-library/react'; + +import { CompareAcademics } from '@/components/compare/CompareAcademics'; +import type { ComparisonData, NationalAverages, School } from '@/lib/types'; + +function school(urn: number, name: string): School { + return { urn, school_name: name, attainment_8_score: 58.7 } as School; +} + +function data(urn: number): ComparisonData { + return { + school_info: school(urn, 'Test High'), + yearly_data: [ + { + year: 202425, + attainment_8_score: 58.7, + english_maths_strong_pass_pct: 30, + ebacc_entry_pct: 10, + }, + ] as ComparisonData['yearly_data'], + ofsted: null, + census: null, + admissions: null, + admissions_history: [], + deprivation: null, + }; +} + +const NATIONAL: NationalAverages = { + year: 202425, + primary: {}, + secondary: { + attainment_8_score: 46.0, + english_maths_strong_pass_pct: 45.4, + ebacc_entry_pct: 40.5, + }, + by_year: [], +}; + +test('grade-5 and EBacc rows show the England anchor and a Below chip when under it', () => { + const s = school(137086, 'Bishop Stopford School'); + render( + , + ); + + // The official anchors appear (45.4% and 40.5%), not just the school numbers. + expect(screen.getByText(/England average 45%/)).toBeInTheDocument(); + expect(screen.getByText(/England average 41%/)).toBeInTheDocument(); + + // 30% grade-5 and 10% EBacc are both well below their anchors → Below chips. + // Attainment 8 (58.7 vs 46.0) is above → at least one "Above" chip too. + expect(screen.getAllByText(/Below England average/).length).toBeGreaterThanOrEqual(2); + expect(screen.getAllByText(/Above England average/).length).toBeGreaterThanOrEqual(1); +}); diff --git a/nextjs-app/app/globals.css b/nextjs-app/app/globals.css index 8eb0779..1b66d32 100644 --- a/nextjs-app/app/globals.css +++ b/nextjs-app/app/globals.css @@ -183,10 +183,15 @@ body { } /* Secondary: teal outline — supporting actions (+ Compare) */ +/* NOTE: a duplicate `.btn` block further down this file sets `border: none`, + which wins over the base `.btn`'s `1px solid transparent`. The outline + variants below therefore declare the full `border` shorthand explicitly so + they don't depend on the base border-width — otherwise `border-color` alone + has no width and the outline never renders (buttons read as plain text). */ .btn-secondary { background: transparent; color: var(--accent-teal); - border-color: var(--accent-teal); + border: 1px solid var(--accent-teal); } .btn-secondary:hover:not(:disabled) { background: var(--accent-teal-bg); @@ -196,7 +201,7 @@ body { .btn-tertiary { background: var(--bg-secondary); color: var(--text-secondary); - border-color: var(--border-color); + border: 1px solid var(--border-color); } .btn-tertiary:hover:not(:disabled) { background: var(--border-color); @@ -207,7 +212,7 @@ body { .btn-active { background: var(--accent-teal-bg); color: var(--accent-teal); - border-color: var(--accent-teal); + border: 1px solid var(--accent-teal); } .btn-active:hover:not(:disabled) { background: transparent; diff --git a/nextjs-app/components/compare/CompareAcademics.tsx b/nextjs-app/components/compare/CompareAcademics.tsx index 08de1f0..6660a81 100644 --- a/nextjs-app/components/compare/CompareAcademics.tsx +++ b/nextjs-app/components/compare/CompareAcademics.tsx @@ -162,6 +162,21 @@ export function CompareAcademics({ const grade5 = latestValues(data, urns, 'english_maths_strong_pass_pct'); const ebacc = latestValues(data, urns, 'ebacc_entry_pct'); const att8Anchor = nationalAverages?.secondary?.attainment_8_score; + const grade5Anchor = nationalAverages?.secondary?.english_maths_strong_pass_pct; + const ebaccAnchor = nationalAverages?.secondary?.ebacc_entry_pct; + + // Every headline number gets its England anchor + verdict chip, so the + // "anchored against the England average" promise holds for the grade-5 + // and EBacc rows too, not just Attainment 8. + const anchorChip = (value: number | null, anchor: number | null | undefined, tol: number) => { + if (value == null || anchor == null) return null; + const v = verdict(value, anchor, tol); + return ( + + {v === 'above' ? 'Above' : v === 'below' ? 'Below' : 'Close to'} England average + + ); + }; return (
{att8[i] != null ? ( <> - {(att8[i] as number).toFixed(1)} + {(att8[i] as number).toFixed(1)}{' '} + {anchorChip(att8[i], att8Anchor, 2)} {att8Anchor != null && ( England average {att8Anchor.toFixed(1)} )} @@ -216,14 +232,38 @@ export function CompareAcademics({ {schools.map((school, i) => ( - {grade5[i] != null ? `${Math.round(grade5[i] as number)}%` : No data} + {grade5[i] != null ? ( + <> + + {Math.round(grade5[i] as number)}% + {' '} + {anchorChip(grade5[i], grade5Anchor, 3)} + {grade5Anchor != null && ( + England average {Math.round(grade5Anchor)}% + )} + + ) : ( + No data + )} ))} EBacc entry {schools.map((school, i) => ( - {ebacc[i] != null ? `${Math.round(ebacc[i] as number)}%` : No data} + {ebacc[i] != null ? ( + <> + + {Math.round(ebacc[i] as number)}% + {' '} + {anchorChip(ebacc[i], ebaccAnchor, 3)} + {ebaccAnchor != null && ( + England average {Math.round(ebaccAnchor)}% + )} + + ) : ( + No data + )} ))}