fix(compare): five must-fix findings from the final expert review #50
@@ -40,6 +40,19 @@ async function twoPrimaryUrns(page: Page): Promise<[string, string]> {
|
||||
return [urns[0], urns[1]];
|
||||
}
|
||||
|
||||
async function twoSecondaryUrns(page: Page): Promise<[string, string]> {
|
||||
const res = await page.request.get('/api/schools?search=school&per_page=100');
|
||||
expect(res.ok()).toBeTruthy();
|
||||
const body = await res.json();
|
||||
const urns: string[] = (body.schools ?? [])
|
||||
.filter((s: { phase?: string; attainment_8_score?: number | null }) =>
|
||||
s.phase === 'Secondary' && s.attainment_8_score != null,
|
||||
)
|
||||
.map((s: { urn: number }) => String(s.urn));
|
||||
expect(urns.length).toBeGreaterThanOrEqual(2);
|
||||
return [urns[0], urns[1]];
|
||||
}
|
||||
|
||||
test('home page loads with hero search', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('h1').first()).toBeVisible();
|
||||
@@ -199,6 +212,23 @@ test('comparing two schools shows the parent-first sections side by side', async
|
||||
}
|
||||
});
|
||||
|
||||
test('comparing two secondary schools renders the secondary sections', async ({ page }) => {
|
||||
const [urn0, urn1] = await twoSecondaryUrns(page);
|
||||
|
||||
await page.goto(`/compare?urns=${urn0},${urn1}`);
|
||||
await expect(page.locator(`a[href*="${urn0}"]`).first()).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// The parent-first sections must render — this page was completely blank
|
||||
// for all-secondary baskets (expert review must-fix #1).
|
||||
await expect(page.getByRole('heading', { name: 'At a glance' }).first()).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
await expect(page.getByRole('heading', { name: 'Ofsted inspection' }).first()).toBeVisible();
|
||||
// A KS4 measure proves the secondary academics variant rendered.
|
||||
await expect(page.getByText(/Attainment 8/i).first()).toBeVisible();
|
||||
await expect(page.getByText(/No primary schools in your comparison/)).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('compare chart on mobile shows school chips with tap-to-focus', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Regression: an all-secondary comparison must render the secondary sections.
|
||||
*
|
||||
* The basket hydrates from the URL a beat after mount, so the auto-phase
|
||||
* effect must re-run once selectedSchools arrives — with deps of only
|
||||
* [comparisonData] it fired once against an empty basket, bailed, and the
|
||||
* page stayed on an empty "primary" tab ("No primary schools in your
|
||||
* comparison") even though all schools were secondary.
|
||||
*/
|
||||
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import { ComparisonView } from '@/components/ComparisonView';
|
||||
import { ComparisonProvider } from '@/context/ComparisonProvider';
|
||||
import type { ComparisonData, School } from '@/lib/types';
|
||||
|
||||
const fetchComparison = jest.fn();
|
||||
jest.mock('@/lib/api', () => ({
|
||||
fetchComparison: (...args: unknown[]) => fetchComparison(...args),
|
||||
}));
|
||||
jest.mock('@/lib/analytics', () => ({ track: jest.fn() }));
|
||||
|
||||
function secondarySchool(urn: number, name: string): School {
|
||||
return {
|
||||
urn,
|
||||
school_name: name,
|
||||
local_authority: 'Testshire',
|
||||
school_type: 'Academy converter',
|
||||
attainment_8_score: 55,
|
||||
phase: 'Secondary',
|
||||
} as School;
|
||||
}
|
||||
|
||||
function data(urn: number, name: string): ComparisonData {
|
||||
return {
|
||||
school_info: secondarySchool(urn, name),
|
||||
yearly_data: [{ year: 202425, attainment_8_score: 55 }] as ComparisonData['yearly_data'],
|
||||
ofsted: null,
|
||||
census: null,
|
||||
admissions: null,
|
||||
admissions_history: [],
|
||||
deprivation: null,
|
||||
};
|
||||
}
|
||||
|
||||
const INITIAL_DATA = {
|
||||
'300': data(300, 'Gamma High'),
|
||||
'400': data(400, 'Delta Academy'),
|
||||
};
|
||||
|
||||
test('an all-secondary comparison renders the sections, not an empty primary tab', async () => {
|
||||
render(
|
||||
<ComparisonProvider>
|
||||
<ComparisonView
|
||||
initialData={INITIAL_DATA}
|
||||
initialNationalAverages={{
|
||||
year: 202425,
|
||||
primary: {},
|
||||
secondary: { attainment_8_score: 46 },
|
||||
by_year: [],
|
||||
}}
|
||||
initialBenchmarks={undefined}
|
||||
initialUrns={[300, 400]}
|
||||
metrics={[]}
|
||||
selectedMetric="attainment_8_score"
|
||||
/>
|
||||
</ComparisonProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('heading', { name: 'At a glance' })).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getAllByText('Gamma High').length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText(/No primary schools in your comparison/)).toBeNull();
|
||||
expect(fetchComparison).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -173,7 +173,11 @@ export function ComparisonView({
|
||||
if (!metricFitsPhase) {
|
||||
setSelectedMetric(newPhase === 'secondary' ? 'attainment_8_score' : 'rwm_expected_pct');
|
||||
}
|
||||
}, [comparisonData]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
// selectedSchools is a dep because the basket hydrates after mount: the
|
||||
// first run sees an empty basket and bails, so it must re-fire when the
|
||||
// schools arrive. primarySchools/secondarySchools/metrics/selectedMetric
|
||||
// are intentionally omitted (derived or would cause loops).
|
||||
}, [comparisonData, selectedSchools]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const handlePhaseChange = (phase: 'primary' | 'secondary') => {
|
||||
phaseLockedByUser.current = true;
|
||||
|
||||
Reference in New Issue
Block a user