Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d52d384cf2 | ||
|
|
ff606dad71 |
@@ -50,6 +50,31 @@ test('school detail page renders name and performance data', async ({ page }) =>
|
|||||||
await expect(page.locator('canvas:visible').first()).toBeVisible({ timeout: 15_000 });
|
await expect(page.locator('canvas:visible').first()).toBeVisible({ timeout: 15_000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('school hero map opens fullscreen on mobile without the Fullscreen API', async ({ page }) => {
|
||||||
|
// iOS Safari has no Element.requestFullscreen; the map must fall back to a
|
||||||
|
// CSS overlay. Simulate that by removing the API before any page script runs.
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
// @ts-expect-error deliberate API removal
|
||||||
|
delete Element.prototype.requestFullscreen;
|
||||||
|
});
|
||||||
|
|
||||||
|
await searchByName(page, 'primary');
|
||||||
|
const firstSchool = schoolLinks(page).first();
|
||||||
|
await expect(firstSchool).toBeVisible({ timeout: 15_000 });
|
||||||
|
await firstSchool.click();
|
||||||
|
await page.waitForURL(/\/school\//);
|
||||||
|
|
||||||
|
const openMap = page.getByRole('button', { name: 'Open full map' });
|
||||||
|
await expect(openMap).toBeVisible({ timeout: 15_000 });
|
||||||
|
await openMap.click();
|
||||||
|
|
||||||
|
const closeMap = page.getByRole('button', { name: 'Close map' });
|
||||||
|
await expect(closeMap).toBeVisible();
|
||||||
|
await closeMap.click();
|
||||||
|
await expect(openMap).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('comparing two schools shows both side by side', async ({ page }) => {
|
test('comparing two schools shows both side by side', async ({ page }) => {
|
||||||
// Collect two school URNs from search results, then load the share URL
|
// Collect two school URNs from search results, then load the share URL
|
||||||
await searchByName(page, 'primary');
|
await searchByName(page, 'primary');
|
||||||
|
|||||||
@@ -34,6 +34,15 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fallback fullscreen (iOS Safari — no Element.requestFullscreen): the API
|
||||||
|
can't promote the element, so pin it over the page ourselves. Above the
|
||||||
|
comparison toast (3000) and everything else except modals (9999+). */
|
||||||
|
.wrapper[data-fs-fallback] {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 5000;
|
||||||
|
}
|
||||||
|
|
||||||
.skeleton {
|
.skeleton {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -29,25 +29,50 @@ interface SchoolHeroMapProps {
|
|||||||
export const SchoolHeroMap = forwardRef<SchoolHeroMapHandle, SchoolHeroMapProps>(
|
export const SchoolHeroMap = forwardRef<SchoolHeroMapHandle, SchoolHeroMapProps>(
|
||||||
function SchoolHeroMap({ lat, lng }, ref) {
|
function SchoolHeroMap({ lat, lng }, ref) {
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [nativeFullscreen, setNativeFullscreen] = useState(false);
|
||||||
|
// iOS Safari has no Element.requestFullscreen — fall back to a
|
||||||
|
// fixed-position overlay driven by state instead of the Fullscreen API.
|
||||||
|
const [fallbackFullscreen, setFallbackFullscreen] = useState(false);
|
||||||
|
const isFullscreen = nativeFullscreen || fallbackFullscreen;
|
||||||
|
|
||||||
const open = useCallback(() => {
|
const open = useCallback(() => {
|
||||||
wrapperRef.current?.requestFullscreen?.().catch(() => {});
|
const el = wrapperRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
if (el.requestFullscreen) {
|
||||||
|
el.requestFullscreen().catch(() => setFallbackFullscreen(true));
|
||||||
|
} else {
|
||||||
|
setFallbackFullscreen(true);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const close = useCallback(() => {
|
const close = useCallback(() => {
|
||||||
if (document.fullscreenElement) document.exitFullscreen().catch(() => {});
|
if (document.fullscreenElement) document.exitFullscreen().catch(() => {});
|
||||||
|
setFallbackFullscreen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({ open }), [open]);
|
useImperativeHandle(ref, () => ({ open }), [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onChange = () => setIsFullscreen(!!document.fullscreenElement);
|
const onChange = () => setNativeFullscreen(!!document.fullscreenElement);
|
||||||
document.addEventListener('fullscreenchange', onChange);
|
document.addEventListener('fullscreenchange', onChange);
|
||||||
return () => document.removeEventListener('fullscreenchange', onChange);
|
return () => document.removeEventListener('fullscreenchange', onChange);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// The fallback overlay sits on top of the page rather than replacing it,
|
||||||
|
// so lock body scroll while it is up.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!fallbackFullscreen) return;
|
||||||
|
const prev = document.body.style.overflow;
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
return () => { document.body.style.overflow = prev; };
|
||||||
|
}, [fallbackFullscreen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={wrapperRef} className={styles.wrapper} data-fullscreen={isFullscreen || undefined}>
|
<div
|
||||||
|
ref={wrapperRef}
|
||||||
|
className={styles.wrapper}
|
||||||
|
data-fullscreen={isFullscreen || undefined}
|
||||||
|
data-fs-fallback={fallbackFullscreen || undefined}
|
||||||
|
>
|
||||||
<LeafletHeroMap lat={lat} lng={lng} interactive={isFullscreen} />
|
<LeafletHeroMap lat={lat} lng={lng} interactive={isFullscreen} />
|
||||||
|
|
||||||
{isFullscreen ? (
|
{isFullscreen ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user