2026-09-21 22:41:40 +01:00
|
|
|
/**
|
2026-09-22 11:58:36 +01:00
|
|
|
* The section's job is to be honest about what it is showing. These tests pin
|
|
|
|
|
* the ways it could mislead: rendering below the minimum, claiming a likeness
|
|
|
|
|
* it does not rank on, showing a missing figure as a number, or hiding a card
|
|
|
|
|
* behind an arrow where a crawler cannot reach it.
|
2026-09-21 22:41:40 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { render, screen } from '@testing-library/react';
|
|
|
|
|
|
|
|
|
|
import {
|
2026-09-22 06:41:49 +01:00
|
|
|
nearbyNoun,
|
2026-09-22 11:59:27 +01:00
|
|
|
NearbySchoolsSection,
|
|
|
|
|
shouldRenderNearby,
|
|
|
|
|
} from '@/components/school/NearbySchoolsSection';
|
|
|
|
|
import type { NearbySchool } from '@/lib/types';
|
2026-09-21 22:41:40 +01:00
|
|
|
|
|
|
|
|
jest.mock('@/components/school/AddToCompareButton', () => ({
|
2026-09-22 11:59:27 +01:00
|
|
|
AddToCompareButton: ({ school }: { school: NearbySchool }) => (
|
2026-09-21 22:41:40 +01:00
|
|
|
<button type="button">Add {school.school_name} to compare</button>
|
|
|
|
|
),
|
|
|
|
|
}));
|
|
|
|
|
|
2026-09-22 11:59:27 +01:00
|
|
|
jest.mock('@/components/school/NearbySchoolsCompareBar', () => ({
|
|
|
|
|
NearbySchoolsCompareBar: ({ thisUrn }: { thisUrn: number }) => (
|
2026-09-21 22:41:40 +01:00
|
|
|
<div data-testid="compare-bar">bar for {thisUrn}</div>
|
|
|
|
|
),
|
|
|
|
|
}));
|
|
|
|
|
|
2026-09-22 11:59:27 +01:00
|
|
|
function school(overrides: Partial<NearbySchool> = {}): NearbySchool {
|
2026-09-21 22:41:40 +01:00
|
|
|
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'],
|
|
|
|
|
metric_value: 74,
|
|
|
|
|
metric_key: 'rwm_expected_pct',
|
|
|
|
|
metric_year: 202425,
|
|
|
|
|
...overrides,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-22 11:59:27 +01:00
|
|
|
function renderSection(nearby: NearbySchool[]) {
|
2026-09-21 22:41:40 +01:00
|
|
|
return render(
|
2026-09-22 11:59:27 +01:00
|
|
|
<NearbySchoolsSection
|
2026-09-21 22:41:40 +01:00
|
|
|
urn={100001}
|
|
|
|
|
schoolName="Meadowbrook Primary School"
|
2026-09-22 06:41:49 +01:00
|
|
|
phase="Primary"
|
2026-09-21 22:41:40 +01:00
|
|
|
thisMetricValue={72}
|
2026-09-22 11:59:27 +01:00
|
|
|
nearby={nearby}
|
2026-09-21 22:41:40 +01:00
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('render gates', () => {
|
|
|
|
|
it.each([
|
|
|
|
|
['undefined', undefined],
|
|
|
|
|
['null', null],
|
|
|
|
|
['empty', []],
|
|
|
|
|
['a single school', [school()]],
|
|
|
|
|
])('renders nothing for %s', (_label, value) => {
|
2026-09-22 11:59:27 +01:00
|
|
|
expect(shouldRenderNearby(value as NearbySchool[] | null | undefined)).toBe(false);
|
2026-09-21 22:41:40 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders for two or more schools', () => {
|
2026-09-22 11:59:27 +01:00
|
|
|
expect(shouldRenderNearby([school(), school({ urn: 100003 })])).toBe(true);
|
2026-09-21 22:41:40 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns null rather than an empty shell below the minimum', () => {
|
|
|
|
|
const { container } = renderSection([school()]);
|
|
|
|
|
expect(container).toBeEmptyDOMElement();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-22 11:58:36 +01:00
|
|
|
describe('what the section claims', () => {
|
|
|
|
|
it('never claims a similar intake, because it does not rank on one', () => {
|
|
|
|
|
renderSection([school(), school({ urn: 100003, shared: [] })]);
|
|
|
|
|
expect(screen.queryByText(/similar intake/i)).not.toBeInTheDocument();
|
2026-09-21 22:41:40 +01:00
|
|
|
});
|
|
|
|
|
|
2026-09-22 11:58:36 +01:00
|
|
|
it('is headed "Other schools nearby", not "similar"', () => {
|
|
|
|
|
renderSection([school(), school({ urn: 100003 })]);
|
|
|
|
|
expect(screen.getByRole('heading', { name: 'Other schools nearby' })).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows chips for what is shared', () => {
|
|
|
|
|
renderSection([school({ shared: ['Mixed', 'Roman Catholic'] }), school({ urn: 100003 })]);
|
|
|
|
|
expect(screen.getAllByText('Roman Catholic').length).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows no chips at all when nothing is shared, rather than inventing one', () => {
|
|
|
|
|
const { container } = render(
|
2026-09-22 11:59:27 +01:00
|
|
|
<NearbySchoolsSection
|
2026-09-22 11:58:36 +01:00
|
|
|
urn={100001}
|
|
|
|
|
schoolName="Meadowbrook Primary School"
|
|
|
|
|
phase="Primary"
|
|
|
|
|
thisMetricValue={72}
|
2026-09-22 11:59:27 +01:00
|
|
|
nearby={[school({ shared: [] }), school({ urn: 100003, shared: [] })]}
|
2026-09-22 11:58:36 +01:00
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
// The card still carries its distance, name, type and figure — just no
|
|
|
|
|
// claim of likeness.
|
|
|
|
|
expect(container.querySelectorAll('li ul').length).toBe(0);
|
|
|
|
|
expect(screen.getAllByText(/miles away/).length).toBe(2);
|
2026-09-21 22:41:40 +01:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-22 06:41:49 +01:00
|
|
|
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(
|
2026-09-22 11:59:27 +01:00
|
|
|
<NearbySchoolsSection
|
2026-09-22 06:41:49 +01:00
|
|
|
urn={100001}
|
|
|
|
|
schoolName="Barnet Sixth Form College"
|
|
|
|
|
phase="16 plus"
|
|
|
|
|
thisMetricValue={null}
|
2026-09-22 11:59:27 +01:00
|
|
|
nearby={[school(), school({ urn: 100003 })]}
|
2026-09-22 06:41:49 +01:00
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
expect(screen.getByText(/Other schools and colleges near Barnet Sixth Form College/)).toBeInTheDocument();
|
|
|
|
|
expect(screen.queryByText(/primary schools/)).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-21 22:41:40 +01:00
|
|
|
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();
|
|
|
|
|
});
|
|
|
|
|
});
|