Server-rendered cards inside a client carousel that scrolls rather than paginates, so all six links stay in the initial HTML and the row still works with JavaScript off. Three client islands, split by what each needs: a school, the whole selection, a DOM ref. The lede claims a similar intake only when no card came from tier 3, chips list what a school actually shares, a missing figure reads "Not published", and the neighbour's number carries no valence colour — green and terracotta mean "against England" everywhere else, and colouring it here would read as ranking the neighbours. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
5.1 KiB
TypeScript
141 lines
5.1 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 {
|
|
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"
|
|
phaseNoun="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('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();
|
|
});
|
|
});
|