PR Checks / Frontend Typecheck + Tests (pull_request) Canceled after 9s
PR Checks / Backend Smoke (pull_request) Canceled after 0s
PR Checks / Build Backend (no push) (pull_request) Canceled after 0s
PR Checks / Build Frontend (no push) (pull_request) Canceled after 0s
PR Checks / Build Pipeline (no push) (pull_request) Canceled after 0s
PR Checks / AI Code Review (Claude) (pull_request) Canceled after 0s
GIAS phase 6 is "16 plus", and PHASE_GROUPS deliberately files it in the secondary group. The payload helper decided the same question with `"secondary" in phase_text`, which that value does not satisfy — so a sixth-form college was handed the primary bucket and offered infant schools as its peers, with the KS2 metric key to label them. No crash; just a page confidently showing the wrong schools. The decision now lives in similar_schools.is_secondary_phase, beside the PHASE_GROUPS bucket it selects from, so the two cannot drift again. A test pins them together. The same binary assumption had a second output. computeSchoolFlags tests for the substring too, so a 16-plus school renders the primary template, and the composer was labelling the section from the template: "Other primary schools near <sixth form college>" above a row of secondaries. The section now derives its noun from the school's own phase, which also removes the duplicated wording from both composers. A 16-plus school's candidates span the whole secondary group, so no single noun fits and it gets the honest general one. Reported in review on #150. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
173 lines
6.2 KiB
TypeScript
173 lines
6.2 KiB
TypeScript
/**
|
|
* The section's job is to be honest about what it matched. These tests pin the
|
|
* ways it could lie: rendering below the minimum, claiming a similar intake at
|
|
* tier 3, showing a missing figure as a number, or hiding a card behind an
|
|
* arrow where a crawler cannot reach it.
|
|
*/
|
|
|
|
import { render, screen } from '@testing-library/react';
|
|
|
|
import {
|
|
nearbyNoun,
|
|
SimilarSchoolsSection,
|
|
shouldRenderSimilar,
|
|
} from '@/components/school/SimilarSchoolsSection';
|
|
import type { SimilarSchool } from '@/lib/types';
|
|
|
|
jest.mock('@/components/school/AddToCompareButton', () => ({
|
|
AddToCompareButton: ({ school }: { school: SimilarSchool }) => (
|
|
<button type="button">Add {school.school_name} to compare</button>
|
|
),
|
|
}));
|
|
|
|
jest.mock('@/components/school/SimilarSchoolsCompareBar', () => ({
|
|
SimilarSchoolsCompareBar: ({ thisUrn }: { thisUrn: number }) => (
|
|
<div data-testid="compare-bar">bar for {thisUrn}</div>
|
|
),
|
|
}));
|
|
|
|
function school(overrides: Partial<SimilarSchool> = {}): SimilarSchool {
|
|
return {
|
|
urn: 100002,
|
|
school_name: 'Willow Lane Primary School',
|
|
distance_miles: 0.6,
|
|
school_type: 'Community school',
|
|
age_range: '4-11',
|
|
shared: ['Mixed', 'No religious character'],
|
|
tier: 1,
|
|
metric_value: 74,
|
|
metric_key: 'rwm_expected_pct',
|
|
metric_year: 202425,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function renderSection(similar: SimilarSchool[]) {
|
|
return render(
|
|
<SimilarSchoolsSection
|
|
urn={100001}
|
|
schoolName="Meadowbrook Primary School"
|
|
phase="Primary"
|
|
thisMetricValue={72}
|
|
similar={similar}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
describe('render gates', () => {
|
|
it.each([
|
|
['undefined', undefined],
|
|
['null', null],
|
|
['empty', []],
|
|
['a single school', [school()]],
|
|
])('renders nothing for %s', (_label, value) => {
|
|
expect(shouldRenderSimilar(value as SimilarSchool[] | null | undefined)).toBe(false);
|
|
});
|
|
|
|
it('renders for two or more schools', () => {
|
|
expect(shouldRenderSimilar([school(), school({ urn: 100003 })])).toBe(true);
|
|
});
|
|
|
|
it('returns null rather than an empty shell below the minimum', () => {
|
|
const { container } = renderSection([school()]);
|
|
expect(container).toBeEmptyDOMElement();
|
|
});
|
|
});
|
|
|
|
describe('the claim the lede makes', () => {
|
|
it('claims a similar intake when every card is tier 1 or 2', () => {
|
|
renderSection([school({ tier: 1 }), school({ urn: 100003, tier: 2 })]);
|
|
expect(screen.getByText(/with a similar intake/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('drops the claim when any card is tier 3', () => {
|
|
renderSection([school({ tier: 1 }), school({ urn: 100003, tier: 3, shared: ['Primary school'] })]);
|
|
expect(screen.queryByText(/with a similar intake/i)).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('what the lede calls the set', () => {
|
|
it.each([
|
|
['Primary', 'primary schools'],
|
|
['Middle deemed primary', 'primary schools'],
|
|
['Secondary', 'secondary schools'],
|
|
['Middle deemed secondary', 'secondary schools'],
|
|
['All-through', 'all-through schools'],
|
|
// GIAS phase 6. Its candidates span the whole secondary group, so no
|
|
// single noun fits and it takes the honest general one.
|
|
['16 plus', 'schools and colleges'],
|
|
['', 'schools'],
|
|
[null, 'schools'],
|
|
])('calls a %s school\'s neighbours "%s"', (phase, expected) => {
|
|
expect(nearbyNoun(phase)).toBe(expected);
|
|
});
|
|
|
|
it('never calls a sixth form college\'s neighbours primary schools', () => {
|
|
render(
|
|
<SimilarSchoolsSection
|
|
urn={100001}
|
|
schoolName="Barnet Sixth Form College"
|
|
phase="16 plus"
|
|
thisMetricValue={null}
|
|
similar={[school(), school({ urn: 100003 })]}
|
|
/>,
|
|
);
|
|
expect(screen.getByText(/Other schools and colleges near Barnet Sixth Form College/)).toBeInTheDocument();
|
|
expect(screen.queryByText(/primary schools/)).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('cards', () => {
|
|
it('links each school to its canonical slug', () => {
|
|
renderSection([school(), school({ urn: 100003, school_name: 'Oakfield Primary School' })]);
|
|
const link = screen.getByRole('link', { name: /Willow Lane Primary School/ });
|
|
expect(link).toHaveAttribute('href', '/school/100002-willow-lane-primary-school');
|
|
});
|
|
|
|
it('shows the distance and the shared characteristics', () => {
|
|
renderSection([school(), school({ urn: 100003 })]);
|
|
expect(screen.getAllByText('0.6 miles away').length).toBeGreaterThan(0);
|
|
expect(screen.getAllByText('Mixed').length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('renders a missing figure as "Not published", never as a number', () => {
|
|
renderSection([school({ metric_value: null }), school({ urn: 100003 })]);
|
|
expect(screen.getByText('Not published')).toBeInTheDocument();
|
|
});
|
|
|
|
it('anchors each figure against this school', () => {
|
|
renderSection([school(), school({ urn: 100003 })]);
|
|
expect(screen.getAllByText('72% at this school').length).toBe(2);
|
|
});
|
|
|
|
it('offers the compare bar once, for this school', () => {
|
|
renderSection([school(), school({ urn: 100003 })]);
|
|
expect(screen.getByTestId('compare-bar')).toHaveTextContent('bar for 100001');
|
|
});
|
|
|
|
it('keeps every card in the DOM, including the ones scrolled out of view', () => {
|
|
const six = Array.from({ length: 6 }, (_, n) =>
|
|
school({ urn: 100002 + n, school_name: `Peer ${n} School` }),
|
|
);
|
|
renderSection(six);
|
|
expect(screen.getAllByRole('link', { name: /Peer \d School/ })).toHaveLength(6);
|
|
});
|
|
|
|
it('offers no arrows when three cards fit the row', () => {
|
|
renderSection([school(), school({ urn: 100003 }), school({ urn: 100004 })]);
|
|
expect(screen.queryByRole('button', { name: /More schools/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('offers arrows once there is a fourth school', () => {
|
|
renderSection(Array.from({ length: 4 }, (_, n) => school({ urn: 100002 + n })));
|
|
expect(screen.getByRole('button', { name: /More schools/ })).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: /Previous schools/ })).toBeInTheDocument();
|
|
});
|
|
|
|
it('says distances are straight-line, and offers no method panel', () => {
|
|
const { container } = renderSection([school(), school({ urn: 100003 })]);
|
|
expect(screen.getByText(/straight-line from this school/i)).toBeInTheDocument();
|
|
expect(container.querySelector('details')).toBeNull();
|
|
});
|
|
});
|