Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce422e6436 | ||
|
|
2ac26acf91 | ||
|
|
3e77057567 | ||
|
|
e0d5a9969e | ||
|
|
dc21e80a5e | ||
|
|
6d3a203699 | ||
|
|
b0c5b6bb57 | ||
|
|
a1128bd801 |
@@ -67,9 +67,12 @@ jobs:
|
|||||||
echo "Promoting $FULL_SHA (images tagged $SHORT_SHA)"
|
echo "Promoting $FULL_SHA (images tagged $SHORT_SHA)"
|
||||||
|
|
||||||
- name: Verify the staging E2E gate passed for this commit
|
- name: Verify the staging E2E gate passed for this commit
|
||||||
|
# Use the built-in Actions token (GITHUB_TOKEN is the documented name;
|
||||||
|
# it carries repository read scope), NOT REGISTRY_TOKEN — the registry
|
||||||
|
# token has no repo scope, so the commit-status API returns 403.
|
||||||
run: |
|
run: |
|
||||||
STATUS_JSON=$(curl -fsS \
|
STATUS_JSON=$(curl -fsS \
|
||||||
-H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
"https://${REGISTRY}/api/v1/repos/${{ gitea.repository }}/commits/${{ steps.resolve.outputs.full }}/status")
|
"https://${REGISTRY}/api/v1/repos/${{ gitea.repository }}/commits/${{ steps.resolve.outputs.full }}/status")
|
||||||
echo "$STATUS_JSON" | python3 -c "
|
echo "$STATUS_JSON" | python3 -c "
|
||||||
import json, sys
|
import json, sys
|
||||||
|
|||||||
@@ -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(
|
||||||
|
<CompareAcademics
|
||||||
|
schools={[s]}
|
||||||
|
data={{ '137086': data(137086) }}
|
||||||
|
nationalAverages={NATIONAL}
|
||||||
|
isSecondary
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
@@ -183,10 +183,15 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Secondary: teal outline — supporting actions (+ Compare) */
|
/* 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 {
|
.btn-secondary {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--accent-teal);
|
color: var(--accent-teal);
|
||||||
border-color: var(--accent-teal);
|
border: 1px solid var(--accent-teal);
|
||||||
}
|
}
|
||||||
.btn-secondary:hover:not(:disabled) {
|
.btn-secondary:hover:not(:disabled) {
|
||||||
background: var(--accent-teal-bg);
|
background: var(--accent-teal-bg);
|
||||||
@@ -196,7 +201,7 @@ body {
|
|||||||
.btn-tertiary {
|
.btn-tertiary {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
border-color: var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
.btn-tertiary:hover:not(:disabled) {
|
.btn-tertiary:hover:not(:disabled) {
|
||||||
background: var(--border-color);
|
background: var(--border-color);
|
||||||
@@ -207,7 +212,7 @@ body {
|
|||||||
.btn-active {
|
.btn-active {
|
||||||
background: var(--accent-teal-bg);
|
background: var(--accent-teal-bg);
|
||||||
color: var(--accent-teal);
|
color: var(--accent-teal);
|
||||||
border-color: var(--accent-teal);
|
border: 1px solid var(--accent-teal);
|
||||||
}
|
}
|
||||||
.btn-active:hover:not(:disabled) {
|
.btn-active:hover:not(:disabled) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@@ -162,6 +162,21 @@ export function CompareAcademics({
|
|||||||
const grade5 = latestValues(data, urns, 'english_maths_strong_pass_pct');
|
const grade5 = latestValues(data, urns, 'english_maths_strong_pass_pct');
|
||||||
const ebacc = latestValues(data, urns, 'ebacc_entry_pct');
|
const ebacc = latestValues(data, urns, 'ebacc_entry_pct');
|
||||||
const att8Anchor = nationalAverages?.secondary?.attainment_8_score;
|
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 (
|
||||||
|
<Chip tone={v === 'above' ? 'good' : v === 'below' ? 'warn' : 'neutral'}>
|
||||||
|
{v === 'above' ? 'Above' : v === 'below' ? 'Below' : 'Close to'} England average
|
||||||
|
</Chip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
@@ -174,7 +189,8 @@ export function CompareAcademics({
|
|||||||
<Cell key={school.urn} school={school} index={i}>
|
<Cell key={school.urn} school={school} index={i}>
|
||||||
{att8[i] != null ? (
|
{att8[i] != null ? (
|
||||||
<>
|
<>
|
||||||
<span className={s.big}>{(att8[i] as number).toFixed(1)}</span>
|
<span className={s.big}>{(att8[i] as number).toFixed(1)}</span>{' '}
|
||||||
|
{anchorChip(att8[i], att8Anchor, 2)}
|
||||||
{att8Anchor != null && (
|
{att8Anchor != null && (
|
||||||
<span className={s.small}>England average {att8Anchor.toFixed(1)}</span>
|
<span className={s.small}>England average {att8Anchor.toFixed(1)}</span>
|
||||||
)}
|
)}
|
||||||
@@ -216,14 +232,38 @@ export function CompareAcademics({
|
|||||||
</RowLabel>
|
</RowLabel>
|
||||||
{schools.map((school, i) => (
|
{schools.map((school, i) => (
|
||||||
<Cell key={school.urn} school={school} index={i}>
|
<Cell key={school.urn} school={school} index={i}>
|
||||||
{grade5[i] != null ? `${Math.round(grade5[i] as number)}%` : <span className={s.small}>No data</span>}
|
{grade5[i] != null ? (
|
||||||
|
<>
|
||||||
|
<span className={s.big} style={{ fontSize: '1.1rem' }}>
|
||||||
|
{Math.round(grade5[i] as number)}%
|
||||||
|
</span>{' '}
|
||||||
|
{anchorChip(grade5[i], grade5Anchor, 3)}
|
||||||
|
{grade5Anchor != null && (
|
||||||
|
<span className={s.small}>England average {Math.round(grade5Anchor)}%</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className={s.small}>No data</span>
|
||||||
|
)}
|
||||||
</Cell>
|
</Cell>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<RowLabel tip="% entering the English Baccalaureate subject combination.">EBacc entry</RowLabel>
|
<RowLabel tip="% entering the English Baccalaureate subject combination.">EBacc entry</RowLabel>
|
||||||
{schools.map((school, i) => (
|
{schools.map((school, i) => (
|
||||||
<Cell key={school.urn} school={school} index={i}>
|
<Cell key={school.urn} school={school} index={i}>
|
||||||
{ebacc[i] != null ? `${Math.round(ebacc[i] as number)}%` : <span className={s.small}>No data</span>}
|
{ebacc[i] != null ? (
|
||||||
|
<>
|
||||||
|
<span className={s.big} style={{ fontSize: '1.1rem' }}>
|
||||||
|
{Math.round(ebacc[i] as number)}%
|
||||||
|
</span>{' '}
|
||||||
|
{anchorChip(ebacc[i], ebaccAnchor, 3)}
|
||||||
|
{ebaccAnchor != null && (
|
||||||
|
<span className={s.small}>England average {Math.round(ebaccAnchor)}%</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className={s.small}>No data</span>
|
||||||
|
)}
|
||||||
</Cell>
|
</Cell>
|
||||||
))}
|
))}
|
||||||
</SectionGrid>
|
</SectionGrid>
|
||||||
|
|||||||
@@ -239,8 +239,17 @@ export function CompareOfsted({
|
|||||||
`https://reports.ofsted.gov.uk/provider/21/${school.urn}`;
|
`https://reports.ofsted.gov.uk/provider/21/${school.urn}`;
|
||||||
return (
|
return (
|
||||||
<Cell key={school.urn} school={school} index={i}>
|
<Cell key={school.urn} school={school} index={i}>
|
||||||
<a className={s.link} href={url} target="_blank" rel="noopener noreferrer">
|
{/* Short visible label to save space on mobile (the coloured
|
||||||
{school.school_name}'s Ofsted page →
|
school name already leads the row); the full name stays in
|
||||||
|
aria-label so screen readers can tell the links apart. */}
|
||||||
|
<a
|
||||||
|
className={s.link}
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label={`${school.school_name}'s Ofsted page`}
|
||||||
|
>
|
||||||
|
Ofsted page →
|
||||||
</a>
|
</a>
|
||||||
</Cell>
|
</Cell>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -72,15 +72,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem;
|
gap: 0.4rem;
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
font-size: 0.8rem;
|
/* Slightly larger than the values below it so the school each row belongs
|
||||||
|
to is easy to read on mobile (hidden on desktop, where the column header
|
||||||
|
names the school). */
|
||||||
|
font-size: 0.9rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--sc, var(--text-secondary));
|
color: var(--sc, var(--text-secondary));
|
||||||
margin-bottom: 0.15rem;
|
margin-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cellDot {
|
.cellDot {
|
||||||
width: 9px;
|
width: 10px;
|
||||||
height: 9px;
|
height: 10px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--dot, var(--text-muted));
|
background: var(--dot, var(--text-muted));
|
||||||
flex: none;
|
flex: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user