PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m18s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 3m48s
Review caught _places_payload documenting a `phase_url` the function never returned. The docstring was not stray prose: the approved design included the phase variant — "Primary schools in Beccles" was one of its four example links — and it was dropped during implementation without being mentioned. Deleting the sentence would have closed the report while losing the feature, so the links are built instead. These are the pages that most needed them. ~950 phase variants were once reachable by nothing at all: absent from every sitemap and unlinked from the place page. "Primary schools in brentwood" is the query they exist to answer. Membership is read from the registry's own `phase_urns` rather than re-derived from the school's phase string. The registry already decides which phases a place publishes and which schools are listed on each, so asking it is both shorter and the only way the link cannot disagree with the page it points at. It also means outcodes need no special case: they carry empty `phase_urns` by design, because nobody searches "primary schools in SW11", so they report no phase links on their own. `phases` is a list rather than a single url. An all-through school is listed on both the primary and secondary pages, so there is no tie to break and no reason to invent one. Each entry renders directly after its own place, so "22 primary schools in Brentwood" reads as part of Brentwood rather than as an unrelated link further along the row. The e2e journey now follows a phase link where the town publishes one and asserts it resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DXnXQKnPpZBBP61fBQiFkq
69 lines
2.8 KiB
TypeScript
69 lines
2.8 KiB
TypeScript
/**
|
|
* School pages had no BreadcrumbList and no links into the location layer.
|
|
* Both are fixed by the same data — the `places` array the API now returns —
|
|
* so they are tested together.
|
|
*/
|
|
import { schoolBreadcrumbJsonLd } from '@/lib/jsonld';
|
|
|
|
const essex = { kind: 'authority', slug: 'essex', name: 'Essex', count: 480, url: '/schools/authority/essex', phases: [] };
|
|
const brentwood = { kind: 'town', slug: 'brentwood', name: 'Brentwood', count: 37, url: '/schools/brentwood', phases: [] };
|
|
const outcode = { kind: 'outcode', slug: 'cm15', name: 'CM15', count: 12, url: '/schools/near/cm15', phases: [] };
|
|
|
|
describe('school breadcrumbs', () => {
|
|
it('reads home to authority to town to school', () => {
|
|
const ld = schoolBreadcrumbJsonLd({
|
|
name: 'Brentwood School', url: '/school/100000-brentwood-school',
|
|
places: [essex, brentwood],
|
|
});
|
|
|
|
expect(ld['@type']).toBe('BreadcrumbList');
|
|
expect(ld.itemListElement.map((i) => i.name))
|
|
.toEqual(['schoolcompare', 'Essex', 'Brentwood', 'Brentwood School']);
|
|
expect(ld.itemListElement.map((i) => i.position)).toEqual([1, 2, 3, 4]);
|
|
});
|
|
|
|
it('skips a level the school has no published place for', () => {
|
|
// A school whose town falls below the publish threshold has no town page.
|
|
// The trail closes over the gap rather than linking to a 404.
|
|
const ld = schoolBreadcrumbJsonLd({
|
|
name: 'Lone School', url: '/school/1-lone-school', places: [essex],
|
|
});
|
|
|
|
expect(ld.itemListElement.map((i) => i.name))
|
|
.toEqual(['schoolcompare', 'Essex', 'Lone School']);
|
|
expect(ld.itemListElement.map((i) => i.position)).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
it('omits outcodes, which are not a place a breadcrumb reads through', () => {
|
|
// CM15 is a useful link in the module but nonsense in a trail: nobody
|
|
// navigates Essex → CM15 → school.
|
|
const ld = schoolBreadcrumbJsonLd({
|
|
name: 'Brentwood School', url: '/school/100000-brentwood-school',
|
|
places: [essex, brentwood, outcode],
|
|
});
|
|
|
|
expect(JSON.stringify(ld)).not.toContain('cm15');
|
|
});
|
|
|
|
it('still produces a valid trail when the school has no places at all', () => {
|
|
const ld = schoolBreadcrumbJsonLd({
|
|
name: 'Orphan School', url: '/school/2-orphan-school', places: [],
|
|
});
|
|
|
|
expect(ld.itemListElement.map((i) => i.name)).toEqual(['schoolcompare', 'Orphan School']);
|
|
});
|
|
|
|
it('uses absolute urls, as every other entity on the site does', () => {
|
|
const ld = schoolBreadcrumbJsonLd({
|
|
name: 'Brentwood School', url: '/school/100000-brentwood-school',
|
|
places: [essex, brentwood],
|
|
});
|
|
|
|
for (const item of ld.itemListElement) {
|
|
expect(item.item).toMatch(/^https:\/\/www\.schoolcompare\.co\.uk\//);
|
|
}
|
|
// The root is the homepage: there is no /schools index page to link to.
|
|
expect(ld.itemListElement[0].item).toBe('https://www.schoolcompare.co.uk/');
|
|
});
|
|
});
|