diff --git a/frontend/__tests__/unit/components/ChapterMap.test.tsx b/frontend/__tests__/unit/components/ChapterMap.test.tsx index d16027d496..96655a4e13 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 { Chapter } from 'types/chapter' import ChapterMap from 'components/ChapterMap' @@ -6,6 +6,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 = { @@ -95,12 +100,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', () => { @@ -123,6 +132,7 @@ describe('ChapterMap', () => { [90, 180], ], maxBoundsViscosity: 1.0, + scrollWheelZoom: false, }) expect(mockMap.setView).toHaveBeenCalledWith([20, 0], 2) }) @@ -150,6 +160,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', () => { @@ -221,6 +237,58 @@ 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('div[role="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('div[role="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('div[role="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('div[role="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('div[role="button"]') + expect(overlay).toHaveAttribute('role', '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', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -281,10 +349,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 36c360aca5..42349c56ec 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,25 @@ const ChapterMap = ({ [90, 180], ], maxBoundsViscosity: 1.0, + scrollWheelZoom: false, // Disable scroll wheel zoom by default }).setView([20, 0], 2) L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', className: 'map-tiles', }).addTo(mapRef.current) + + // Enable scroll wheel zoom when user clicks on the map + mapRef.current.on('click', () => { + mapRef.current?.scrollWheelZoom.enable() + setIsMapActive(true) + }) + + // Disable scroll wheel zoom when mouse leaves the map + mapRef.current.on('mouseout', () => { + mapRef.current?.scrollWheelZoom.disable() + setIsMapActive(false) + }) } const map = mapRef.current @@ -102,7 +116,34 @@ const ChapterMap = ({ } }, [geoLocData, showLocal]) - return
+ return ( +
+
+ {!isMapActive && ( +
{ + mapRef.current?.scrollWheelZoom.enable() + setIsMapActive(true) + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + mapRef.current?.scrollWheelZoom.enable() + setIsMapActive(true) + } + }} + aria-label="Click to interact with map" + > +
+ Click to interact with map +
+
+ )} +
+ ) } export default ChapterMap