PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m13s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 16s
GIAS NurseryProvision is text ("Has Nursery Classes", "No Nursery
Classes", "Not applicable"), but the header and the place table tested it
for truthiness. Every school with a value got a Nursery chip or a "Yes",
including secondaries aged 11-18. hasNurseryClasses() matches the one
value that means a nursery, and the type now says the field is a string.
The single-sex chip appended 's to the plural GIAS gender, giving
"Girls's school". singleSexLabel() gives "Girls' school" / "Boys' school".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
492 lines
22 KiB
TypeScript
492 lines
22 KiB
TypeScript
import { render, screen } from '@testing-library/react';
|
||
import { PlaceView } from '@/components/places/PlaceView';
|
||
import type { PlaceDetail } from '@/lib/places';
|
||
|
||
const detail: PlaceDetail = {
|
||
place: { kind: 'town', slug: 'brentwood', name: 'Brentwood', count: 29,
|
||
parent_authority: 'Essex', phases: ['primary'] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', rwm_expected_pct: 82,
|
||
ofsted_grade: 1, phase: 'Primary' } as never,
|
||
{ urn: 2, school_name: 'Beta Primary', rwm_expected_pct: 44,
|
||
ofsted_grade: 3, phase: 'Primary' } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 63, attainment_8_score: null },
|
||
};
|
||
|
||
describe('PlaceView', () => {
|
||
it('leads with an H1 that matches how the place is searched', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByRole('heading', { level: 1 }))
|
||
.toHaveTextContent(/primary schools in brentwood/i);
|
||
});
|
||
|
||
it('states the count so the page says something before the table', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByText(/29 schools/i)).toBeInTheDocument();
|
||
});
|
||
|
||
it('compares the local average against England, which a list cannot', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByTestId('local-vs-england')).toHaveTextContent('63');
|
||
expect(screen.getByTestId('local-vs-england')).toHaveTextContent('61');
|
||
});
|
||
|
||
it('links every school in scope, which is what de-orphans them', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getAllByRole('link', { name: /Primary$/ })).toHaveLength(2);
|
||
});
|
||
|
||
it('links to the parent authority so the place sits in a hierarchy', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByRole('link', { name: /Essex/i }))
|
||
.toHaveAttribute('href', '/schools/authority/essex');
|
||
});
|
||
|
||
it('shows the Ofsted distribution, not just a count of Outstanding', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByTestId('ofsted-distribution')).toBeInTheDocument();
|
||
});
|
||
|
||
it('links to neighbouring places so the page is not a dead end', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[{ kind: 'town', slug: 'romford', name: 'Romford', count: 40 }]} />);
|
||
expect(screen.getByRole('link', { name: /Romford/ }))
|
||
.toHaveAttribute('href', '/schools/romford');
|
||
});
|
||
|
||
it('says nothing about an average it does not have', () => {
|
||
render(<PlaceView detail={{ ...detail, averages:
|
||
{ rwm_expected_pct: null, attainment_8_score: null } }}
|
||
phase="primary" englandAverage={61} neighbours={[]} />);
|
||
expect(screen.queryByTestId('local-vs-england')).not.toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('PlaceView structured data', () => {
|
||
function jsonLd() {
|
||
const { container } = render(<PlaceView detail={detail} phase="primary"
|
||
englandAverage={61} neighbours={[]} />);
|
||
const el = container.querySelector('script[type="application/ld+json"]');
|
||
return JSON.parse(el!.textContent!);
|
||
}
|
||
|
||
it('declares the page as a ranked list, not prose', () => {
|
||
const types = jsonLd()['@graph'].map((n: { '@type': string }) => n['@type']);
|
||
expect(types).toContain('ItemList');
|
||
expect(types).toContain('BreadcrumbList');
|
||
});
|
||
|
||
it('gives every listed school an absolute URL on the canonical host', () => {
|
||
const list = jsonLd()['@graph'].find((n: { '@type': string }) => n['@type'] === 'ItemList');
|
||
expect(list.itemListElement).toHaveLength(2);
|
||
for (const item of list.itemListElement) {
|
||
expect(item.url).toMatch(/^https:\/\/www\.schoolcompare\.co\.uk\/school\//);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('PlaceView phase variants', () => {
|
||
it('links the phase variants that exist', () => {
|
||
render(<PlaceView detail={detail} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByRole('link', { name: /Primary schools in Brentwood/i }))
|
||
.toHaveAttribute('href', '/schools/brentwood/primary');
|
||
});
|
||
|
||
it('links no variant for a phase below its own threshold', () => {
|
||
render(<PlaceView detail={detail} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.queryByRole('link', { name: /Secondary schools in Brentwood/i }))
|
||
.not.toBeInTheDocument();
|
||
});
|
||
|
||
it('does not link sideways from a variant page to itself', () => {
|
||
render(<PlaceView detail={detail} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.queryByRole('link', { name: /Primary schools in Brentwood/i }))
|
||
.not.toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('PlaceView presentation', () => {
|
||
// /schools/brentwood shipped with 8 of 27 rows blank: an unphased page shows
|
||
// one primary-only measure for a list that also holds secondaries.
|
||
const mixed: PlaceDetail = {
|
||
place: { kind: 'town', slug: 'brentwood', name: 'Brentwood', count: 4,
|
||
parent_authority: 'Essex', phases: ['primary', 'secondary'] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', phase: 'Primary',
|
||
rwm_expected_pct: 82, attainment_8_score: null } as never,
|
||
{ urn: 2, school_name: 'Beta High', phase: 'Secondary',
|
||
rwm_expected_pct: null, attainment_8_score: 47 } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 63, attainment_8_score: 45 },
|
||
};
|
||
|
||
it('gives each phase its own table rather than one column of blanks', () => {
|
||
render(<PlaceView detail={mixed} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByRole('heading', { name: /^Primary schools/ })).toBeInTheDocument();
|
||
expect(screen.getByRole('heading', { name: /^Secondary schools/ })).toBeInTheDocument();
|
||
expect(screen.getByText('82%')).toBeInTheDocument();
|
||
expect(screen.getByText('47')).toBeInTheDocument();
|
||
});
|
||
|
||
it('names the measure in plain words, not jargon', () => {
|
||
// The first cut said "RWM expected", which appears nowhere else on the site.
|
||
render(<PlaceView detail={mixed} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByText('Reading, writing & maths')).toBeInTheDocument();
|
||
expect(screen.getByText('Attainment 8')).toBeInTheDocument();
|
||
expect(screen.queryByText(/RWM expected/i)).not.toBeInTheDocument();
|
||
});
|
||
|
||
it('says a missing result is unpublished rather than showing a bare dash', () => {
|
||
const noResult: PlaceDetail = {
|
||
...mixed,
|
||
schools: [{ urn: 3, school_name: 'New Primary', phase: 'Primary',
|
||
rwm_expected_pct: null, attainment_8_score: null } as never],
|
||
};
|
||
render(<PlaceView detail={noResult} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByText('Not published')).toBeInTheDocument();
|
||
});
|
||
|
||
it('styles school links to the site convention rather than browser default', () => {
|
||
const { container } = render(<PlaceView detail={mixed} englandAverage={61}
|
||
neighbours={[]} />);
|
||
const link = container.querySelector('a[href^="/school/"]');
|
||
expect(link?.className).toBeTruthy();
|
||
});
|
||
|
||
it('a phased page shows one table and no phase headings', () => {
|
||
render(<PlaceView detail={mixed} phase="primary" englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.queryByRole('heading', { name: /^Secondary schools/ }))
|
||
.not.toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('PlaceView table alignment', () => {
|
||
const aligned: PlaceDetail = {
|
||
place: { kind: 'town', slug: 'brentwood', name: 'Brentwood', count: 2,
|
||
parent_authority: 'Essex', phases: ['primary'] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', phase: 'Primary',
|
||
rwm_expected_pct: 82, attainment_8_score: null } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 63, attainment_8_score: null },
|
||
};
|
||
|
||
it('aligns the measure heading and its values with the same class', () => {
|
||
// They were aligned by two different selectors whose specificity did not
|
||
// match: `.table th:last-child` (0,2,1) won and went right, while `.num`
|
||
// (0,1,0) lost to `.table td` (0,1,1) and stayed left. Sharing one class
|
||
// is what makes them impossible to drift apart.
|
||
const { container } = render(<PlaceView detail={aligned} englandAverage={61}
|
||
neighbours={[]} />);
|
||
const th = container.querySelectorAll('th')[1];
|
||
const td = container.querySelectorAll('tbody td')[1];
|
||
expect(th.className).toBeTruthy();
|
||
expect(td.className).toBe(th.className);
|
||
});
|
||
|
||
it('leaves the school-name column unclassed so it takes the spare width', () => {
|
||
const { container } = render(<PlaceView detail={aligned} englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(container.querySelectorAll('th')[0].className).toBe('');
|
||
});
|
||
});
|
||
|
||
describe('PlaceView authorities', () => {
|
||
const straddling: PlaceDetail = {
|
||
place: { kind: 'outcode', slug: 'sw19', name: 'SW19', count: 33,
|
||
parent_authority: 'Merton', phases: ['primary'],
|
||
authorities: [
|
||
{ name: 'Merton', slug: 'merton', count: 26 },
|
||
{ name: 'Wandsworth', slug: 'wandsworth', count: 7 },
|
||
] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', phase: 'Primary',
|
||
rwm_expected_pct: 82, attainment_8_score: null } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 63, attainment_8_score: null },
|
||
};
|
||
|
||
it('names every authority the place straddles, not just the largest', () => {
|
||
// SW19 is mostly Merton but partly Wandsworth. Naming one asserts
|
||
// something false about a quarter of outcodes.
|
||
render(<PlaceView detail={straddling} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByRole('link', { name: 'Merton' }))
|
||
.toHaveAttribute('href', '/schools/authority/merton');
|
||
expect(screen.getByRole('link', { name: 'Wandsworth' }))
|
||
.toHaveAttribute('href', '/schools/authority/wandsworth');
|
||
});
|
||
|
||
it('joins them readably rather than as a bare list', () => {
|
||
// Asserted on the summary line's whole text: a loose /and/ matcher also
|
||
// hits "Wandsworth".
|
||
const { container } = render(<PlaceView detail={straddling}
|
||
englandAverage={61} neighbours={[]} />);
|
||
const summary = container.querySelector('header p');
|
||
expect(summary?.textContent).toContain('Merton and Wandsworth');
|
||
});
|
||
|
||
it('falls back to the single parent when the field is absent', () => {
|
||
// A cached API response predating the authorities field must not blank
|
||
// the line entirely.
|
||
const legacy = { ...straddling,
|
||
place: { ...straddling.place, authorities: undefined } };
|
||
render(<PlaceView detail={legacy} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByRole('link', { name: 'Merton' })).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('PlaceView list ordering', () => {
|
||
const detail3: PlaceDetail = {
|
||
place: { kind: 'town', slug: 'brentwood', name: 'Brentwood', count: 2,
|
||
parent_authority: 'Essex', phases: ['primary'] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', phase: 'Primary',
|
||
rwm_expected_pct: 40, attainment_8_score: null } as never,
|
||
{ urn: 2, school_name: 'Beta Primary', phase: 'Primary',
|
||
rwm_expected_pct: 90, attainment_8_score: null } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 65, attainment_8_score: null },
|
||
};
|
||
|
||
it('renders schools in the order the API sent them, not by score', () => {
|
||
// The API sorts alphabetically now; the component must not re-sort.
|
||
render(<PlaceView detail={detail3} englandAverage={61} neighbours={[]} />);
|
||
const links = screen.getAllByRole('link', { name: /Primary$/ });
|
||
expect(links.map((l) => l.textContent))
|
||
.toEqual(['Alpha Primary', 'Beta Primary']);
|
||
});
|
||
|
||
it('declares the list as ascending rather than implying a ranking', () => {
|
||
// An ItemList carrying `position` reads as a ranking unless it says
|
||
// otherwise, and the table is A-Z.
|
||
const { container } = render(<PlaceView detail={detail3} englandAverage={61}
|
||
neighbours={[]} />);
|
||
const ld = JSON.parse(
|
||
container.querySelector('script[type="application/ld+json"]')!.textContent!);
|
||
const list = ld['@graph'].find((n: { '@type': string }) => n['@type'] === 'ItemList');
|
||
expect(list.itemListOrder).toBe('https://schema.org/ItemListOrderAscending');
|
||
});
|
||
});
|
||
|
||
describe('PlaceView phase links', () => {
|
||
const authority: PlaceDetail = {
|
||
place: { kind: 'authority', slug: 'barnet', name: 'Barnet', count: 156,
|
||
parent_authority: null, phases: ['primary', 'secondary'] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', phase: 'Primary',
|
||
rwm_expected_pct: 82, attainment_8_score: null } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 63, attainment_8_score: null },
|
||
};
|
||
|
||
it('keeps an authority phase link in the authority namespace', () => {
|
||
// The link was built as `/schools/${slug}/${phase}` for every kind, so an
|
||
// authority page pointed into the town namespace. For 87 of 151
|
||
// authorities that 404'd; for the other 64 it silently landed on the town
|
||
// page of the same name — a different set of schools, and exactly the
|
||
// duplicate the two namespaces exist to prevent. Barnet is one of the 64.
|
||
render(<PlaceView detail={authority} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByRole('link', { name: /^Primary schools in Barnet$/ }))
|
||
.toHaveAttribute('href', '/schools/authority/barnet/primary');
|
||
expect(screen.getByRole('link', { name: /^Secondary schools in Barnet$/ }))
|
||
.toHaveAttribute('href', '/schools/authority/barnet/secondary');
|
||
});
|
||
|
||
it('still uses the bare namespace for a town', () => {
|
||
render(<PlaceView detail={detail} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByRole('link', { name: /^Primary schools in Brentwood$/ }))
|
||
.toHaveAttribute('href', '/schools/brentwood/primary');
|
||
});
|
||
|
||
it('offers no phase link when the place publishes none', () => {
|
||
// Outcodes are the case: no phase route exists for them, so the registry
|
||
// reports no phases and the nav does not render.
|
||
const outcode = { ...detail,
|
||
place: { ...detail.place, kind: 'outcode', slug: 'cm13', name: 'CM13',
|
||
phases: [] } };
|
||
render(<PlaceView detail={outcode} englandAverage={61} neighbours={[]} />);
|
||
expect(screen.queryByRole('navigation', { name: 'By phase' }))
|
||
.not.toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('PlaceView unlinkable authorities', () => {
|
||
const withUnpublished: PlaceDetail = {
|
||
place: { kind: 'outcode', slug: 'tr21', name: 'TR21', count: 8,
|
||
parent_authority: 'Cornwall', phases: [],
|
||
authorities: [
|
||
{ name: 'Cornwall', slug: 'cornwall', count: 6 },
|
||
{ name: 'Isles Of Scilly', slug: null, count: 2 },
|
||
] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', phase: 'Primary',
|
||
rwm_expected_pct: 82, attainment_8_score: null } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 63, attainment_8_score: null },
|
||
};
|
||
|
||
it('names an authority with no page without linking it', () => {
|
||
// City of London and the Isles of Scilly hold fewer schools than a page
|
||
// needs. Saying where the place is stays right; linking there would 404.
|
||
const { container } = render(<PlaceView detail={withUnpublished}
|
||
englandAverage={61} neighbours={[]} />);
|
||
expect(screen.getByRole('link', { name: 'Cornwall' })).toBeInTheDocument();
|
||
expect(screen.queryByRole('link', { name: 'Isles Of Scilly' }))
|
||
.not.toBeInTheDocument();
|
||
expect(container.querySelector('header p')?.textContent)
|
||
.toContain('Isles Of Scilly');
|
||
});
|
||
});
|
||
|
||
describe('PlaceView school attributes', () => {
|
||
/*
|
||
* The table shipped with one column of scores, which answers "how did they
|
||
* do" and nothing about whether the school is one a family could use. Age
|
||
* range, faith, nursery and constituency are the four facts a parent
|
||
* filters on before they look at a number at all.
|
||
*/
|
||
const withAttributes: PlaceDetail = {
|
||
place: { kind: 'town', slug: 'chelmsford', name: 'Chelmsford', count: 3,
|
||
parent_authority: 'Essex', phases: ['primary', 'secondary'] },
|
||
schools: [
|
||
{ urn: 1, school_name: 'Alpha Primary', phase: 'Primary',
|
||
rwm_expected_pct: 82, attainment_8_score: null,
|
||
age_range: '4-11', religious_denomination: 'Church of England',
|
||
nursery_provision: 'Has Nursery Classes',
|
||
parliamentary_constituency: 'Chelmsford' } as never,
|
||
{ urn: 2, school_name: 'Beta High', phase: 'Secondary',
|
||
rwm_expected_pct: null, attainment_8_score: 47,
|
||
age_range: '11-16', religious_denomination: 'Does not apply',
|
||
nursery_provision: 'No Nursery Classes',
|
||
parliamentary_constituency: 'Witham' } as never,
|
||
],
|
||
averages: { rwm_expected_pct: 63, attainment_8_score: 45 },
|
||
};
|
||
|
||
function headings(container: HTMLElement, table = 0): string[] {
|
||
return Array.from(container.querySelectorAll('table')[table]
|
||
.querySelectorAll('thead th')).map((th) => th.textContent ?? '');
|
||
}
|
||
|
||
it('heads a primary table with all four attributes', () => {
|
||
const { container } = render(<PlaceView detail={withAttributes}
|
||
englandAverage={61} neighbours={[]} />);
|
||
expect(headings(container)).toEqual([
|
||
'School', 'Reading, writing & maths',
|
||
'Ages', 'Religious character', 'Nursery', 'Constituency',
|
||
]);
|
||
});
|
||
|
||
it('omits nursery from a secondary table, where it does not apply', () => {
|
||
const { container } = render(<PlaceView detail={withAttributes}
|
||
englandAverage={61} neighbours={[]} />);
|
||
expect(headings(container, 1)).toEqual([
|
||
'School', 'Attainment 8', 'Ages', 'Religious character', 'Constituency',
|
||
]);
|
||
});
|
||
|
||
it('keeps the measure beside the school name, where a phone can see it', () => {
|
||
// Six columns overflow a phone and .tableWrap turns that into a swipe.
|
||
// With the measure last, the one number the page exists for is the one
|
||
// scrolled off the screen.
|
||
const { container } = render(<PlaceView detail={withAttributes}
|
||
phase="primary" englandAverage={61} neighbours={[]} />);
|
||
expect(headings(container)[1]).toBe('Reading, writing & maths');
|
||
});
|
||
|
||
it('shows the age range without repeating the column heading', () => {
|
||
render(<PlaceView detail={withAttributes} englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByText('4–11')).toBeInTheDocument();
|
||
expect(screen.queryByText('Ages 4–11')).not.toBeInTheDocument();
|
||
});
|
||
|
||
it('names the faith of a faith school', () => {
|
||
render(<PlaceView detail={withAttributes} englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByText('Church of England')).toBeInTheDocument();
|
||
});
|
||
|
||
it('reads "Does not apply" as no religious character, not as a value', () => {
|
||
// GIAS spells the absence of a faith as "Does not apply", which is a
|
||
// database answer rather than an English one. The school page already
|
||
// suppresses it; the two must not disagree about the same school.
|
||
const { container } = render(<PlaceView detail={withAttributes}
|
||
englandAverage={61} neighbours={[]} />);
|
||
const secondary = container.querySelectorAll('table')[1]
|
||
.querySelectorAll('tbody td');
|
||
expect(secondary[3].textContent).toBe('—');
|
||
expect(screen.queryByText(/Does not apply/)).not.toBeInTheDocument();
|
||
});
|
||
|
||
it('marks a nursery as such and a school without one as not', () => {
|
||
const { container } = render(<PlaceView detail={withAttributes}
|
||
englandAverage={61} neighbours={[]} />);
|
||
const cells = container.querySelectorAll('table')[0]
|
||
.querySelectorAll('tbody td');
|
||
expect(cells[4].textContent).toBe('Yes');
|
||
});
|
||
|
||
it('names the constituency of each school', () => {
|
||
render(<PlaceView detail={withAttributes} englandAverage={61}
|
||
neighbours={[]} />);
|
||
expect(screen.getByText('Chelmsford', { selector: 'td' })).toBeInTheDocument();
|
||
expect(screen.getByText('Witham', { selector: 'td' })).toBeInTheDocument();
|
||
});
|
||
|
||
it('dashes an attribute the data does not carry', () => {
|
||
// nursery_provision and parliamentary_constituency are absent from marts
|
||
// the pipeline has not rebuilt, and the API degrades them to null rather
|
||
// than failing. A row must survive that.
|
||
const bare: PlaceDetail = {
|
||
...withAttributes,
|
||
schools: [{ urn: 3, school_name: 'Gamma Primary', phase: 'Primary',
|
||
rwm_expected_pct: 70 } as never],
|
||
};
|
||
const { container } = render(<PlaceView detail={bare} phase="primary"
|
||
englandAverage={61} neighbours={[]} />);
|
||
const cells = Array.from(container.querySelectorAll('tbody td'))
|
||
.map((td) => td.textContent);
|
||
expect(cells.slice(2)).toEqual(['—', '—', '—', '—']);
|
||
});
|
||
|
||
it('reads "Not applicable" as no nursery, not as a yes', () => {
|
||
// GIAS sends text. Tested for truthiness, every value was a "Yes".
|
||
const notApplicable: PlaceDetail = {
|
||
...withAttributes,
|
||
schools: [{ urn: 5, school_name: 'Epsilon Primary', phase: 'Primary',
|
||
rwm_expected_pct: 70, nursery_provision: 'Not applicable' } as never],
|
||
};
|
||
const { container } = render(<PlaceView detail={notApplicable} phase="primary"
|
||
englandAverage={61} neighbours={[]} />);
|
||
expect(container.querySelector('tbody')!.textContent).not.toContain('Yes');
|
||
});
|
||
|
||
it('gives an all-through school its nursery under primary only', () => {
|
||
// All-through schools render in both groups. Nursery belongs to the
|
||
// primary reading of the same school, not the secondary one.
|
||
const allThrough: PlaceDetail = {
|
||
...withAttributes,
|
||
schools: [{ urn: 4, school_name: 'Delta Academy', phase: 'All-through',
|
||
rwm_expected_pct: 66, attainment_8_score: 51,
|
||
age_range: '4-18', religious_denomination: 'None',
|
||
nursery_provision: 'Has Nursery Classes',
|
||
parliamentary_constituency: 'Chelmsford' } as never],
|
||
};
|
||
const { container } = render(<PlaceView detail={allThrough}
|
||
englandAverage={61} neighbours={[]} />);
|
||
const tables = container.querySelectorAll('table');
|
||
expect(tables[0].textContent).toContain('Yes');
|
||
expect(tables[1].textContent).not.toContain('Yes');
|
||
});
|
||
});
|