diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_Spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_Spec.ts similarity index 100% rename from app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_Spec.ts rename to app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_Spec.ts diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_loading_Spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_loading_Spec.ts similarity index 100% rename from app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_loading_Spec.ts rename to app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_loading_Spec.ts diff --git a/app/client/src/widgets/MapWidget/component/MapComponent.test.tsx b/app/client/src/widgets/MapWidget/component/MapComponent.test.tsx new file mode 100644 index 0000000000..998a4d382d --- /dev/null +++ b/app/client/src/widgets/MapWidget/component/MapComponent.test.tsx @@ -0,0 +1,121 @@ +import React from "react"; +import { render } from "@testing-library/react"; +import Marker from "../component/Marker"; + +// Mock the google maps API +const mockAddListener = jest.fn().mockImplementation((event, callback) => { + // Store the callback to simulate click events + if (event === "click") { + ( + mockAddListener as unknown as { + clickCallback: (...args: unknown[]) => void; + } + ).clickCallback = callback; + } + + return "listener-id"; // Return a mock listener ID +}); + +const mockRemoveListener = jest.fn(); +const mockSetMap = jest.fn(); +const mockSetIcon = jest.fn(); +const mockSetPosition = jest.fn(); +const mockSetTitle = jest.fn(); + +// Add type declaration for the global google object +declare global { + interface Window { + google: unknown; + } +} + +// Mock the google object +(global as unknown as { google: unknown }).google = { + maps: { + Marker: jest.fn().mockImplementation(() => ({ + setMap: mockSetMap, + setIcon: mockSetIcon, + setPosition: mockSetPosition, + setTitle: mockSetTitle, + addListener: mockAddListener, + })), + Point: jest.fn().mockImplementation((x, y) => ({ x, y })), + event: { + clearListeners: jest.fn(), + removeListener: mockRemoveListener, + }, + }, +}; + +describe("Map Widget - Marker Component", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should trigger onClick callback only once per click", () => { + const onClickMock = jest.fn(); + + // Render the marker component with onClick handler + render( + , + ); + + // Simulate a marker click by directly calling the stored callback + ( + mockAddListener as unknown as { + clickCallback: (...args: unknown[]) => void; + } + ).clickCallback(); + + // Verify onClick was called exactly once + expect(onClickMock).toHaveBeenCalledTimes(1); + + // Simulate another click + ( + mockAddListener as unknown as { + clickCallback: (...args: unknown[]) => void; + } + ).clickCallback(); + + // Verify onClick was called exactly twice (once per click) + expect(onClickMock).toHaveBeenCalledTimes(2); + }); + + it("should clear previous click listeners when onClick prop changes", () => { + const { rerender } = render( + , + ); + + // Verify clearListeners was called during initial render + expect(google.maps.event.clearListeners).toHaveBeenCalledWith( + expect.anything(), + "click", + ); + + // Reset the mock to check if it's called again + (google.maps.event.clearListeners as jest.Mock).mockClear(); + + // Rerender with a different onClick handler + rerender( + , + ); + + // Verify clearListeners was called again when onClick changed + expect(google.maps.event.clearListeners).toHaveBeenCalledWith( + expect.anything(), + "click", + ); + }); +}); diff --git a/app/client/src/widgets/MapWidget/component/Marker.tsx b/app/client/src/widgets/MapWidget/component/Marker.tsx index 7701ded5be..ef9bec51e4 100644 --- a/app/client/src/widgets/MapWidget/component/Marker.tsx +++ b/app/client/src/widgets/MapWidget/component/Marker.tsx @@ -31,10 +31,6 @@ const Marker: React.FC = (options) => { title, }); - googleMapMarker.addListener("click", () => { - if (onClick) onClick(); - }); - setMarker(googleMapMarker); } @@ -79,19 +75,32 @@ const Marker: React.FC = (options) => { useEffect(() => { if (!marker) return; - marker.addListener("click", () => { + google.maps.event.clearListeners(marker, "click"); + const clickListener = marker.addListener("click", () => { if (onClick) onClick(); }); + + return () => { + google.maps.event.removeListener(clickListener); + }; }, [marker, onClick]); // add dragend event on marker useEffect(() => { - if (!marker) return; + if (!marker || !onDragEnd) return; - marker.addListener("dragend", (e: google.maps.MapMouseEvent) => { - if (onDragEnd) onDragEnd(e); - }); - }, [marker, options.onDragEnd]); + google.maps.event.clearListeners(marker, "dragend"); + const dragEndListener = marker.addListener( + "dragend", + (e: google.maps.MapMouseEvent) => { + if (onDragEnd) onDragEnd(e); + }, + ); + + return () => { + google.maps.event.removeListener(dragEndListener); + }; + }, [marker, onDragEnd]); return null; };