108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
/**
|
|||
|
|
* Regression: opening a compare link while a DIFFERENT basket is stored must
|
||
|
|
* not blank the page.
|
||
|
|
*
|
||
|
|
* The basket hydrates from localStorage first, which can fire a fetch for the
|
||
|
|
* OLD school set; the URL-seed effect then replaces the basket with the URL's
|
||
|
|
* schools (already covered by SSR data, so no new fetch). When the stale
|
||
|
|
* response for the old set finally lands, it must not clobber the fresh SSR
|
||
|
|
* data — that left every section (including the trends chart) empty until a
|
||
|
|
* hard refresh.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { act, 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 school(urn: number, name: string): School {
|
||
|
|
return {
|
||
|
|
urn,
|
||
|
|
school_name: name,
|
||
|
|
local_authority: 'Testshire',
|
||
|
|
school_type: 'Community school',
|
||
|
|
rwm_expected_pct: 80,
|
||
|
|
phase: 'Primary',
|
||
|
|
} as School;
|
||
|
|
}
|
||
|
|
|
||
|
|
function data(urn: number, name: string): ComparisonData {
|
||
|
|
return {
|
||
|
|
school_info: school(urn, name),
|
||
|
|
yearly_data: [{ year: 202425, rwm_expected_pct: 80 }] as ComparisonData['yearly_data'],
|
||
|
|
ofsted: null,
|
||
|
|
census: null,
|
||
|
|
admissions: null,
|
||
|
|
admissions_history: [],
|
||
|
|
deprivation: null,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// The visitor's previously stored basket (a different school entirely).
|
||
|
|
const STORED_SCHOOL = school(900, 'Old Stored School');
|
||
|
|
|
||
|
|
// The comparison the URL (and SSR) actually asked for.
|
||
|
|
const URL_DATA = {
|
||
|
|
'100': data(100, 'Alpha Primary'),
|
||
|
|
'200': data(200, 'Beta Primary'),
|
||
|
|
};
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
fetchComparison.mockReset();
|
||
|
|
localStorage.clear();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a stale fetch for the previously stored basket does not clobber the URL comparison', async () => {
|
||
|
|
localStorage.setItem('selectedSchools', JSON.stringify([STORED_SCHOOL]));
|
||
|
|
|
||
|
|
const pending: Array<(v: unknown) => void> = [];
|
||
|
|
fetchComparison.mockImplementation(() => new Promise((resolve) => pending.push(resolve)));
|
||
|
|
|
||
|
|
render(
|
||
|
|
<ComparisonProvider>
|
||
|
|
<ComparisonView
|
||
|
|
initialData={URL_DATA}
|
||
|
|
initialNationalAverages={{
|
||
|
|
year: 202425,
|
||
|
|
primary: { rwm_expected_pct: 62 },
|
||
|
|
secondary: {},
|
||
|
|
by_year: [],
|
||
|
|
}}
|
||
|
|
initialBenchmarks={undefined}
|
||
|
|
initialUrns={[100, 200]}
|
||
|
|
metrics={[]}
|
||
|
|
selectedMetric="rwm_expected_pct"
|
||
|
|
/>
|
||
|
|
</ComparisonProvider>,
|
||
|
|
);
|
||
|
|
|
||
|
|
// The URL's schools render from SSR data once the basket is reseeded.
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(screen.getByRole('heading', { name: 'At a glance' })).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
expect(screen.getAllByText('Alpha Primary').length).toBeGreaterThan(0);
|
||
|
|
|
||
|
|
// The transient stored-basket fetch (for school 900) resolves LATE, after
|
||
|
|
// the basket has moved on to the URL's schools.
|
||
|
|
await act(async () => {
|
||
|
|
for (const resolve of pending) {
|
||
|
|
resolve({
|
||
|
|
comparison: { '900': data(900, 'Old Stored School') },
|
||
|
|
national_averages: { year: 202425, primary: {}, secondary: {}, by_year: [] },
|
||
|
|
benchmarks: undefined,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// The page must still show the URL comparison — not go blank.
|
||
|
|
expect(screen.getByRole('heading', { name: 'At a glance' })).toBeInTheDocument();
|
||
|
|
expect(screen.getAllByText('Alpha Primary').length).toBeGreaterThan(0);
|
||
|
|
});
|