From 9ca8a39ea5fccfb4f5446bba6179ed50a93dcf30 Mon Sep 17 00:00:00 2001 From: Streetlives-codex Date: Fri, 24 Jul 2026 16:11:35 -0400 Subject: [PATCH 1/5] perf: prioritize mobile location detail content --- .../location-detail/street-view.tsx | 143 +++--------------- src/components/map.tsx | 39 +++-- 2 files changed, 51 insertions(+), 131 deletions(-) diff --git a/src/components/location-detail/street-view.tsx b/src/components/location-detail/street-view.tsx index 3be8c596..8269e05b 100644 --- a/src/components/location-detail/street-view.tsx +++ b/src/components/location-detail/street-view.tsx @@ -1,138 +1,37 @@ "use client"; -import { - Position, - SimplifiedLocationData, - YourPeerLegacyLocationData, -} from "@/components/common"; -import LocationStubMarker from "@/components/location-stub-marker"; -import { - activeMarkerIcon, - defaultZoom, - mapStyles, -} from "@/components/map-common"; +import { YourPeerLegacyLocationData } from "@/components/common"; import { buildStreetViewUrls } from "@/lib/streetView"; -import { - APIProvider, - Map, - MapCameraChangedEvent, - Marker, -} from "@vis.gl/react-google-maps"; -import { useCallback, useEffect, useState } from "react"; export default function StreetView({ location, }: { location: YourPeerLegacyLocationData; }) { - const [zoom, setZoom] = useState(defaultZoom); - const [mapCenter, setMapCenter] = useState(location); - const [locationStubs, setLocationStubs] = useState( - [], - ); - const { imageUrl, mapsUrl } = buildStreetViewUrls(location, { size: "600x500", }); - // TODO: eliminate duplicate code - const handleCameraChange = useCallback( - (ev: MapCameraChangedEvent) => { - const googleMapDiv = ev.map.getDiv(); - - // if google map is already hidden, then ignore the event, because we get a weird zoom - if ( - !googleMapDiv || - (googleMapDiv.clientHeight === 0 && googleMapDiv.clientWidth === 0) - ) - return; - - const newCenter = ev.detail.center; - if ( - newCenter.lat !== 0 && - newCenter.lng !== 0 && - (mapCenter.lat !== newCenter.lat || mapCenter.lng !== newCenter.lng) - ) { - setMapCenter(newCenter); - } - - const newZoom = ev.detail.zoom; - if (newZoom && newZoom !== zoom) { - setZoom(newZoom); - } - }, - [mapCenter, setMapCenter, zoom, setZoom], - ); - - // TODO: call the locationStubs API - useEffect(() => {}, []); - return location.closed ? undefined : ( -
- -
-
- - - - - {locationStubs - ? locationStubs - .filter((locationStub) => locationStub.id !== location.id) - .map((locationStub) => ( - - )) - : undefined} - - - -
- - Open Street View - -
-
+ + + + Open Street View + + ); } diff --git a/src/components/map.tsx b/src/components/map.tsx index 0cc7d288..70f64ace 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -382,6 +382,25 @@ export default function LocationsMap({ useState(cookieLocationSlugClickedOnMobile); const [locationStubClickedOnMobile, setLocationStubClickedOnMobile] = useState(); + const [shouldLoadMap, setShouldLoadMap] = useState(false); + const showMapViewOnMobile = useViewStore( + (state) => state.showMapViewOnMobile, + ); + + useEffect(() => { + const updateMapLoading = () => { + // The detail panel entirely covers the map on mobile. Do not spend + // mobile bandwidth and main-thread time loading Google Maps behind it. + setShouldLoadMap( + window.innerWidth >= 768 || + (!locationDetailStub && showMapViewOnMobile), + ); + }; + + updateMapLoading(); + window.addEventListener("resize", updateMapLoading); + return () => window.removeEventListener("resize", updateMapLoading); + }, [locationDetailStub, showMapViewOnMobile]); useEffect(() => { if (locationSlugClickedOnMobile) { @@ -432,15 +451,17 @@ export default function LocationsMap({ return ( <>
- - - + {shouldLoadMap && ( + + + + )}
{locationStubClickedOnMobile ? ( Date: Fri, 24 Jul 2026 16:20:33 -0400 Subject: [PATCH 2/5] fix: restore mobile map toggle loading --- src/components/main-component.tsx | 13 +------- src/components/map-loading.ts | 9 ++++++ src/components/map.tsx | 11 ++++--- tests/unit/map-loading.test.ts | 51 +++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 src/components/map-loading.ts create mode 100644 tests/unit/map-loading.test.ts diff --git a/src/components/main-component.tsx b/src/components/main-component.tsx index e8fe271a..35ebabfd 100644 --- a/src/components/main-component.tsx +++ b/src/components/main-component.tsx @@ -2,9 +2,7 @@ import { useViewStore } from "@/lib/store"; import classNames from "classnames"; -import { usePathname } from "next/navigation"; import { Suspense } from "react"; -import { LOCATION_ROUTE } from "./common"; import FiltersPopup from "./filters-popup"; import { MapLoadingAnimation } from "./map-loading-animation"; @@ -15,26 +13,17 @@ export function MainComponent({ mapContainer: React.ReactNode; sidePanel: React.ReactNode; }) { - const currentPath = usePathname() as string; - const [ignore, firstPathComponent, secondPathComponent] = - currentPath.split("/"); - const isLocationDetailPage = - firstPathComponent === LOCATION_ROUTE && - typeof secondPathComponent === "string"; - const showMapViewOnMobile = useViewStore( (state) => state.showMapViewOnMobile, ); - const showMapView = showMapViewOnMobile && !isLocationDetailPage; - const classnames = classNames([ "flex-1", "overflow-hidden", "flex", "flex-col", "md:flex-row", - showMapView ? "showMapOnMobile" : "hideMapOnMobile", + showMapViewOnMobile ? "showMapOnMobile" : "hideMapOnMobile", ]); return ( diff --git a/src/components/map-loading.ts b/src/components/map-loading.ts new file mode 100644 index 00000000..031bd9ce --- /dev/null +++ b/src/components/map-loading.ts @@ -0,0 +1,9 @@ +export function shouldLoadGoogleMap({ + viewportWidth, + showMapViewOnMobile, +}: { + viewportWidth: number; + showMapViewOnMobile: boolean; +}): boolean { + return viewportWidth >= 768 || showMapViewOnMobile; +} diff --git a/src/components/map.tsx b/src/components/map.tsx index 70f64ace..bd9eafc4 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -28,6 +28,7 @@ import { defaultZoom, mapStyles, myLocationIcon } from "./map-common"; import { MobileTray } from "./mobile-tray"; import { getUrlWithNewFilterParameter } from "./navigation"; import { shouldAutoRedirectToNearby } from "./nearby-redirect"; +import { shouldLoadGoogleMap } from "./map-loading"; function isMobile(): boolean { return window.innerWidth < 768; @@ -389,18 +390,18 @@ export default function LocationsMap({ useEffect(() => { const updateMapLoading = () => { - // The detail panel entirely covers the map on mobile. Do not spend - // mobile bandwidth and main-thread time loading Google Maps behind it. setShouldLoadMap( - window.innerWidth >= 768 || - (!locationDetailStub && showMapViewOnMobile), + shouldLoadGoogleMap({ + viewportWidth: window.innerWidth, + showMapViewOnMobile, + }), ); }; updateMapLoading(); window.addEventListener("resize", updateMapLoading); return () => window.removeEventListener("resize", updateMapLoading); - }, [locationDetailStub, showMapViewOnMobile]); + }, [showMapViewOnMobile]); useEffect(() => { if (locationSlugClickedOnMobile) { diff --git a/tests/unit/map-loading.test.ts b/tests/unit/map-loading.test.ts new file mode 100644 index 00000000..04b8f144 --- /dev/null +++ b/tests/unit/map-loading.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { shouldLoadGoogleMap } from "@/components/map-loading"; + +describe("shouldLoadGoogleMap", () => { + it("does not load a map behind a hidden mobile location detail panel", () => { + expect( + shouldLoadGoogleMap({ + viewportWidth: 390, + showMapViewOnMobile: false, + }), + ).toBe(false); + }); + + it("loads the map when a mobile location detail user selects map view", () => { + expect( + shouldLoadGoogleMap({ + viewportWidth: 390, + showMapViewOnMobile: true, + }), + ).toBe(true); + }); + + it("loads the map for a mobile list page after the map toggle changes", () => { + const mobileList = { viewportWidth: 390 }; + + expect( + shouldLoadGoogleMap({ ...mobileList, showMapViewOnMobile: false }), + ).toBe(false); + expect( + shouldLoadGoogleMap({ ...mobileList, showMapViewOnMobile: true }), + ).toBe(true); + }); + + it("re-evaluates to load the map after a resize to desktop", () => { + expect( + shouldLoadGoogleMap({ + viewportWidth: 1024, + showMapViewOnMobile: false, + }), + ).toBe(true); + }); + + it("loads the map on desktop regardless of the mobile toggle state", () => { + expect( + shouldLoadGoogleMap({ + viewportWidth: 768, + showMapViewOnMobile: false, + }), + ).toBe(true); + }); +}); From 5a091aac9ab36256ed809ed93522d4b4a900849a Mon Sep 17 00:00:00 2001 From: Streetlives-codex Date: Fri, 24 Jul 2026 16:29:55 -0400 Subject: [PATCH 3/5] test: cover responsive map initialization --- src/components/map.tsx | 15 ++-- tests/unit/locations-map.test.tsx | 110 ++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 tests/unit/locations-map.test.tsx diff --git a/src/components/map.tsx b/src/components/map.tsx index bd9eafc4..0e972fe7 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -390,12 +390,15 @@ export default function LocationsMap({ useEffect(() => { const updateMapLoading = () => { - setShouldLoadMap( - shouldLoadGoogleMap({ - viewportWidth: window.innerWidth, - showMapViewOnMobile, - }), - ); + const canLoadMap = shouldLoadGoogleMap({ + viewportWidth: window.innerWidth, + showMapViewOnMobile, + }); + + // Deferring the initial load protects mobile LCP. Once the user has + // opened the map, keep it mounted so a list/map toggle preserves its + // pan and zoom state instead of constructing a new map each time. + setShouldLoadMap((hasLoadedMap) => hasLoadedMap || canLoadMap); }; updateMapLoading(); diff --git a/tests/unit/locations-map.test.tsx b/tests/unit/locations-map.test.tsx new file mode 100644 index 00000000..78fd2a6e --- /dev/null +++ b/tests/unit/locations-map.test.tsx @@ -0,0 +1,110 @@ +import { act, render, screen } from "@testing-library/react"; +import { type ReactNode } from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.hoisted(() => { + process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY = "test-api-key"; +}); + +vi.mock("next-client-cookies", () => ({ + useCookies: () => ({ + get: vi.fn(), + set: vi.fn(), + remove: vi.fn(), + }), +})); + +vi.mock("next/navigation", () => ({ + usePathname: () => "/locations/example", + useRouter: () => ({ push: vi.fn() }), + useSearchParams: () => new URLSearchParams(), +})); + +vi.mock("@vis.gl/react-google-maps", () => ({ + APIProvider: ({ children }: { children: ReactNode }) => ( +
{children}
+ ), + Map: ({ children }: { children: ReactNode }) =>
{children}
, + Marker: () => null, + useMap: () => null, +})); + +vi.mock("@/components/location-stub-marker", () => ({ default: () => null })); +vi.mock("@/components/mobile-tray", () => ({ MobileTray: () => null })); + +import LocationsMap from "@/components/map"; +import { GeoCoordinatesContext } from "@/components/geo-context"; +import { useViewStore } from "@/lib/store"; + +function setViewportWidth(width: number) { + Object.defineProperty(window, "innerWidth", { + configurable: true, + value: width, + }); +} + +function renderMap() { + return render( + + + , + ); +} + +describe("LocationsMap responsive loading", () => { + beforeEach(() => { + useViewStore.setState({ showMapViewOnMobile: false }); + }); + + it("initializes Google Maps on desktop", () => { + setViewportWidth(1024); + renderMap(); + + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + }); + + it("defers Google Maps on mobile until the map toggle is selected", () => { + setViewportWidth(390); + renderMap(); + + expect(screen.queryByTestId("google-map-provider")).not.toBeInTheDocument(); + + act(() => useViewStore.getState().setShowMapViewOnMobile(true)); + + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + }); + + it("preserves an initialized map while resizing in both directions", () => { + setViewportWidth(1024); + renderMap(); + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + + act(() => { + setViewportWidth(390); + window.dispatchEvent(new Event("resize")); + }); + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + + act(() => { + setViewportWidth(1024); + window.dispatchEvent(new Event("resize")); + }); + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + }); + + it("preserves an initialized map when mobile map view is toggled", () => { + setViewportWidth(390); + renderMap(); + + act(() => useViewStore.getState().setShowMapViewOnMobile(true)); + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + + act(() => useViewStore.getState().setShowMapViewOnMobile(false)); + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + + act(() => useViewStore.getState().setShowMapViewOnMobile(true)); + expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); + }); +}); From 38afa81efbbfda66a7048f91b755ac2001b795b1 Mon Sep 17 00:00:00 2001 From: Streetlives-codex Date: Fri, 24 Jul 2026 16:33:57 -0400 Subject: [PATCH 4/5] fix: show mobile location details on navigation --- src/components/main-component.tsx | 22 +++++++++++++++-- src/components/map-loading.ts | 4 +++- src/components/map.tsx | 3 ++- tests/unit/locations-map.test.tsx | 13 ++++++++-- tests/unit/main-component.test.tsx | 38 ++++++++++++++++++++++++++++++ tests/unit/map-loading.test.ts | 10 +++++--- 6 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 tests/unit/main-component.test.tsx diff --git a/src/components/main-component.tsx b/src/components/main-component.tsx index 35ebabfd..10b87771 100644 --- a/src/components/main-component.tsx +++ b/src/components/main-component.tsx @@ -2,7 +2,9 @@ import { useViewStore } from "@/lib/store"; import classNames from "classnames"; -import { Suspense } from "react"; +import { usePathname } from "next/navigation"; +import { Suspense, useEffect } from "react"; +import { LOCATION_ROUTE } from "./common"; import FiltersPopup from "./filters-popup"; import { MapLoadingAnimation } from "./map-loading-animation"; @@ -16,6 +18,20 @@ export function MainComponent({ const showMapViewOnMobile = useViewStore( (state) => state.showMapViewOnMobile, ); + const setShowMapViewOnMobile = useViewStore( + (state) => state.setShowMapViewOnMobile, + ); + const currentPath = usePathname(); + const [, firstPathComponent, secondPathComponent] = currentPath.split("/"); + const isLocationDetailPage = + firstPathComponent === LOCATION_ROUTE && + typeof secondPathComponent === "string"; + + useEffect(() => { + if (isLocationDetailPage && showMapViewOnMobile) { + setShowMapViewOnMobile(false); + } + }, [isLocationDetailPage, setShowMapViewOnMobile, showMapViewOnMobile]); const classnames = classNames([ "flex-1", @@ -23,7 +39,9 @@ export function MainComponent({ "flex", "flex-col", "md:flex-row", - showMapViewOnMobile ? "showMapOnMobile" : "hideMapOnMobile", + showMapViewOnMobile && !isLocationDetailPage + ? "showMapOnMobile" + : "hideMapOnMobile", ]); return ( diff --git a/src/components/map-loading.ts b/src/components/map-loading.ts index 031bd9ce..bd91c301 100644 --- a/src/components/map-loading.ts +++ b/src/components/map-loading.ts @@ -1,9 +1,11 @@ export function shouldLoadGoogleMap({ viewportWidth, showMapViewOnMobile, + isLocationDetail, }: { viewportWidth: number; showMapViewOnMobile: boolean; + isLocationDetail: boolean; }): boolean { - return viewportWidth >= 768 || showMapViewOnMobile; + return viewportWidth >= 768 || (showMapViewOnMobile && !isLocationDetail); } diff --git a/src/components/map.tsx b/src/components/map.tsx index 0e972fe7..38cd0480 100644 --- a/src/components/map.tsx +++ b/src/components/map.tsx @@ -393,6 +393,7 @@ export default function LocationsMap({ const canLoadMap = shouldLoadGoogleMap({ viewportWidth: window.innerWidth, showMapViewOnMobile, + isLocationDetail: !!locationDetailStub, }); // Deferring the initial load protects mobile LCP. Once the user has @@ -404,7 +405,7 @@ export default function LocationsMap({ updateMapLoading(); window.addEventListener("resize", updateMapLoading); return () => window.removeEventListener("resize", updateMapLoading); - }, [showMapViewOnMobile]); + }, [locationDetailStub, showMapViewOnMobile]); useEffect(() => { if (locationSlugClickedOnMobile) { diff --git a/tests/unit/locations-map.test.tsx b/tests/unit/locations-map.test.tsx index 78fd2a6e..61dfd044 100644 --- a/tests/unit/locations-map.test.tsx +++ b/tests/unit/locations-map.test.tsx @@ -35,6 +35,7 @@ vi.mock("@/components/mobile-tray", () => ({ MobileTray: () => null })); import LocationsMap from "@/components/map"; import { GeoCoordinatesContext } from "@/components/geo-context"; import { useViewStore } from "@/lib/store"; +import { type SimplifiedLocationData } from "@/components/common"; function setViewportWidth(width: number) { Object.defineProperty(window, "innerWidth", { @@ -43,12 +44,12 @@ function setViewportWidth(width: number) { }); } -function renderMap() { +function renderMap(locationDetailStub?: SimplifiedLocationData) { return render( - + , ); } @@ -76,6 +77,14 @@ describe("LocationsMap responsive loading", () => { expect(screen.getByTestId("google-map-provider")).toBeInTheDocument(); }); + it("does not initialize a hidden map on a mobile location detail route", () => { + setViewportWidth(390); + useViewStore.setState({ showMapViewOnMobile: true }); + renderMap({} as SimplifiedLocationData); + + expect(screen.queryByTestId("google-map-provider")).not.toBeInTheDocument(); + }); + it("preserves an initialized map while resizing in both directions", () => { setViewportWidth(1024); renderMap(); diff --git a/tests/unit/main-component.test.tsx b/tests/unit/main-component.test.tsx new file mode 100644 index 00000000..fcb0e6e1 --- /dev/null +++ b/tests/unit/main-component.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const route = vi.hoisted(() => ({ pathname: "/locations" })); + +vi.mock("next/navigation", () => ({ + usePathname: () => route.pathname, +})); + +vi.mock("@/components/filters-popup", () => ({ default: () => null })); + +import { MainComponent } from "@/components/main-component"; +import { useViewStore } from "@/lib/store"; + +describe("MainComponent mobile route transitions", () => { + beforeEach(() => { + useViewStore.setState({ showMapViewOnMobile: false }); + route.pathname = "/locations"; + }); + + it("resets map view and displays location details after navigating from the mobile map", () => { + route.pathname = "/locations/example-location"; + useViewStore.setState({ showMapViewOnMobile: true }); + + render(} sidePanel={
} />); + + expect(screen.getByRole("main")).toHaveClass("hideMapOnMobile"); + expect(useViewStore.getState().showMapViewOnMobile).toBe(false); + }); + + it("continues to show the map for non-detail mobile routes", () => { + useViewStore.setState({ showMapViewOnMobile: true }); + + render(} sidePanel={
} />); + + expect(screen.getByRole("main")).toHaveClass("showMapOnMobile"); + }); +}); diff --git a/tests/unit/map-loading.test.ts b/tests/unit/map-loading.test.ts index 04b8f144..8cb5c636 100644 --- a/tests/unit/map-loading.test.ts +++ b/tests/unit/map-loading.test.ts @@ -7,21 +7,23 @@ describe("shouldLoadGoogleMap", () => { shouldLoadGoogleMap({ viewportWidth: 390, showMapViewOnMobile: false, + isLocationDetail: true, }), ).toBe(false); }); - it("loads the map when a mobile location detail user selects map view", () => { + it("does not load the hidden map when a mobile location detail route is opened", () => { expect( shouldLoadGoogleMap({ viewportWidth: 390, showMapViewOnMobile: true, + isLocationDetail: true, }), - ).toBe(true); + ).toBe(false); }); it("loads the map for a mobile list page after the map toggle changes", () => { - const mobileList = { viewportWidth: 390 }; + const mobileList = { viewportWidth: 390, isLocationDetail: false }; expect( shouldLoadGoogleMap({ ...mobileList, showMapViewOnMobile: false }), @@ -36,6 +38,7 @@ describe("shouldLoadGoogleMap", () => { shouldLoadGoogleMap({ viewportWidth: 1024, showMapViewOnMobile: false, + isLocationDetail: true, }), ).toBe(true); }); @@ -45,6 +48,7 @@ describe("shouldLoadGoogleMap", () => { shouldLoadGoogleMap({ viewportWidth: 768, showMapViewOnMobile: false, + isLocationDetail: false, }), ).toBe(true); }); From c608e6e1dae67d14848a48820ca998a235755fdc Mon Sep 17 00:00:00 2001 From: Streetlives-codex Date: Fri, 24 Jul 2026 16:48:47 -0400 Subject: [PATCH 5/5] fix: retain mobile location map preview --- .../location-detail/street-view.tsx | 143 +++++++++++++++--- tests/unit/street-view-component.test.tsx | 60 ++++++++ 2 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 tests/unit/street-view-component.test.tsx diff --git a/src/components/location-detail/street-view.tsx b/src/components/location-detail/street-view.tsx index 8269e05b..3be8c596 100644 --- a/src/components/location-detail/street-view.tsx +++ b/src/components/location-detail/street-view.tsx @@ -1,37 +1,138 @@ "use client"; -import { YourPeerLegacyLocationData } from "@/components/common"; +import { + Position, + SimplifiedLocationData, + YourPeerLegacyLocationData, +} from "@/components/common"; +import LocationStubMarker from "@/components/location-stub-marker"; +import { + activeMarkerIcon, + defaultZoom, + mapStyles, +} from "@/components/map-common"; import { buildStreetViewUrls } from "@/lib/streetView"; +import { + APIProvider, + Map, + MapCameraChangedEvent, + Marker, +} from "@vis.gl/react-google-maps"; +import { useCallback, useEffect, useState } from "react"; export default function StreetView({ location, }: { location: YourPeerLegacyLocationData; }) { + const [zoom, setZoom] = useState(defaultZoom); + const [mapCenter, setMapCenter] = useState(location); + const [locationStubs, setLocationStubs] = useState( + [], + ); + const { imageUrl, mapsUrl } = buildStreetViewUrls(location, { size: "600x500", }); + // TODO: eliminate duplicate code + const handleCameraChange = useCallback( + (ev: MapCameraChangedEvent) => { + const googleMapDiv = ev.map.getDiv(); + + // if google map is already hidden, then ignore the event, because we get a weird zoom + if ( + !googleMapDiv || + (googleMapDiv.clientHeight === 0 && googleMapDiv.clientWidth === 0) + ) + return; + + const newCenter = ev.detail.center; + if ( + newCenter.lat !== 0 && + newCenter.lng !== 0 && + (mapCenter.lat !== newCenter.lat || mapCenter.lng !== newCenter.lng) + ) { + setMapCenter(newCenter); + } + + const newZoom = ev.detail.zoom; + if (newZoom && newZoom !== zoom) { + setZoom(newZoom); + } + }, + [mapCenter, setMapCenter, zoom, setZoom], + ); + + // TODO: call the locationStubs API + useEffect(() => {}, []); + return location.closed ? undefined : ( - - - - Open Street View - - +
+ +
+
+ + + + + {locationStubs + ? locationStubs + .filter((locationStub) => locationStub.id !== location.id) + .map((locationStub) => ( + + )) + : undefined} + + + +
+ + Open Street View + +
+
); } diff --git a/tests/unit/street-view-component.test.tsx b/tests/unit/street-view-component.test.tsx new file mode 100644 index 00000000..d087c240 --- /dev/null +++ b/tests/unit/street-view-component.test.tsx @@ -0,0 +1,60 @@ +import { render, screen } from "@testing-library/react"; +import { type ReactNode } from "react"; +import { describe, expect, it, vi } from "vitest"; + +vi.hoisted(() => { + process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY = "test-api-key"; +}); + +vi.mock("@vis.gl/react-google-maps", () => ({ + APIProvider: ({ children }: { children: ReactNode }) => <>{children}, + Map: ({ children }: { children: ReactNode }) => ( +
{children}
+ ), + Marker: () =>
, +})); + +vi.mock("@/components/location-stub-marker", () => ({ default: () => null })); + +import StreetView from "@/components/location-detail/street-view"; +import { type YourPeerLegacyLocationData } from "@/components/common"; + +const LOCATION = { + lat: 40.6319, + lng: -74.0298, + closed: false, + name: "Example location", + streetview: null, +} as YourPeerLegacyLocationData; + +describe("StreetView", () => { + it("keeps the interactive location map and marker for the mobile layout", () => { + render(); + + expect(screen.getByTestId("location-mini-map")).toBeInTheDocument(); + expect(screen.getByTestId("location-marker")).toBeInTheDocument(); + expect( + document.querySelector("#miniMap")?.parentElement?.className, + ).toContain("md:hidden"); + expect( + screen.getAllByRole("link", { name: "Open Street View" })[1], + ).toHaveAttribute("href", expect.stringContaining("google.com/maps")); + }); + + it("keeps the static Street View image for desktop", () => { + render(); + + const image = document.querySelector("img"); + expect(image).not.toBeNull(); + expect(image).toHaveAttribute("loading", "lazy"); + expect(image?.className).toContain("object-cover"); + }); + + it("does not render a map or preview for closed locations", () => { + const { container } = render( + , + ); + + expect(container).toBeEmptyDOMElement(); + }); +});