Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 78 additions & 5 deletions frontend/__tests__/unit/components/ChapterMap.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { render } from '@testing-library/react'
import { render, fireEvent } from '@testing-library/react'
import { Chapter } from 'types/chapter'
import ChapterMap from 'components/ChapterMap'

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 = {
Expand Down Expand Up @@ -95,12 +100,16 @@ describe('ChapterMap', () => {

describe('rendering', () => {
it('renders the map container with correct id and style', () => {
render(<ChapterMap {...defaultProps} />)
const { container } = render(<ChapterMap {...defaultProps} />)

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', () => {
Expand All @@ -123,6 +132,7 @@ describe('ChapterMap', () => {
[90, 180],
],
maxBoundsViscosity: 1.0,
scrollWheelZoom: false,
})
expect(mockMap.setView).toHaveBeenCalledWith([20, 0], 2)
})
Expand Down Expand Up @@ -150,6 +160,12 @@ describe('ChapterMap', () => {
expect(L.markerClusterGroup).toHaveBeenCalled()
expect(mockMap.addLayer).toHaveBeenCalledWith(mockMarkerClusterGroup)
})

it('sets up event listeners for map interaction', () => {
render(<ChapterMap {...defaultProps} />)
expect(mockMap.on).toHaveBeenCalledWith('click', expect.any(Function))
expect(mockMap.on).toHaveBeenCalledWith('mouseout', expect.any(Function))
})
})

describe('Markers', () => {
Expand Down Expand Up @@ -221,6 +237,58 @@ describe('ChapterMap', () => {
})
})

describe('Interactive Overlay', () => {
it('displays overlay with "Click to interact with map" message initially', () => {
const { getByText } = render(<ChapterMap {...defaultProps} />)
expect(getByText('Click to interact with map')).toBeInTheDocument()
})

it('removes overlay when clicked', () => {
const { getByText, queryByText } = render(<ChapterMap {...defaultProps} />)

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(<ChapterMap {...defaultProps} />)

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(<ChapterMap {...defaultProps} />)

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(<ChapterMap {...defaultProps} />)

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(<ChapterMap {...defaultProps} />)

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
Expand Down Expand Up @@ -281,10 +349,15 @@ describe('ChapterMap', () => {
it('applies custom styles correctly', () => {
const customStyle = { width: '800px', height: '600px', border: '1px solid red' }

render(<ChapterMap {...defaultProps} style={customStyle} />)
const { container } = render(<ChapterMap {...defaultProps} style={customStyle} />)

// 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')
})
})

Expand Down
45 changes: 43 additions & 2 deletions frontend/src/components/ChapterMap.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -19,6 +19,7 @@ const ChapterMap = ({
}) => {
const mapRef = useRef<L.Map | null>(null)
const markerClusterRef = useRef<MarkerClusterGroup | null>(null)
const [isMapActive, setIsMapActive] = useState(false)

useEffect(() => {
if (!mapRef.current) {
Expand All @@ -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
Expand Down Expand Up @@ -102,7 +116,34 @@ const ChapterMap = ({
}
}, [geoLocData, showLocal])

return <div id="chapter-map" style={style} />
return (
<div className="relative" style={style}>
<div id="chapter-map" className="h-full w-full" />
{!isMapActive && (
<div
role="button"
tabIndex={0}
className="absolute inset-0 z-[1000] flex cursor-pointer items-center justify-center rounded-[inherit] bg-black/10"
onClick={() => {
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"
>
<div className="rounded-lg bg-white/90 px-5 py-3 text-sm font-medium text-gray-700 shadow-lg">
Click to interact with map
</div>
</div>
)}
</div>
)
}

export default ChapterMap