Skip to content

Conversation

@kris70lesgo
Copy link
Contributor

Proposed change

Resolves #3012

This pr fixes the ChapterMap locked/unlocked interaction behavior by enforcing a fully locked map state on initial load and enabling map interactions only through an explicit user action.

Summary of changes

  • Disable all map interaction handlers by default (drag, zoom, scroll, keyboard).
  • Enable interactions only when the user explicitly clicks the Unlock map button.
  • Centralize map interaction toggling logic to avoid inconsistent state.
  • Ensure visual and interactive cues accurately reflect the locked state.
  • Update unit tests to assert full interaction enable/disable lifecycle.

This approach prevents accidental map interactions while scrolling and aligns the behavior with explicit user intent.

Checklist

  • Required: I read and followed the contributing guidelines
  • Required: I ran make check-test locally and all tests passed
  • I used AI for code, documentation, or tests in this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 1, 2026

Summary by CodeRabbit

  • New Features

    • Maps now load locked with interactions disabled by default.
    • A visible unlock overlay with an accessible Unlock button enables full map interactions and toggles zoom-control visibility; overlay and controls include improved pointer-event handling.
    • Pressing Escape re-locks the map and disables interactions.
  • Tests

    • Added tests for initial locked state, unlocking behavior, Escape handling, overlay/zoom-control visibility, pointer-event classes, interaction enable/disable verification, and listener cleanup on unmount.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

ChapterMap now initializes with interactions disabled and shows an explicit unlock overlay/button. Introduces setMapInteractivity and unlockMap, uses an Unlock button to enable interactions, adds Escape-to-relock handling, removes mouseout/click auto-toggle, and updates tests to cover interactivity, overlay, ARIA, zoom-control visibility, clustering, and cleanup. (50 words)

Changes

Cohort / File(s) Summary
Component implementation
frontend/src/components/ChapterMap.tsx
Adds setMapInteractivity(map, enabled) and unlockMap(); initialize map with interactions disabled; replaces click/mouseout activation with an explicit unlock overlay/button; adds Escape key handling to re-lock and toggle zoom control; preserves marker clustering, user-location marker, and fitBounds logic.
Component tests
frontend/__tests__/unit/components/ChapterMap.test.tsx
Expands mock map to include dragging, touchZoom, doubleClickZoom, boxZoom, keyboard, scrollWheelZoom enable/disable spies; updates assertions for disabled-on-init, unlock via button, re-lock via Escape (and no-op when locked), overlay/zoom UI visibility, pointer-events classes, marker-cluster creation/addition, and Escape listener cleanup on unmount.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • kasya
  • arkid15r

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive All changes are focused on the explicit unlock behavior for ChapterMap interaction locking. The wide-screen layout objective (#3012.2) is noted as a secondary goal but not addressed in this PR's code changes. Clarify whether the wide-screen layout fixes (minZoom, maxBounds, tile repetition) mentioned in #3012 are intentionally deferred to a separate PR or if they should be included in this changeset.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix ChapterMap locked state with explicit unlock' directly summarizes the main change: implementing explicit unlock behavior for the locked ChapterMap state.
Description check ✅ Passed The description is clearly related to the changeset, referencing issue #3012 and detailing the specific changes made to disable map interactions by default and enable them only via explicit unlock button.
Linked Issues check ✅ Passed The PR implements the primary objective from #3012: disabling all map interactions (drag, zoom, scroll, keyboard) when locked and enabling them only through explicit user unlock action via button click.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/__tests__/unit/components/ChapterMap.test.tsx (1)

483-535: Consider adding cleanup test for the Escape key listener.

While the Escape key handler includes proper cleanup (line 203 in ChapterMap.tsx), consider adding a test to verify that the event listener is removed when the component unmounts. This would provide additional confidence in the cleanup behavior.

Example test for cleanup verification
it('cleans up Escape key listener on unmount', () => {
  const addEventListenerSpy = jest.spyOn(window, 'addEventListener')
  const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener')
  
  const { unmount } = render(<ChapterMap {...defaultProps} />)
  
  const handleEscape = addEventListenerSpy.mock.calls[0]?.[1]
  
  unmount()
  
  expect(removeEventListenerSpy).toHaveBeenCalledWith('keydown', handleEscape)
  
  addEventListenerSpy.mockRestore()
  removeEventListenerSpy.mockRestore()
})
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 204f833 and 8ad904c.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
  • frontend/src/components/ChapterMap.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🔇 Additional comments (11)
frontend/src/components/ChapterMap.tsx (5)

36-44: LGTM! Clean interactivity control implementation.

The function correctly toggles all major Leaflet interaction handlers. The approach is clean and centralized.


46-50: LGTM! Proper guard and state management.

The early return guard and sequential state update are correct.


75-75: LGTM! Correctly enforces locked-by-default behavior.

This aligns with the PR objective to prevent accidental interactions on load.


195-204: LGTM! Proper global event listener with cleanup.

The Escape key handler is correctly implemented with proper cleanup and appropriate dependency tracking.


232-242: LGTM! Proper pointer-events pattern and accessibility.

The overlay implementation correctly uses pointer-events-none on the wrapper with pointer-events-auto on the button, and includes proper accessibility attributes.

frontend/__tests__/unit/components/ChapterMap.test.tsx (6)

14-37: LGTM! Comprehensive interaction handler mocks.

All new Leaflet interaction handlers are properly mocked with enable/disable methods.


183-197: LGTM! Comprehensive initialization coverage.

Tests correctly verify that all interaction handlers are disabled on init and that the marker cluster group is properly created and added.


310-339: LGTM! Thorough unlock overlay testing.

Tests correctly verify overlay removal, interaction handler enabling, and accessibility attributes.


483-535: Verify the window event testing pattern.

The Escape key tests have good coverage, but the pattern fireEvent.keyDown(window, { key: 'Escape' }) may not work as expected since fireEvent is designed for DOM elements, not the window object.

According to React Testing Library best practices, window events should be tested using native event dispatch:

const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
window.dispatchEvent(event)

Please verify that these tests are actually triggering the event handler as intended.

🔎 Recommended pattern for testing window events
-      fireEvent.keyDown(window, { key: 'Escape' })
+      const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
+      window.dispatchEvent(escapeEvent)

Apply this pattern to lines 496, 510, and 530.


438-446: LGTM! Correct zoom control visibility testing.

Test properly verifies that zoom control is added when the map is unlocked.


537-552: LGTM! Proper pointer-events structure validation.

Tests correctly verify the CSS classes for the pointer-events pattern on both the overlay wrapper and unlock button.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 1, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ad904c and 04b8f8c.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🔇 Additional comments (6)
frontend/__tests__/unit/components/ChapterMap.test.tsx (6)

14-37: LGTM! Comprehensive mock interaction handlers.

The mock structure correctly covers all map interaction handlers (dragging, touchZoom, doubleClickZoom, scrollWheelZoom, boxZoom, keyboard) with enable/disable methods, aligning with the Leaflet API and the PR's explicit lock/unlock requirements.


183-197: LGTM! Proper initialization validation.

These tests correctly verify that:

  1. All interaction handlers (dragging, touchZoom, doubleClickZoom, scrollWheelZoom, boxZoom, keyboard) are disabled on initial render—the core requirement for the locked state.
  2. The marker cluster group is created and added to the map.

310-339: LGTM! Thorough unlock flow validation.

These tests comprehensively verify the unlock behavior:

  • Overlay is removed when the unlock button is clicked.
  • All six interaction handlers are enabled.
  • Proper accessibility attributes (type="button", aria-label="Unlock map") are present.

The use of .closest('button')! with non-null assertion is acceptable here since the test would fail earlier if the button doesn't exist.


438-446: LGTM! Zoom control visibility correctly validated.

The test properly verifies that the zoom control is added to the map only after the unlock button is clicked, maintaining a clean UI in the locked state.


536-553: LGTM! Proper event listener cleanup verification.

This test correctly validates that the Escape key listener is removed on unmount, preventing memory leaks. The spy-based approach is appropriate for verifying cleanup behavior.

The pattern of finding the handler via addEventListenerSpy.mock.calls.find(([event]) => event === 'keydown') works well in this isolated test context and properly restores the spies afterward.


556-571: LGTM! Critical pointer-events structure validation.

These tests ensure the correct CSS pointer-events configuration:

  • The overlay wrapper has pointer-events-none (line 560) so it doesn't block map interactions when unlocked.
  • The unlock button has pointer-events-auto (line 569) so it remains clickable despite the parent's pointer-events-none.

This is essential for the UX: the button must be accessible in the locked state, but the overlay shouldn't interfere with map interactions once unlocked.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 1, 2026
@kris70lesgo
Copy link
Contributor Author

@kasya can u review the pr ?

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kris70lesgo I sure can review this but first you're required to:

  • address any comments from CodeRabbit ☑️
  • ensure there are no new issues from Sonar (there are currently 10 new issues introduced in this PR)
Image
  • Run make check-test locally and address all found issues (which you didn't, though you added a checkmark next to it)
Image Image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/ChapterMap.tsx (1)

14-14: Remove duplicate import.

Line 10 already imports 'leaflet.markercluster', making this import redundant.

🔎 Proposed fix
-import 'leaflet.markercluster'
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89fe333 and 629d949.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
  • frontend/src/components/ChapterMap.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/tests/unit/components/ChapterMap.test.tsx
🔇 Additional comments (5)
frontend/src/components/ChapterMap.tsx (5)

36-44: LGTM! Well-centralized interaction control.

The function cleanly toggles all six Leaflet interaction handlers (dragging, touchZoom, doubleClickZoom, scrollWheelZoom, boxZoom, keyboard) using a unified enable/disable pattern.


46-50: LGTM! Clear unlock logic.

The function properly guards against null references and correctly enables both map interactions and the active state.


232-242: Verify that the overlay z-index blocks marker and cluster interactions.

The overlay uses z-[500] to block map interactions, and the pointer-events strategy (none on wrapper, auto on button) is correct. However, please verify that marker clicks and cluster interactions are fully blocked when the map is locked, as Leaflet markers can have their own z-index values.

Test manually by:

  1. Loading the map in locked state
  2. Attempting to click on markers and clusters through the overlay
  3. Verifying no popups open and no interactions occur

75-75: LGTM! Enforces initial locked state.

Calling setMapInteractivity(mapRef.current, false) after map initialization correctly implements the requirement to start with all interactions disabled.


1-273: No action needed. Leaflet 1.9.4 has no known React 19 compatibility issues—Leaflet is library-agnostic and the React compatibility concerns you may have encountered relate to react-leaflet (not used here), which requires v5 for React 19. The code correctly uses vanilla Leaflet with proper ref handling and useEffect cleanup, which is safe for React 19.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/src/components/ChapterMap.tsx (1)

35-43: Consider extracting setMapInteractivity outside the component.

Since this function doesn't depend on any component state or props (it only uses the map parameter), it can be moved outside the component to avoid recreation on each render.

🔎 Proposed refactor
+const setMapInteractivity = (map: L.Map, enabled: boolean) => {
+  const action = enabled ? 'enable' : 'disable'
+  map.dragging[action]()
+  map.touchZoom[action]()
+  map.doubleClickZoom[action]()
+  map.scrollWheelZoom[action]()
+  map.boxZoom[action]()
+  map.keyboard[action]()
+}
+
 const ChapterMap = ({
   geoLocData,
   showLocal,
   style,
   userLocation,
   onShareLocation,
 }: {
   ...
 }) => {
   ...
-  const setMapInteractivity = (map: L.Map, enabled: boolean) => {
-    const action = enabled ? 'enable' : 'disable'
-    map.dragging[action]()
-    map.touchZoom[action]()
-    map.doubleClickZoom[action]()
-    map.scrollWheelZoom[action]()
-    map.boxZoom[action]()
-    map.keyboard[action]()
-  }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 629d949 and 6c53be2.

📒 Files selected for processing (2)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
  • frontend/src/components/ChapterMap.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🔇 Additional comments (9)
frontend/src/components/ChapterMap.tsx (3)

45-49: LGTM!

The unlockMap function has proper null checking and cleanly centralizes the unlock logic.


194-208: Good implementation addressing the Escape key race condition.

The handler now properly calls stopPropagation() and preventDefault() to prevent conflicts with other global Escape handlers (like MultiSearch). The early return guard for !isMapActive ensures the handler only acts when the map is unlocked.


236-246: Well-structured accessible unlock overlay.

Good use of pointer-events-none on the overlay container with pointer-events-auto on the button. The accessibility attributes (type="button", aria-label, focus-visible:ring-2) ensure keyboard users can interact with the unlock flow.

frontend/__tests__/unit/components/ChapterMap.test.tsx (6)

14-37: LGTM!

Comprehensive mock setup for all map interaction handlers, consistent with the component's setMapInteractivity implementation.


183-197: LGTM!

Good test coverage verifying that all interaction handlers are disabled on initialization, aligning with the PR objective of a fully locked initial state.


320-333: LGTM!

Thorough test coverage for the unlock flow, verifying all six interaction handlers are enabled when the user clicks the unlock button.


489-516: Good implementation addressing the past review concern.

The tests now correctly use globalThis.dispatchEvent(new KeyboardEvent('keydown', ...)) instead of fireEvent.keyDown(window, ...), ensuring the window-level event listener is properly triggered. The additional assertions for preventDefault and stopPropagation (lines 514-515) verify that the race condition mitigation is working.


550-567: LGTM!

Proper verification of event listener cleanup on unmount. The spy approach correctly captures the registered handler and confirms it's removed during cleanup.


570-584: LGTM!

Good structural tests verifying the pointer-events CSS pattern that enables the unlock button to be clickable while the rest of the overlay passes events through to the map.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 3, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
frontend/__tests__/unit/components/ChapterMap.test.tsx (3)

494-495: Use consistent button selection pattern.

Line 495 clicks the result of getByText('Unlock map') directly, while other tests (lines 313, 323, 338, 444, 471, 481) use getByText('Unlock map').closest('button') with null checks. The closest('button') pattern is more defensive and ensures you're interacting with the button element even if the text is in a child element.

🔎 Suggested consistent approach
-      const unlockButton = getByText('Unlock map')
-      fireEvent.click(unlockButton)
+      const unlockButton = getByText('Unlock map').closest('button')
+      expect(unlockButton).not.toBeNull()
+      fireEvent.click(unlockButton as HTMLElement)

503-515: Consider removing implementation detail assertions.

The test verifies that preventDefault and stopPropagation were called on the keyboard event. This tests implementation details rather than behavior. The re-lock functionality is already validated by checking the UI state (line 509) and mock calls (lines 510-511).

🔎 Simplified test approach

The assertions at lines 503-504 and 514-515 can be removed while still fully testing the re-lock behavior:

-      const event = new KeyboardEvent('keydown', { key: 'Escape', cancelable: true })
-      const preventDefaultSpy = jest.spyOn(event, 'preventDefault')
-      const stopPropagationSpy = jest.spyOn(event, 'stopPropagation')
-
-      globalThis.dispatchEvent(event)
+      globalThis.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))

       // Verify map is locked again
       expect(getByText('Unlock map')).toBeInTheDocument()
       expect(mockMap.dragging.disable).toHaveBeenCalled()
       expect(mockMap.scrollWheelZoom.disable).toHaveBeenCalled()
-
-      // Verify event was contained to prevent race conditions
-      expect(preventDefaultSpy).toHaveBeenCalled()
-      expect(stopPropagationSpy).toHaveBeenCalled()

579-584: Use consistent button selection pattern.

Line 582 uses getByText('Unlock map') without closest('button'), inconsistent with the safer pattern used throughout the file (lines 313, 323, 338, 444, 471, 481). If the button text is rendered in a child element (e.g., a <span>), this test would incorrectly check the child's class instead of the button's class.

🔎 Suggested consistent approach
-      const button = getByText('Unlock map')
+      const button = getByText('Unlock map').closest('button')
+      expect(button).not.toBeNull()
       expect(button).toHaveClass('pointer-events-auto')
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c53be2 and c74ba4c.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🔇 Additional comments (6)
frontend/__tests__/unit/components/ChapterMap.test.tsx (6)

14-37: LGTM!

The mock interaction handlers are comprehensive and correctly structured to test the full lock/unlock lifecycle for all map interactions.


183-197: LGTM!

The tests correctly verify that all interaction handlers are disabled on initialization, ensuring the map starts in a fully locked state as required by the PR objectives.


310-342: LGTM!

The unlock button tests are comprehensive, covering interaction enabling, overlay removal, and accessibility attributes. The defensive pattern with closest('button') and null checks is good practice.


502-506: LGTM!

The Escape key tests correctly use globalThis.dispatchEvent(new KeyboardEvent(...)) to trigger window-level keyboard events, properly testing the re-lock behavior.


550-567: LGTM!

The cleanup test properly verifies that the Escape key listener is removed on unmount, preventing memory leaks.

Note: The test finds the handler by searching for the 'keydown' event listener in addEventListener calls (lines 557-559). This works for the current implementation but assumes only one keydown handler exists. Given the component's scope, this is acceptable.


571-577: LGTM!

Testing for the pointer-events-none class is appropriate here since pointer events are critical to the overlay functionality—allowing clicks through to the unlock button while blocking other interactions.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 3, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/__tests__/unit/components/ChapterMap.test.tsx (1)

530-531: Consider using the consistent button selection pattern.

Line 530-531 directly clicks the result of getByText('Unlock map'), while most other tests in this file use the more explicit pattern:

const unlockButton = getByText('Unlock map').closest('button')
expect(unlockButton).not.toBeNull()
fireEvent.click(unlockButton as HTMLElement)

While the current approach works due to event bubbling, using closest('button') is more explicit and maintains consistency with lines 313-315, 323-325, 338-341, 444-446, 471-473, and 481-483.

Suggested refactor for consistency
-      const unlockButton = getByText('Unlock map')
-      fireEvent.click(unlockButton)
+      const unlockButton = getByText('Unlock map').closest('button')
+      expect(unlockButton).not.toBeNull()
+      fireEvent.click(unlockButton as HTMLElement)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c74ba4c and e093f3f.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-11-17T16:47:05.578Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:427-427
Timestamp: 2025-11-17T16:47:05.578Z
Learning: In the frontend test files for the OWASP/Nest repository, `expect(true).toBe(true)` no-op assertions may be intentionally added as workarounds when ESLint rule jest/expect-expect doesn't detect expectations inside helper functions or waitFor callbacks. These can be resolved by configuring the ESLint rule's assertFunctionNames option to recognize custom assertion helpers instead of flagging them as redundant.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🔇 Additional comments (8)
frontend/__tests__/unit/components/ChapterMap.test.tsx (8)

14-37: LGTM: Mock interaction handlers are well-structured.

The added mock handlers (dragging, touchZoom, doubleClickZoom, boxZoom, keyboard) correctly mirror the Leaflet API structure and support comprehensive testing of the new interaction lock/unlock behavior.


183-197: LGTM: Initialization tests properly verify locked state.

The test correctly asserts that all interaction handlers are disabled on initialization, aligning with the PR objective of enforcing a fully locked map state on load. Good separation of cluster group creation test.


310-342: LGTM: Unlock button tests are comprehensive.

The tests thoroughly verify:

  • Overlay removal on unlock
  • All six interaction handlers enabled (dragging, touchZoom, doubleClickZoom, scrollWheelZoom, boxZoom, keyboard)
  • Proper accessibility attributes (type, aria-label)

The button selection pattern using closest('button') with null checks is appropriate for these tests.


441-450: LGTM: Zoom control visibility test is correct.

The test properly verifies that the zoom control is created and added to the map when the unlock button is clicked.


456-486: LGTM: Share location button visibility tests are thorough.

The tests comprehensively cover the three visibility scenarios:

  1. Not shown when map is locked
  2. Shown after unlocking
  3. Not rendered when callback prop is missing

489-541: LGTM: Escape key re-lock tests are comprehensive and correctly implemented.

The tests properly verify:

  • Map re-locks when Escape is pressed (interactions disabled, overlay shown)
  • No-op behavior when Escape pressed while already locked
  • Zoom control removed on re-lock

The use of globalThis.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) correctly addresses the previous review concern about triggering window-level keyboard events.


543-560: LGTM: Event listener cleanup test is well-designed.

The test properly verifies that the Escape key listener is cleaned up on component unmount by:

  • Spying on addEventListener/removeEventListener
  • Finding the registered 'keydown' handler
  • Confirming the same handler is removed
  • Cleaning up spies

563-579: LGTM: Pointer events structure tests ensure correct CSS layering.

The tests properly verify the pointer-events CSS classes:

  • Overlay wrapper has pointer-events-none to avoid blocking underlying elements
  • Unlock button has pointer-events-auto to remain clickable

This ensures the overlay behaves correctly without interfering with other UI interactions.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 3, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/__tests__/unit/components/ChapterMap.test.tsx (1)

444-486: LGTM! Zoom control and share location visibility tests are solid.

The tests correctly verify:

  1. Zoom control appears only after unlocking
  2. Share location button visibility is gated by map active state and prop presence

Minor note: Lines 471 and 481 name the variable overlay when it's actually the button element. Consider renaming to unlockButton for clarity.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e093f3f and a060a8b.

📒 Files selected for processing (1)
  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-17T17:30:42.139Z
Learnt from: anurag2787
Repo: OWASP/Nest PR: 2671
File: frontend/__tests__/unit/components/MultiSearch.test.tsx:169-171
Timestamp: 2025-11-17T17:30:42.139Z
Learning: In the OWASP/Nest frontend tests (PR #2671 context), wrapper functions like `expectChaptersCountEqualsThree` that simply call another helper with a fixed parameter (e.g., `expectChaptersCountEquals(3)`) are intentionally used to avoid arrow function callbacks in `waitFor` calls. This pattern prevents adding nesting depth that would trigger rule typescript:S2004. Example: `await waitFor(expectChaptersCountEqualsThree)` avoids the extra nesting from `await waitFor(() => expectChaptersCountEquals(3))`.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
📚 Learning: 2025-07-12T17:36:57.255Z
Learnt from: Rajgupta36
Repo: OWASP/Nest PR: 1717
File: frontend/__tests__/unit/pages/createProgram.test.tsx:70-86
Timestamp: 2025-07-12T17:36:57.255Z
Learning: When testing React page components that use mocked form components, validation logic should be tested at the form component level, not the page level. Page-level tests should focus on authentication, role checking, submission handling, and navigation logic.

Applied to files:

  • frontend/__tests__/unit/components/ChapterMap.test.tsx
🔇 Additional comments (5)
frontend/__tests__/unit/components/ChapterMap.test.tsx (5)

14-37: LGTM! Comprehensive interaction control mocks.

The mock structure correctly covers all Leaflet interaction handlers (dragging, touchZoom, doubleClickZoom, scrollWheelZoom, boxZoom, keyboard) with enable/disable methods, enabling thorough testing of the explicit unlock feature.


183-197: LGTM! Initialization tests validate locked state.

These tests correctly verify that:

  1. All interaction handlers (dragging, touchZoom, doubleClickZoom, scrollWheelZoom, boxZoom, keyboard) are disabled on initial load
  2. Marker cluster group is properly created and added to the map

This aligns with the PR objective to enforce a fully locked ChapterMap state on initial load.


310-342: LGTM! Unlock button tests are comprehensive.

The tests properly validate the unlock flow:

  1. Overlay removal on button click
  2. All interaction handlers (dragging, touchZoom, doubleClickZoom, scrollWheelZoom, boxZoom, keyboard) enabled
  3. Proper ARIA attributes (type, aria-label) for accessibility

The defensive null checks and consistent button selection pattern make the tests clear and maintainable.


489-562: LGTM! Escape key re-lock tests are thorough and correctly implemented.

These tests properly validate the Escape key behavior:

  1. Re-locks the map (disables interactions, shows overlay)
  2. No-op when map is already locked
  3. Removes zoom control on re-lock
  4. Cleans up event listener on unmount

The implementation correctly uses globalThis.dispatchEvent(new KeyboardEvent(...)) to trigger window-level keydown events, addressing the past review feedback. The event listener cleanup test is particularly good for preventing memory leaks.


564-580: LGTM! Pointer events structure tests validate critical overlay behavior.

These tests verify the CSS class structure that enables the overlay to function correctly:

  1. Overlay wrapper has pointer-events-none to pass clicks through
  2. Unlock button has pointer-events-auto to receive clicks

While testing CSS classes is generally an implementation detail, these are structural requirements that make the explicit unlock feature work properly, so this coverage is valuable.

@kris70lesgo
Copy link
Contributor Author

@kasya, thanks for the review.
All the issues by coderabbit and sonar has been resolved
I’ve now run make check-test end-to-end
All checks pass except pnpm run graphql-codegen, which fails locally because the backend isn’t running (CSRF token fetch).
Please let me know if you’d like me to spin up the full backend locally as well, but otherwise this should be ready for review now

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 5, 2026

Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kris70lesgo! Found a couple of issues you'll need to address with this PR ⬇️

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously the map would lock back on loosing focus. After your changes I'm only able to lock the map back with Escape button but I believe we need to keep that lock on loosing focus as well. 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I'm still able to click into pins when map is closed:

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve ChapterMap UX: Locking, Interactions, and Screen Layout

2 participants