diff --git a/CHANGELOG.md b/CHANGELOG.md index 296e63ea9..50c45d787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,12 @@ ### 🐛 Bug fixes +- Fixed campus map not rendering in timetable modal by adding explicit height to `#campus-map` container +- Fixed "Zoom to Fit" button: resolved sidebar overlap and enabled view reset for single-marker maps + ### 🔧 Internal changes +- Added test coverage for `CampusMap` component (7 test cases covering rendering, markers, and zoom functionality) - Simplified timetable availability check in `Description.tsx` by removing redundant `Object.keys(sessions).length !== 0` condition - Updated OpenStreetMap tile layer URL from HTTP to HTTPS in `CampusMap.tsx` for improved security - Extracted read-only selection, prerequisite, and status computations (`isSelected`, `arePrereqsSatisfied*`, `compute*Status`, `totalSelectedCredits`) from `Graph.js` into a typed `graphState.ts` module diff --git a/js/components/common/CampusMap.tsx b/js/components/common/CampusMap.tsx index 19fd578ca..f82f56ffb 100644 --- a/js/components/common/CampusMap.tsx +++ b/js/components/common/CampusMap.tsx @@ -33,12 +33,16 @@ export function CampusMap({ if (!map || !mapGroup) return // markerCount is the number of markers on the map. - // Only adjust the map bounds if there are multiple markers because the zoom will - // be strange for 1 marker. Note that the map will be centered on the single marker const markerCount = mapGroup.getLayers().length if (markerCount > 1) { + // Multiple markers: fit bounds to show all markers map.fitBounds(mapGroup.getBounds(), { padding: [30, 30] }) + } else if (markerCount === 1) { + // Single marker: reset to default zoom level 16 and center on the marker + const bounds = mapGroup.getBounds() + const center = bounds.getCenter() + map.setView(center, 16) } } diff --git a/js/components/common/__tests__/CampusMap.test.js b/js/components/common/__tests__/CampusMap.test.js new file mode 100644 index 000000000..52fec03fb --- /dev/null +++ b/js/components/common/__tests__/CampusMap.test.js @@ -0,0 +1,181 @@ +import { render, screen } from "@testing-library/react" +import userEvent from "@testing-library/user-event" +import { CampusMap } from "../CampusMap" + +// Mock Leaflet to avoid DOM rendering issues in tests +jest.mock("leaflet", () => ({ + Icon: jest.fn(() => ({})), + Map: jest.fn(), + latLngBounds: jest.fn(() => ({ + extend: jest.fn(), + getCenter: jest.fn(() => ({ lat: 43.6596426, lng: -79.3976676 })), + })), + latLng: jest.fn((lat, lng) => ({ lat, lng })), +})) + +/* eslint-disable react/prop-types */ +jest.mock("react-leaflet", () => ({ + MapContainer: ({ children }) =>
{children}
, + TileLayer: ({ url }) =>
, + FeatureGroup: ({ children }) =>
{children}
, + Marker: ({ position, children }) => ( +
+ {children} +
+ ), + Popup: ({ children }) =>
{children}
, +})) +/* eslint-enable react/prop-types */ + +describe("CampusMap", () => { + const mockLecturesByBuilding = [ + { + buildingName: "Bahen Centre for Information Technology", + buildingCode: "BA", + address: "40 St George St", + lat: 43.6596426, + lng: -79.3976676, + postalCode: "M5S 2E4", + days: [ + { + dayString: "Monday", + dayOrder: 0, + timeframes: [ + { + courseCode: "CSC309H1", + session: "L5101", + startTime: 11, + endTime: 13, + room: "BA3200", + }, + ], + }, + ], + }, + ] + + it("renders the map container", () => { + render( + + ) + + const mapContainer = screen.getByTestId("map-container") + expect(mapContainer).toBeTruthy() + }) + + it("renders markers for each building", () => { + render( + + ) + + const markers = screen.getAllByTestId("marker") + expect(markers).toHaveLength(1) + }) + + it("renders multiple markers for multiple buildings", () => { + const multipleBuildings = [ + ...mockLecturesByBuilding, + { + buildingName: "Myhal Centre", + buildingCode: "MY", + address: "55 St George St", + lat: 43.6597, + lng: -79.3975, + postalCode: "M5S 2E5", + days: [ + { + dayString: "Tuesday", + dayOrder: 1, + timeframes: [ + { + courseCode: "CSC301H1", + session: "L0101", + startTime: 14, + endTime: 16, + room: "MY150", + }, + ], + }, + ], + }, + ] + + render( + + ) + + const markers = screen.getAllByTestId("marker") + expect(markers).toHaveLength(2) + }) + + it("renders Zoom to Fit button", () => { + render( + + ) + + const zoomButton = screen.getByText("Zoom to Fit") + expect(zoomButton).toBeTruthy() + }) + + it("zoom button is clickable", async () => { + const user = userEvent.setup() + render( + + ) + + const zoomButton = screen.getByText("Zoom to Fit") + await user.click(zoomButton) + + // Button should not throw errors when clicked + expect(zoomButton).toBeTruthy() + }) + + it("does not render markers for buildings without coordinates", () => { + const buildingsWithoutCoords = [ + { + buildingName: "Unknown Building", + buildingCode: "UNK", + address: "Unknown", + lat: undefined, + lng: undefined, + postalCode: "M5S 2E4", + days: [], + }, + ] + + render( + + ) + + const markers = screen.queryAllByTestId("marker") + expect(markers).toHaveLength(0) + }) + + it("renders popup component for markers", () => { + render( + + ) + + // Check that popup component is rendered + const popups = screen.getAllByTestId("popup") + expect(popups.length).toBeGreaterThan(0) + }) +}) diff --git a/style/app.scss b/style/app.scss index a967783e3..8344cbfc7 100644 --- a/style/app.scss +++ b/style/app.scss @@ -337,7 +337,12 @@ ol { fill: var(--secondary-color); } +#campus-map { + height: 500px; +} + #map-sidebar { + margin-top: 3.75rem; overflow-y: auto; padding: 0; }