2026-09-30 21:30:33 +01:00
|
|
|
import { act, fireEvent, render } from '@testing-library/react';
|
|
|
|
|
import LeafletMapInner from '@/components/LeafletMapInner';
|
|
|
|
|
import { primaryFixture } from '../support/schoolFixtures';
|
|
|
|
|
import type { School } from '@/lib/types';
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The results map's own logic, against real Leaflet in jsdom: which pin is
|
|
|
|
|
* selected, whether the card opens, and what the card offers. jsdom lays out
|
|
|
|
|
* nothing, so this pins behaviour, never positions.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const base = primaryFixture.schoolInfo;
|
|
|
|
|
const a: School = { ...base, urn: 1, school_name: 'Southmead Primary School', latitude: 51.43, longitude: -0.21, distance: 0.2, rwm_expected_pct: 52 };
|
|
|
|
|
const b: School = { ...base, urn: 2, school_name: 'Greenmead School', latitude: 51.431, longitude: -0.205, distance: 0.2,
|
|
|
|
|
school_type: 'Community special school', rwm_expected_pct: 0, reading_expected_pct: 0, writing_expected_pct: 0, maths_expected_pct: 0 };
|
|
|
|
|
const schools = [a, b];
|
|
|
|
|
const centre: [number, number] = [51.43, -0.21];
|
|
|
|
|
|
|
|
|
|
function setWide(wide: boolean) {
|
|
|
|
|
window.matchMedia = ((q: string) => ({
|
|
|
|
|
matches: wide, media: q, addEventListener() {}, removeEventListener() {},
|
|
|
|
|
})) as unknown as typeof window.matchMedia;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderMap(props: Partial<React.ComponentProps<typeof LeafletMapInner>> = {}) {
|
|
|
|
|
const all = {
|
|
|
|
|
schools, center: centre, zoom: 13, referencePoint: centre, radiusMiles: 1,
|
|
|
|
|
nationalAvgRwm: 62, ...props,
|
|
|
|
|
};
|
|
|
|
|
const view = render(<LeafletMapInner {...all} />);
|
|
|
|
|
return { ...view, rerender: (next: Partial<typeof all>) => view.rerender(<LeafletMapInner {...all} {...next} />) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
beforeEach(() => setWide(true));
|
|
|
|
|
|
|
|
|
|
it('draws a pin per school, the search location and the radius', () => {
|
|
|
|
|
const { container } = renderMap();
|
|
|
|
|
expect(container.querySelectorAll('.sc-pin')).toHaveLength(2);
|
|
|
|
|
expect(container.querySelector('.sc-home')).not.toBeNull();
|
|
|
|
|
expect(container.querySelector('.sc-radius-label')).toHaveTextContent('1 mile');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports a pin click, and marks and opens the selected school', () => {
|
|
|
|
|
const onMarkerClick = jest.fn();
|
|
|
|
|
const { container, rerender } = renderMap({ onMarkerClick });
|
|
|
|
|
fireEvent.click(container.querySelectorAll('.sc-pin')[0]);
|
|
|
|
|
expect(onMarkerClick).toHaveBeenCalledWith(a);
|
|
|
|
|
|
|
|
|
|
rerender({ onMarkerClick, selectedUrn: 1 });
|
|
|
|
|
expect(container.querySelectorAll('.sc-pin--selected')).toHaveLength(1);
|
|
|
|
|
expect(container.querySelector('.sc-popup')).toHaveTextContent('Southmead Primary School');
|
|
|
|
|
expect(container.querySelector('.sc-popup')).toHaveTextContent('52% RWM -10 pts');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('keeps the special-school card free of a benchmark and a placeholder 0%', () => {
|
|
|
|
|
const { container } = renderMap({ selectedUrn: 2 });
|
|
|
|
|
const card = container.querySelector('.sc-popup')!;
|
|
|
|
|
expect(card).toHaveTextContent('Greenmead School');
|
|
|
|
|
expect(card).not.toHaveTextContent(/%|pts/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('adds to compare from the card, and the card follows the basket', () => {
|
|
|
|
|
const onAddToCompare = jest.fn();
|
|
|
|
|
const { container, rerender } = renderMap({ onAddToCompare, selectedUrn: 1, compareUrns: [] });
|
|
|
|
|
fireEvent.click(container.querySelector('[data-compare]')!);
|
|
|
|
|
expect(onAddToCompare).toHaveBeenCalledWith(a);
|
|
|
|
|
|
|
|
|
|
rerender({ onAddToCompare, selectedUrn: 1, compareUrns: [1] });
|
|
|
|
|
expect(container.querySelector('[data-compare]')).toHaveTextContent('✓ Comparing');
|
|
|
|
|
fireEvent.click(container.querySelector('[data-compare]')!);
|
|
|
|
|
expect(onAddToCompare).toHaveBeenCalledTimes(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('tells the page when the card is closed from the map, but not when replaced', () => {
|
|
|
|
|
const onDeselect = jest.fn();
|
|
|
|
|
const { container, rerender } = renderMap({ onDeselect, selectedUrn: 1 });
|
|
|
|
|
rerender({ onDeselect, selectedUrn: 2 });
|
|
|
|
|
expect(onDeselect).not.toHaveBeenCalled();
|
|
|
|
|
|
|
|
|
|
fireEvent.click(container.querySelector('.leaflet-popup-close-button')!);
|
|
|
|
|
expect(onDeselect).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('keeps the selection when the results reload under it', () => {
|
|
|
|
|
const onDeselect = jest.fn();
|
|
|
|
|
const { container, rerender } = renderMap({ onDeselect, selectedUrn: 1 });
|
|
|
|
|
act(() => rerender({ onDeselect, selectedUrn: 1, schools: [...schools] }));
|
|
|
|
|
expect(onDeselect).not.toHaveBeenCalled();
|
|
|
|
|
expect(container.querySelector('.sc-popup')).toHaveTextContent('Southmead Primary School');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('opens no card on a narrow screen, where the page shows a bottom sheet', () => {
|
|
|
|
|
setWide(false);
|
|
|
|
|
const { container } = renderMap({ selectedUrn: 1 });
|
|
|
|
|
expect(container.querySelectorAll('.sc-pin--selected')).toHaveLength(1);
|
|
|
|
|
expect(container.querySelector('.sc-popup')).toBeNull();
|
|
|
|
|
});
|
2026-09-30 21:50:34 +01:00
|
|
|
|
|
|
|
|
it('puts the card back when the pins are rebuilt for a reason other than the schools', () => {
|
|
|
|
|
const onDeselect = jest.fn();
|
|
|
|
|
const { container, rerender } = renderMap({ onDeselect, selectedUrn: 1 });
|
|
|
|
|
act(() => rerender({ onDeselect, selectedUrn: 1, radiusMiles: 3, referencePoint: [51.43, -0.21] }));
|
|
|
|
|
expect(onDeselect).not.toHaveBeenCalled();
|
|
|
|
|
expect(container.querySelector('.sc-radius-label')).toHaveTextContent('3 miles');
|
|
|
|
|
expect(container.querySelector('.sc-popup')).toHaveTextContent('Southmead Primary School');
|
|
|
|
|
expect(container.querySelectorAll('.sc-pin--selected')).toHaveLength(1);
|
|
|
|
|
});
|