PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 19s
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 21s
The row repeated the search, local authority and school type above the results list, and nothing else: no phase, distance, gender, sixth form, admissions or faith. The filter bar already shows every one of them, in its selects and More filters count on desktop and as removable chips on phones, so the row was a partial second copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
178 lines
8.3 KiB
TypeScript
178 lines
8.3 KiB
TypeScript
import { act, fireEvent, render, screen } from '@testing-library/react';
|
|
import { HomeView } from '@/components/HomeView';
|
|
import { FilterBar } from '@/components/FilterBar';
|
|
import { fetchSchools } from '@/lib/api';
|
|
import { track } from '@/lib/analytics';
|
|
import { primaryFixture } from '../support/schoolFixtures';
|
|
import type { SchoolsResponse } from '@/lib/types';
|
|
|
|
/*
|
|
* The results toolbar (option B of the 2026-09-30 results-controls mockups):
|
|
* search, filters and the List/Map switch pinned under the header, with a
|
|
* floating List/Map button standing in for the switch on phones. Layout is CSS
|
|
* and not visible to jsdom; these pin the behaviour and the accessible names
|
|
* the E2E journeys rely on.
|
|
*/
|
|
|
|
let params = new URLSearchParams('postcode=SW196AR&radius=1');
|
|
const push = jest.fn();
|
|
jest.mock('next/navigation', () => ({
|
|
useSearchParams: () => params,
|
|
usePathname: () => '/',
|
|
useRouter: () => ({ push, replace: jest.fn(), prefetch: jest.fn() }),
|
|
}));
|
|
jest.mock('@/context/ComparisonContext', () => ({
|
|
useComparisonContext: () => ({ addSchool: jest.fn(), removeSchool: jest.fn(), selectedSchools: [] }),
|
|
}));
|
|
jest.mock('@/lib/api', () => ({
|
|
fetchSchools: jest.fn(),
|
|
fetchNationalAverages: jest.fn(async () => ({})),
|
|
fetchLAaverages: jest.fn(async () => ({ secondary: { attainment_8_by_la: {} } })),
|
|
}));
|
|
jest.mock('@/lib/analytics', () => ({ track: jest.fn() }));
|
|
jest.mock('@/components/SchoolMap', () => ({ SchoolMap: () => <div data-testid="map" /> }));
|
|
|
|
const filters = {
|
|
local_authorities: ['Wandsworth'], school_types: ['Community school'], years: [],
|
|
phases: ['Primary', 'Secondary'], genders: [], admissions_policies: [],
|
|
school_type_groups: [{ value: 'council', label: 'State school: council-run' }],
|
|
};
|
|
|
|
function results(): SchoolsResponse {
|
|
return { schools: [{ ...primaryFixture.schoolInfo, school_name: 'Southmead Primary School' }],
|
|
total: 1, page: 1, page_size: 25, total_pages: 1 };
|
|
}
|
|
|
|
beforeEach(() => {
|
|
params = new URLSearchParams('postcode=SW196AR&radius=1');
|
|
push.mockClear();
|
|
jest.mocked(track).mockClear();
|
|
jest.mocked(fetchSchools).mockReset().mockResolvedValue(results());
|
|
});
|
|
|
|
describe('the List/Map switch', () => {
|
|
it('lives in the toolbar with the filters and says which view is on', () => {
|
|
render(<HomeView initialSchools={results()} filters={filters} />);
|
|
const view = screen.getByRole('group', { name: 'Results view' });
|
|
expect(view.closest('div[class*="resultsToolbar"]')).not.toBeNull();
|
|
// A postcode search opens on the map.
|
|
expect(screen.getByRole('button', { name: 'Map' })).toHaveAttribute('aria-pressed', 'true');
|
|
expect(screen.getByRole('button', { name: 'List' })).toHaveAttribute('aria-pressed', 'false');
|
|
});
|
|
|
|
it('has a floating twin that flips between map and list', async () => {
|
|
render(<HomeView initialSchools={results()} filters={filters} />);
|
|
await act(async () => {});
|
|
fireEvent.click(screen.getByRole('button', { name: 'Show list' }));
|
|
expect(screen.queryByTestId('map')).not.toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: 'List' })).toHaveAttribute('aria-pressed', 'true');
|
|
expect(track).toHaveBeenCalledWith('results_view_changed', { view: 'list', via: 'floating' });
|
|
|
|
await act(async () => fireEvent.click(screen.getByRole('button', { name: 'Show map' })));
|
|
expect(screen.getByTestId('map')).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: 'Show list' })).toBeInTheDocument();
|
|
});
|
|
|
|
it('does not track a click on the view already showing', async () => {
|
|
render(<HomeView initialSchools={results()} filters={filters} />);
|
|
await act(async () => {});
|
|
fireEvent.click(screen.getByRole('button', { name: 'Map' }));
|
|
expect(track).not.toHaveBeenCalledWith('results_view_changed', expect.anything());
|
|
});
|
|
|
|
it('is absent from a name search, which has no map', () => {
|
|
params = new URLSearchParams('search=southmead');
|
|
render(<HomeView initialSchools={results()} filters={filters} />);
|
|
expect(screen.queryByRole('group', { name: 'Results view' })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole('button', { name: 'Show map' })).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('the toolbar filters', () => {
|
|
it('keeps distance, phase and school type in the row, not behind More filters', () => {
|
|
render(<FilterBar filters={filters} />);
|
|
const row = screen.getByRole('group', { name: 'Filters' });
|
|
for (const name of ['Distance', 'Phase', 'School type']) {
|
|
expect(row).toContainElement(screen.getByRole('combobox', { name }));
|
|
}
|
|
expect(screen.getByRole('combobox', { name: 'Distance' })).toHaveDisplayValue('Within 1 mile');
|
|
expect(screen.queryByRole('combobox', { name: 'Local authority' })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('counts only what More filters hides', () => {
|
|
params = new URLSearchParams('postcode=SW196AR&radius=1&school_type=council&local_authority=Wandsworth');
|
|
render(<FilterBar filters={filters} />);
|
|
expect(screen.getByRole('button', { name: /More filters \(1\)/ })).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('the phone chips line', () => {
|
|
it('drops its "more this way" fade when nothing is left to scroll', () => {
|
|
params = new URLSearchParams('postcode=SW196AR&radius=1&phase=primary');
|
|
render(<FilterBar filters={filters} />);
|
|
// jsdom lays nothing out, so the line reads as not overflowing at all.
|
|
const line = screen.getByRole('group', { name: 'Applied filters' }).parentElement!;
|
|
expect(line.className).toMatch(/controlsAtEnd/);
|
|
});
|
|
});
|
|
|
|
describe('the phone filter sheet', () => {
|
|
it('offers the results total', () => {
|
|
render(<HomeView initialSchools={results()} filters={filters} />);
|
|
fireEvent.click(screen.getByRole('button', { name: 'Filters' }));
|
|
expect(screen.getByRole('button', { name: 'Show 1 school' })).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('the folded search', () => {
|
|
it('summarises the search and unfolds on tap', () => {
|
|
render(<FilterBar filters={filters} />);
|
|
const summary = screen.getByRole('button', { name: 'Edit search: SW196AR, within 1 mile' });
|
|
fireEvent.click(summary);
|
|
expect(screen.queryByRole('button', { name: /Edit search/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('folds again once the edited search is submitted', () => {
|
|
render(<FilterBar filters={filters} />);
|
|
fireEvent.click(screen.getByRole('button', { name: /Edit search/ }));
|
|
const input = screen.getByRole('searchbox', { name: 'School name or postcode' });
|
|
fireEvent.change(input, { target: { value: 'SW19 1AA' } });
|
|
fireEvent.submit(input.closest('form')!);
|
|
expect(screen.getByRole('button', { name: /Edit search/ })).toBeInTheDocument();
|
|
});
|
|
|
|
it('refolds and shows the new text when the search changes some other way', () => {
|
|
const view = render(<FilterBar filters={filters} />);
|
|
fireEvent.click(screen.getByRole('button', { name: /Edit search/ }));
|
|
fireEvent.change(screen.getByRole('searchbox', { name: 'School name or postcode' }),
|
|
{ target: { value: 'half-typed' } });
|
|
|
|
// Back button: the URL changes under the component, nothing is submitted.
|
|
params = new URLSearchParams('postcode=SW170AA&radius=3');
|
|
view.rerender(<FilterBar filters={filters} />);
|
|
|
|
expect(screen.getByRole('button', { name: 'Edit search: SW170AA, within 3 miles' }))
|
|
.toBeInTheDocument();
|
|
expect(screen.getByRole('searchbox', { name: 'School name or postcode' }))
|
|
.toHaveValue('SW170AA');
|
|
});
|
|
|
|
it('never appears in the hero, or before anything has been searched', () => {
|
|
const { unmount } = render(<FilterBar filters={filters} isHero />);
|
|
expect(screen.queryByRole('button', { name: /Edit search/ })).not.toBeInTheDocument();
|
|
unmount();
|
|
params = new URLSearchParams('local_authority=Wandsworth');
|
|
render(<FilterBar filters={filters} />);
|
|
expect(screen.queryByRole('button', { name: /Edit search/ })).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('the results list', () => {
|
|
it('repeats no applied filters above the results; the filter bar shows them', () => {
|
|
params = new URLSearchParams('search=southmead&school_type=council&local_authority=Wandsworth');
|
|
const { container } = render(<HomeView initialSchools={results()} filters={filters} />);
|
|
expect(container.querySelector('[class*="activeFilters"]')).toBeNull();
|
|
expect(screen.queryByText('Search: southmead')).not.toBeInTheDocument();
|
|
});
|
|
});
|