diff --git a/frontend/__tests__/unit/components/ChapterMap.test.tsx b/frontend/__tests__/unit/components/ChapterMap.test.tsx index bbdc01e5f5..68400d3cfe 100644 --- a/frontend/__tests__/unit/components/ChapterMap.test.tsx +++ b/frontend/__tests__/unit/components/ChapterMap.test.tsx @@ -1,4 +1,4 @@ -import { render } from '@testing-library/react' +import { render, fireEvent } from '@testing-library/react' import * as L from 'leaflet' import { Chapter } from 'types/chapter' import ChapterMap from 'components/ChapterMap' @@ -7,6 +7,11 @@ const mockMap = { setView: jest.fn().mockReturnThis(), addLayer: jest.fn().mockReturnThis(), fitBounds: jest.fn().mockReturnThis(), + on: jest.fn().mockReturnThis(), + scrollWheelZoom: { + enable: jest.fn(), + disable: jest.fn(), + }, } const mockMarker = { @@ -96,12 +101,16 @@ describe('ChapterMap', () => { describe('rendering', () => { it('renders the map container with correct id and style', () => { - render() + const { container } = render() const mapContainer = document.getElementById('chapter-map') expect(mapContainer).toBeInTheDocument() expect(mapContainer).toHaveAttribute('id', 'chapter-map') - expect(mapContainer).toHaveStyle('width: 100%; height: 400px;') + expect(mapContainer).toHaveClass('h-full', 'w-full') + + // Check that the parent container has the correct styles applied + const parentContainer = container.firstChild as HTMLElement + expect(parentContainer).toHaveClass('relative') }) it('renders with empty data without crashing', () => { @@ -122,6 +131,7 @@ describe('ChapterMap', () => { [90, 180], ], maxBoundsViscosity: 1.0, + scrollWheelZoom: false, }) expect(mockMap.setView).toHaveBeenCalledWith([20, 0], 2) }) @@ -143,6 +153,12 @@ describe('ChapterMap', () => { expect(L.markerClusterGroup).toHaveBeenCalled() expect(mockMap.addLayer).toHaveBeenCalledWith(mockMarkerClusterGroup) }) + + it('sets up event listeners for map interaction', () => { + render() + expect(mockMap.on).toHaveBeenCalledWith('click', expect.any(Function)) + expect(mockMap.on).toHaveBeenCalledWith('mouseout', expect.any(Function)) + }) }) describe('Markers', () => { @@ -249,6 +265,57 @@ describe('ChapterMap', () => { }) }) + describe('Interactive Overlay', () => { + it('displays overlay with "Click to interact with map" message initially', () => { + const { getByText } = render() + expect(getByText('Click to interact with map')).toBeInTheDocument() + }) + + it('removes overlay when clicked', () => { + const { getByText, queryByText } = render() + + const overlay = getByText('Click to interact with map').closest('button') + fireEvent.click(overlay!) + + expect(queryByText('Click to interact with map')).not.toBeInTheDocument() + }) + + it('enables scroll wheel zoom when overlay is clicked', () => { + const { getByText } = render() + + const overlay = getByText('Click to interact with map').closest('button') + fireEvent.click(overlay!) + + expect(mockMap.scrollWheelZoom.enable).toHaveBeenCalled() + }) + + it('handles keyboard interaction with Enter key', () => { + const { getByText } = render() + + const overlay = getByText('Click to interact with map').closest('button') + fireEvent.keyDown(overlay!, { key: 'Enter' }) + + expect(mockMap.scrollWheelZoom.enable).toHaveBeenCalled() + }) + + it('handles keyboard interaction with Space key', () => { + const { getByText } = render() + + const overlay = getByText('Click to interact with map').closest('button') + fireEvent.keyDown(overlay!, { key: ' ' }) + + expect(mockMap.scrollWheelZoom.enable).toHaveBeenCalled() + }) + + it('has proper accessibility attributes', () => { + const { getByText } = render() + + const overlay = getByText('Click to interact with map').closest('button') + expect(overlay).toHaveAttribute('tabIndex', '0') + expect(overlay).toHaveAttribute('aria-label', 'Click to interact with map') + }) + }) + describe('Local View', () => { it('sets local view when showLocal is true', () => { render() @@ -307,10 +374,15 @@ describe('ChapterMap', () => { it('applies custom styles correctly', () => { const customStyle = { width: '800px', height: '600px', border: '1px solid red' } - render() + const { container } = render() + + // Custom styles should be applied to the parent container + const parentContainer = container.firstChild as HTMLElement + expect(parentContainer).toHaveStyle('width: 800px; height: 600px; border: 1px solid red;') + // Map container should have Tailwind classes const mapContainer = document.getElementById('chapter-map') - expect(mapContainer).toHaveStyle('width: 800px; height: 600px; border: 1px solid red;') + expect(mapContainer).toHaveClass('h-full', 'w-full') }) }) diff --git a/frontend/src/components/ChapterMap.tsx b/frontend/src/components/ChapterMap.tsx index 254d6de087..3921e6711d 100644 --- a/frontend/src/components/ChapterMap.tsx +++ b/frontend/src/components/ChapterMap.tsx @@ -1,6 +1,6 @@ 'use client' import L, { MarkerClusterGroup } from 'leaflet' -import React, { useEffect, useRef } from 'react' +import React, { useEffect, useRef, useState } from 'react' import type { Chapter } from 'types/chapter' import 'leaflet.markercluster' import 'leaflet/dist/leaflet.css' @@ -19,6 +19,7 @@ const ChapterMap = ({ }) => { const mapRef = useRef(null) const markerClusterRef = useRef(null) + const [isMapActive, setIsMapActive] = useState(false) useEffect(() => { if (!mapRef.current) { @@ -29,12 +30,28 @@ const ChapterMap = ({ [90, 180], ], maxBoundsViscosity: 1.0, + scrollWheelZoom: false, }).setView([20, 0], 2) L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', className: 'map-tiles', }).addTo(mapRef.current) + + mapRef.current.on('click', () => { + mapRef.current?.scrollWheelZoom.enable() + setIsMapActive(true) + }) + + mapRef.current.on('mouseout', (e: L.LeafletMouseEvent) => { + const originalEvent = e.originalEvent as MouseEvent + const relatedTarget = originalEvent.relatedTarget as Node | null + const container = mapRef.current?.getContainer() + if (relatedTarget && container?.contains(relatedTarget)) return + + mapRef.current?.scrollWheelZoom.disable() + setIsMapActive(false) + }) } const map = mapRef.current @@ -108,7 +125,34 @@ const ChapterMap = ({ } }, [geoLocData, showLocal]) - return
+ return ( +
+
+ {!isMapActive && ( + + )} +
+ ) } export default ChapterMap