diff --git a/assets/src/components/map.tsx b/assets/src/components/map.tsx index 86df0e524..3def2e3fd 100644 --- a/assets/src/components/map.tsx +++ b/assets/src/components/map.tsx @@ -37,7 +37,7 @@ import { createControlComponent } from "@react-leaflet/core" import { StateDispatchContext } from "../contexts/stateDispatchContext" import { joinClasses } from "../helpers/dom" import { TrainVehicle, Vehicle, VehicleId } from "../realtime.d" -import { DirectionId, Shape, Stop } from "../schedule" +import { Shape, Stop } from "../schedule" import { equalByElements } from "../helpers/array" import inTestGroup, { TestGroups } from "../userInTestGroup" import { @@ -72,7 +72,6 @@ export interface Props { allowStreetView?: boolean streetViewInitiallyEnabled?: boolean allowFullscreen?: boolean - stopCardDirection?: DirectionId includeStopCard?: boolean stations?: Stop[] | null } @@ -471,7 +470,6 @@ const Map = (props: Props): ReactElement => { { +export const StopMarkerWithStopCard = ({ ...props }: StopMarkerProps) => { const [isSelected, setIsSelected] = useState(props.selected || false) const popupHandlers: LeafletEventHandlerFnMap = { @@ -229,7 +225,7 @@ export const StopMarkerWithStopCard = ({ eventHandlers={{ ...props.eventHandlers, ...popupHandlers }} selected={isSelected} > - + ) } @@ -254,7 +250,7 @@ export type InteractiveStopMarkerProps = { export const StopMarkerWithInfo = ({ includeStopCard = false, ...props -}: StopMarkerProps & StopCardProps & InteractiveStopMarkerProps) => +}: StopMarkerProps & InteractiveStopMarkerProps) => includeStopCard ? ( ) : ( diff --git a/assets/src/components/mapMarkers.tsx b/assets/src/components/mapMarkers.tsx index 4c98ab19e..57bfa3d8c 100644 --- a/assets/src/components/mapMarkers.tsx +++ b/assets/src/components/mapMarkers.tsx @@ -9,7 +9,7 @@ import { joinClasses } from "../helpers/dom" import vehicleLabelString from "../helpers/vehicleLabel" import { drawnStatus, statusClasses } from "../models/vehicleStatus" import { TrainVehicle, Vehicle } from "../realtime" -import { DirectionId, Shape, Stop, StopId } from "../schedule" +import { Shape, Stop } from "../schedule" import { UserSettings } from "../userSettings" import garages, { Garage } from "../data/garages" @@ -257,72 +257,113 @@ export const StationMarker = React.memo( } ) -export const RouteStopMarkers = ({ +/** + * @returns a list of stops at unique locations. Where a platform stop is at the exact location of a station, the list will include the station. + */ +const uniqueStopsByLocation = (stops: Stop[]) => { + const locationToStop: Record = {} + stops.forEach((stop) => { + const key = `${stop.lat}_${stop.lon}` + const existingStopAtLocation = locationToStop[key] + if ( + existingStopAtLocation === undefined || + stop.locationType === LocationType.Station + ) { + locationToStop[key] = stop + } + }) + return Object.values(locationToStop) +} + +export const StopMarkers = ({ stops, zoomLevel, - direction, includeStopCard, + zoomLevelConfig = {}, }: { stops: Stop[] zoomLevel: number - direction?: DirectionId includeStopCard?: boolean + zoomLevelConfig?: { + minStopZoom?: number + minStationZoom?: number + } }): JSX.Element => { - const seenStopIds = new Set() - // Keep the first occurrence of each stop when there are duplicates - const uniqueStops: Stop[] = stops.flatMap((stop) => { - if (!seenStopIds.has(stop.id)) { - seenStopIds.add(stop.id) - return [stop] - } - return [] - }) + const { minStopZoom = 17, minStationZoom = 15 } = zoomLevelConfig + const uniqueStops: Stop[] = uniqueStopsByLocation(stops) const streetViewActive = useContext(StreetViewModeEnabledContext) return ( <> - {uniqueStops.map((stop) => - stop.locationType === LocationType.Station ? ( - - ) : ( - { - const url = streetViewUrl({ - latitude: stop.lat, - longitude: stop.lon, - }) - window.FS?.event( - "User clicked map bus stop to open street view", - { - streetViewUrl_str: url, - clickedMapAt: { - latitude_real: stop.lat, - longitude_real: stop.lon, + {uniqueStops.map((stop) => { + switch (stop.locationType) { + case LocationType.Station: { + return ( + zoomLevel >= minStationZoom && ( + + ) + ) + } + default: { + return ( + zoomLevel >= minStopZoom && ( + { + const url = streetViewUrl({ + latitude: stop.lat, + longitude: stop.lon, + }) + window.FS?.event( + "User clicked map bus stop to open street view", + { + streetViewUrl_str: url, + clickedMapAt: { + latitude_real: stop.lat, + longitude_real: stop.lon, + }, + } + ) + window.open(url, "_blank") }, } - ) - window.open(url, "_blank") - }, + : {} } - : {} - } - /> - ) - )} + /> + ) + ) + } + } + })} ) } +export const RouteStopMarkers = (props: { + stops: Stop[] + zoomLevel: number + includeStopCard?: boolean +}): JSX.Element => { + return ( + + ) +} + export const RouteShape = React.memo( ({ shape, diff --git a/assets/src/components/mapPage/mapDisplay.tsx b/assets/src/components/mapPage/mapDisplay.tsx index b089c6706..99507a86f 100644 --- a/assets/src/components/mapPage/mapDisplay.tsx +++ b/assets/src/components/mapPage/mapDisplay.tsx @@ -1,11 +1,10 @@ import Leaflet from "leaflet" -import React, { useContext, useEffect, useState } from "react" -import { Pane } from "react-leaflet" +import React, { useContext, useEffect, useMemo, useState } from "react" +import { Pane, useMap } from "react-leaflet" import { SocketContext } from "../../contexts/socketContext" import useMostRecentVehicleById from "../../hooks/useMostRecentVehicleById" import usePatternsByIdForRoute from "../../hooks/usePatternsByIdForRoute" import useSocket from "../../hooks/useSocket" -import { useStations } from "../../hooks/useStations" import useVehiclesForRoute from "../../hooks/useVehiclesForRoute" import { isVehicle, filterVehicles } from "../../models/vehicle" import { Ghost, Vehicle, VehicleId } from "../../realtime" @@ -14,6 +13,7 @@ import { RouteId, RoutePattern, RoutePatternId, + Stop, } from "../../schedule" import { RoutePatternIdentifier, @@ -33,6 +33,7 @@ import { LocationMarker, RouteShape, RouteStopMarkers, + StopMarkers, VehicleMarker, } from "../mapMarkers" import { MapSafeAreaContext } from "../../contexts/mapSafeAreaContext" @@ -44,6 +45,8 @@ import { setTileType } from "../../state/mapLayersState" import { TileType } from "../../tilesetUrls" import { LayersControl } from "../map/controls/layersControl" import { LocationSearchResult } from "../../models/locationSearchResult" +import { useAllStops } from "../../hooks/useAllStops" +import { LocationType, RouteType } from "../../models/stopData" const SecondaryRouteVehicles = ({ selectedVehicleRoute, @@ -206,7 +209,6 @@ const RoutePatternLayers = ({ @@ -227,11 +229,13 @@ const SelectedVehicleDataLayers = ({ routePatterns, selectVehicle, setStateClasses, + stops, }: { vehicleOrGhost: Vehicle | Ghost | null routePatterns: ByRoutePatternId | null selectVehicle: (vehicleOrGhost: Vehicle | Ghost) => void setStateClasses: (classes: string | undefined) => void + stops: Stop[] }) => { const position = (selectedVehicleOrGhost && @@ -257,6 +261,10 @@ const SelectedVehicleDataLayers = ({ isVehicle(selectedVehicleOrGhost) && !selectedVehicleOrGhost.isShuttle + const routePatternStopIdSet = new Set( + (routePatternForVehicle?.shape?.stops || []).map((s) => s.id) + ) + useEffect(() => { setStateClasses(FollowerStatusClasses(followerState.shouldFollow)) }, [followerState.shouldFollow, setStateClasses]) @@ -292,7 +300,14 @@ const SelectedVehicleDataLayers = ({ )} )} - + !routePatternStopIdSet.has(s.id)) + : stops + } + /> | null selectVehicle: (vehicleOrGhost: Vehicle | Ghost) => void setStateClasses: (classes: string | undefined) => void + stops: Stop[] }) => { const selectedRoutePattern: RoutePattern | undefined = routePatterns ? routePatterns[routePatternIdentifier.routePatternId] @@ -323,6 +340,10 @@ const SelectedRouteDataLayers = ({ : [] const followerState = useInteractiveFollowerState() + const routePatternStopIdSet = new Set( + (selectedRoutePattern?.shape?.stops || []).map((s) => s.id) + ) + useEffect(() => { setStateClasses(FollowerStatusClasses(followerState.shouldFollow)) }, [followerState.shouldFollow, setStateClasses]) @@ -344,6 +365,9 @@ const SelectedRouteDataLayers = ({ positions={routeShapePositions} {...followerState} /> + !routePatternStopIdSet.has(s.id))} + /> ) } @@ -373,7 +397,7 @@ const SelectedLocationDataLayer = ({ ) } -const SelectionDataLayers = ({ +const DataLayers = ({ selectedEntity, setSelection, setStateClasses, @@ -389,6 +413,8 @@ const SelectionDataLayers = ({ fetchedSelectedLocation ) + const stops = useAllStops() || [] + const routePatternIdentifier = routePatternIdentifierForSelection(liveSelectedEntity) @@ -434,6 +460,7 @@ const SelectionDataLayers = ({ routePatterns={routePatterns} selectVehicle={selectVehicle} setStateClasses={setStateClasses} + stops={stops} /> ) case SelectedEntityType.RoutePattern: @@ -446,20 +473,64 @@ const SelectionDataLayers = ({ routePatterns={routePatterns} selectVehicle={selectVehicle} setStateClasses={setStateClasses} + stops={stops} /> ) case SelectedEntityType.Location: return ( - + <> + + + ) default: - return + return ( + <> + + + + ) } } +const NearbyStops = ({ stops }: { stops: Stop[] }) => { + const stationsAndBus = useMemo( + () => + stops.filter( + (s) => + s.locationType === LocationType.Station || + s.vehicleType === RouteType.Bus + ), + [stops] + ) + const [nearbyStops, setNearbyStops] = useState([]) + const map = useMap() + map.addEventListener("moveend", () => { + const bounds = map.getBounds() + // Only show nearby stations or bus stops + setNearbyStops( + stationsAndBus.filter((s) => bounds.contains([s.lat, s.lon])) + ) + }) + + return ( + + {(zoomLevel) => { + return ( + + ) + }} + + ) +} + const MapDisplay = ({ selectedEntity, setSelection, @@ -471,8 +542,6 @@ const MapDisplay = ({ streetViewInitiallyEnabled?: boolean fetchedSelectedLocation: LocationSearchResult | null }) => { - const stations = useStations() - const [stateClasses, setStateClasses] = useState( undefined ) @@ -490,7 +559,6 @@ const MapDisplay = ({ vehicles={[]} allowStreetView={true} includeStopCard={true} - stations={stations} shapes={[]} stateClasses={stateClasses} streetViewInitiallyEnabled={streetViewInitiallyEnabled} @@ -502,7 +570,7 @@ const MapDisplay = ({ paddingBottomRight: [50, 20], }} > - { const routesLabelId = "stop-card-routes-label-" + useId() @@ -78,11 +76,6 @@ const StopCard = ({ >
{stop.name}
- {direction !== undefined && ( -
- {direction == 1 ? "Inbound" : "Outbound"} -
- )}
{routes.length > 0 ? (
diff --git a/assets/tests/components/mapMarkers.test.tsx b/assets/tests/components/mapMarkers.test.tsx index 0f6157464..9214bc8e7 100644 --- a/assets/tests/components/mapMarkers.test.tsx +++ b/assets/tests/components/mapMarkers.test.tsx @@ -5,6 +5,7 @@ import { RouteShape, RouteStopMarkers, StationMarker, + StopMarkers, TrainVehicleMarker, VehicleMarker, } from "../../src/components/mapMarkers" @@ -19,6 +20,10 @@ import { LocationType } from "../../src/models/stopData" import useDeviceSupportsHover from "../../src/hooks/useDeviceSupportsHover" import { mockFullStoryEvent } from "../testHelpers/mockHelpers" import { StopMarkerWithInfo } from "../../src/components/map/markers/stopMarker" +import { + getAllStationIcons, + getAllStopIcons, +} from "../testHelpers/selectors/components/mapPage/map" const originalScrollTo = global.scrollTo // Clicking/moving map calls scrollTo under the hood @@ -80,10 +85,9 @@ describe("StopMarkerWithInfo", () => { mockFullStoryEvent() const { container } = renderInMap( - + ) await userEvent.click(container.querySelector(".c-vehicle-map__stop")!) - expect(screen.getByText("Outbound")).toBeInTheDocument() expect(window.FS!.event).toHaveBeenCalledWith("Bus stop card opened") }) }) @@ -119,6 +123,53 @@ describe("StationMarker", () => { }) }) +describe("StopMarkers", () => { + test("When zoom = 14, renders no markers ", () => { + const { container } = renderInMap( + + ) + + expect(getAllStationIcons(container)).toHaveLength(0) + expect(getAllStopIcons(container)).toHaveLength(0) + }) + test("When zoom = 15, renders station markers only", () => { + const { container } = renderInMap( + + ) + + expect(getAllStationIcons(container)).toHaveLength(1) + expect(getAllStopIcons(container)).toHaveLength(0) + }) + + test("When zoom = 17, renders station and stop markers", () => { + const { container } = renderInMap( + + ) + + expect(getAllStationIcons(container)).toHaveLength(1) + expect(getAllStopIcons(container)).toHaveLength(1) + }) + + test("When a stop has the same location as a station, renders the station", () => { + const { container } = renderInMap( + + ) + + expect(getAllStationIcons(container)).toHaveLength(1) + expect(getAllStopIcons(container)).toHaveLength(0) + }) +}) + describe("RouteStopMarkers", () => { test("Returns station and stop markers", () => { const { container } = renderInMap( @@ -129,12 +180,23 @@ describe("RouteStopMarkers", () => { expect(container.querySelectorAll(".c-vehicle-map__stop")).toHaveLength(1) }) - test("Deduplicates list by stop id", () => { + test("When a stop has the same location as a station, renders the station", () => { const { container } = renderInMap( - + ) - expect(container.querySelectorAll(".c-vehicle-map__stop")).toHaveLength(1) + expect(getAllStationIcons(container)).toHaveLength(1) + expect(getAllStopIcons(container)).toHaveLength(0) }) }) diff --git a/assets/tests/components/mapPage.test.tsx b/assets/tests/components/mapPage.test.tsx index a44ab370c..d013fea1c 100644 --- a/assets/tests/components/mapPage.test.tsx +++ b/assets/tests/components/mapPage.test.tsx @@ -25,7 +25,7 @@ import * as dateTime from "../../src/util/dateTime" import userEvent from "@testing-library/user-event" import useVehicleForId from "../../src/hooks/useVehicleForId" -import { useStations } from "../../src/hooks/useStations" +import { useAllStops } from "../../src/hooks/useAllStops" import { LocationType } from "../../src/models/stopData" import { SearchPageState, @@ -78,6 +78,10 @@ import getTestGroups from "../../src/userTestGroups" import { TestGroups } from "../../src/userInTestGroup" import locationSearchResultFactory from "../factories/locationSearchResult" import { useLocationSearchResultById } from "../../src/hooks/useLocationSearchResultById" +import { + getAllStationIcons, + getAllStopIcons, +} from "../testHelpers/selectors/components/mapPage/map" jest.mock("../../src/hooks/useSearchResults", () => ({ __esModule: true, @@ -121,9 +125,9 @@ jest.mock("../../src/hooks/useVehiclesForRoute", () => ({ default: jest.fn(() => null), })) -jest.mock("../../src/hooks/useStations", () => ({ +jest.mock("../../src/hooks/useAllStops", () => ({ __esModule: true, - useStations: jest.fn(() => []), + useAllStops: jest.fn(() => []), })) jest.mock("../../src/tilesetUrls", () => ({ @@ -168,10 +172,6 @@ function getMapSearchPanel() { }) } -function getAllStationIcons(container: HTMLElement): NodeListOf { - return container.querySelectorAll(".c-station-icon") -} - beforeAll(() => { mockTileUrls() }) @@ -295,11 +295,73 @@ describe("", () => { expect(dispatch).not.toHaveBeenCalledWith(closeView()) }) - test("renders stations on zoom", async () => { - ;(useStations as jest.Mock).mockReturnValue( - stopFactory.params({ locationType: LocationType.Station }).buildList(3) + test("renders nearby stations on zoom = 15", async () => { + setHtmlWidthHeightForLeafletMap() + ;(useAllStops as jest.Mock).mockReturnValue([ + // 2 stations at map center which should be visible + stopFactory.build({ + locationType: LocationType.Station, + }), + stopFactory.build({ + locationType: LocationType.Station, + }), + // 1 station not near center which should not be visible + stopFactory.build({ + locationType: LocationType.Station, + lat: 42.0, + lon: -71.0, + }), + // 1 stop near center which should not be visible + stopFactory.build({ + locationType: LocationType.Stop, + }), + ]) + + const { container } = render( + + + ) + expect(getAllStationIcons(container)).toHaveLength(0) + expect(getAllStopIcons(container)).toHaveLength(0) + + const zoomIn = zoomInButton.get() + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + + expect(getAllStationIcons(container)).toHaveLength(2) + expect(getAllStopIcons(container)).toHaveLength(0) + }) + + test("renders all nearby stops and stations only on zoom = 17", async () => { + setHtmlWidthHeightForLeafletMap() + ;(useAllStops as jest.Mock).mockReturnValue([ + // 2 stations at map center which should be visible + stopFactory.build({ + locationType: LocationType.Station, + }), + stopFactory.build({ + locationType: LocationType.Station, + }), + // 1 station not near center which should not be visible + stopFactory.build({ + locationType: LocationType.Station, + lat: 42.0, + lon: -71.0, + }), + // 1 stop near center which should be visible + stopFactory.build({ + locationType: LocationType.Stop, + }), + // 1 stop not near center which should not be visible + stopFactory.build({ + locationType: LocationType.Stop, + lat: 41.0, + lon: -72.0, + }), + ]) + const { container } = render( @@ -307,12 +369,16 @@ describe("", () => { ) expect(getAllStationIcons(container)).toHaveLength(0) + expect(getAllStopIcons(container)).toHaveLength(0) const zoomIn = zoomInButton.get() await userEvent.click(zoomIn) await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) - expect(getAllStationIcons(container)).toHaveLength(3) + expect(getAllStationIcons(container)).toHaveLength(2) + expect(getAllStopIcons(container)).toHaveLength(1) }) test("clicking a vehicle on the map, should set vehicle as new selection", async () => { diff --git a/assets/tests/components/mapPage/mapDisplay.test.tsx b/assets/tests/components/mapPage/mapDisplay.test.tsx index b1d09ddfc..6e0df5f3f 100644 --- a/assets/tests/components/mapPage/mapDisplay.test.tsx +++ b/assets/tests/components/mapPage/mapDisplay.test.tsx @@ -9,10 +9,9 @@ import MapDisplay from "../../../src/components/mapPage/mapDisplay" import { RoutesProvider } from "../../../src/contexts/routesContext" import usePatternsByIdForRoute from "../../../src/hooks/usePatternsByIdForRoute" import { useRouteShapes } from "../../../src/hooks/useShapes" -import { useStations } from "../../../src/hooks/useStations" import useVehicleForId from "../../../src/hooks/useVehicleForId" import useVehiclesForRoute from "../../../src/hooks/useVehiclesForRoute" -import { LocationType } from "../../../src/models/stopData" +import { LocationType, RouteType } from "../../../src/models/stopData" import { Ghost, VehicleId, @@ -44,6 +43,11 @@ import { stopIcon } from "../../testHelpers/selectors/components/map/markers/sto import { routePropertiesCard } from "../../testHelpers/selectors/components/mapPage/routePropertiesCard" import { vehiclePropertiesCard } from "../../testHelpers/selectors/components/mapPage/vehiclePropertiesCard" import locationSearchResultFactory from "../../factories/locationSearchResult" +import { useAllStops } from "../../../src/hooks/useAllStops" +import { + getAllStationIcons, + getAllStopIcons, +} from "../../testHelpers/selectors/components/mapPage/map" jest.mock("../../../src/hooks/usePatternsByIdForRoute", () => ({ __esModule: true, @@ -69,9 +73,9 @@ jest.mock("../../../src/hooks/useVehiclesForRoute", () => ({ default: jest.fn(() => null), })) -jest.mock("../../../src/hooks/useStations", () => ({ +jest.mock("../../../src/hooks/useAllStops", () => ({ __esModule: true, - useStations: jest.fn(() => []), + useAllStops: jest.fn(() => []), })) jest.mock("../../../src/hooks/useShapes", () => ({ @@ -109,16 +113,81 @@ function mockUseVehiclesForRouteMap(map: { ).mockImplementation((_, routeId: RouteId | null) => map[routeId!] || null) } -function getAllStationIcons(container: HTMLElement): NodeListOf { - return container.querySelectorAll(".c-station-icon") -} - describe("", () => { - test("renders stations on zoom", async () => { - ;(useStations as jest.Mock).mockReturnValue( - stopFactory.params({ locationType: LocationType.Station }).buildList(3) + test("renders nearby stations only on zoom = 15", async () => { + setHtmlWidthHeightForLeafletMap() + ;(useAllStops as jest.Mock).mockReturnValue([ + // 2 stations at map center should be visible + stopFactory.build({ + locationType: LocationType.Station, + }), + stopFactory.build({ + locationType: LocationType.Station, + }), + // 1 station not near center which should not be visible + stopFactory.build({ + locationType: LocationType.Station, + lat: 42.0, + lon: -71.0, + }), + // 1 stop near center which should not be visible + stopFactory.build({ + locationType: LocationType.Stop, + }), + ]) + + const { container } = render( + ) + expect(getAllStationIcons(container)).toHaveLength(0) + expect(getAllStopIcons(container)).toHaveLength(0) + + const zoomIn = zoomInButton.get() + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + + expect(getAllStationIcons(container)).toHaveLength(2) + expect(getAllStopIcons(container)).toHaveLength(0) + }) + + test("renders all nearby stations and bus stops only on zoom = 17", async () => { + setHtmlWidthHeightForLeafletMap() + ;(useAllStops as jest.Mock).mockReturnValue([ + // 2 stations at map center which should be visible + stopFactory.build({ + locationType: LocationType.Station, + }), + stopFactory.build({ + locationType: LocationType.Station, + }), + // 1 station not near center which should not be visible + stopFactory.build({ + locationType: LocationType.Station, + lat: 42.0, + lon: -71.0, + }), + // 1 stop near center which should be visible + stopFactory.build({ + locationType: LocationType.Stop, + }), + // 1 subway near center which should not be visible + stopFactory.build({ + locationType: LocationType.Stop, + vehicleType: RouteType.Subway, + }), + // 1 stop not near center which should not be visible + stopFactory.build({ + locationType: LocationType.Stop, + lat: 41.0, + lon: -72.0, + }), + ]) + const { container } = render( ", () => { ) expect(getAllStationIcons(container)).toHaveLength(0) + expect(getAllStopIcons(container)).toHaveLength(0) const zoomIn = zoomInButton.get() await userEvent.click(zoomIn) await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + + expect(getAllStationIcons(container)).toHaveLength(2) + expect(getAllStopIcons(container)).toHaveLength(1) + }) + + test("when zoomed in to see nearby stop and a vehicle is selected, only renders stops on that vehicle's route pattern once", async () => { + setHtmlWidthHeightForLeafletMap() + + const stop = stopFactory.build({ + locationType: LocationType.Stop, + }) + + const route = routeFactory.build() + + const routePattern = routePatternFactory.build({ + routeId: route.id, + shape: shapeFactory.build({ stops: [stop] }), + }) + + const selectedVehicle = randomLocationVehicle.build({ + routeId: route.id, + runId: runIdFactory.build(), + routePatternId: routePattern.id, + }) + + mockUseVehicleForId([selectedVehicle]) + mockUseVehiclesForRouteMap({ [route.id]: [selectedVehicle] }) + ;(usePatternsByIdForRoute as jest.Mock).mockReturnValue({ + [routePattern.id]: routePattern, + }) + ;(useAllStops as jest.Mock).mockReturnValue([stop]) + + const { container } = render( + + ) + + expect(getAllStopIcons(container)).toHaveLength(1) + + const zoomIn = zoomInButton.get() + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + + expect(getAllStopIcons(container)).toHaveLength(1) + }) + + test("when zoomed in to see nearby stop and a route pattern is selected, only renders stops on that route pattern once", async () => { + setHtmlWidthHeightForLeafletMap() + + const stop = stopFactory.build({ + locationType: LocationType.Stop, + }) + + const route = routeFactory.build() + const routePattern = routePatternFactory.build({ + routeId: route.id, + shape: shapeFactory.build({ stops: [stop] }), + }) + + ;(usePatternsByIdForRoute as jest.Mock).mockReturnValue({ + [routePattern.id]: routePattern, + }) + ;(useAllStops as jest.Mock).mockReturnValue([stop]) + + const { container } = render( + + ) + + expect(getAllStopIcons(container)).toHaveLength(1) + + const zoomIn = zoomInButton.get() + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) + await userEvent.click(zoomIn) - expect(getAllStationIcons(container)).toHaveLength(3) + expect(getAllStopIcons(container)).toHaveLength(1) }) test("clicking a vehicle on the map, should set vehicle as new selection", async () => { diff --git a/assets/tests/components/stopCard.test.tsx b/assets/tests/components/stopCard.test.tsx index 23ad81e8d..17bfe2979 100644 --- a/assets/tests/components/stopCard.test.tsx +++ b/assets/tests/components/stopCard.test.tsx @@ -24,32 +24,6 @@ describe("StopCard", () => { ).not.toBeInTheDocument() }) - test("doesn't render direction when none is present", () => { - const stop = stopFactory.build() - - render() - - expect(screen.queryByText(/Inbound/)).not.toBeInTheDocument() - - expect(screen.queryByText(/Outbound/)).not.toBeInTheDocument() - }) - - test("render inbound direction", () => { - const stop = stopFactory.build() - - render() - - expect(screen.getByText(/Inbound/)).toBeInTheDocument() - }) - - test("render outbound direction", () => { - const stop = stopFactory.build() - - render() - - expect(screen.getByText(/Outbound/)).toBeInTheDocument() - }) - test("when routes are present, renders the routes", () => { const stop = stopFactory.build({ routes: [{ type: 1, name: "Red", id: "Red" }], diff --git a/assets/tests/factories/stop.ts b/assets/tests/factories/stop.ts index 968c10ff5..75beae010 100644 --- a/assets/tests/factories/stop.ts +++ b/assets/tests/factories/stop.ts @@ -1,15 +1,19 @@ import { Factory } from "fishery" import { LocationType } from "../../src/models/stopData" import { Stop } from "../../src/schedule" +import { localGeoCoordinateFactory } from "./geoCoordinate" -const stopFactory = Factory.define(({ sequence }) => ({ - id: `stop${sequence}`, - name: `Some Stop - ${sequence}`, - locationType: LocationType.Stop, - vehicleType: 3, - lat: 0, - lon: 0, - routes: undefined, -})) +const stopFactory = Factory.define(({ sequence }) => { + const coord = localGeoCoordinateFactory.build() + return { + id: `stop${sequence}`, + name: `Some Stop - ${sequence}`, + locationType: LocationType.Stop, + vehicleType: 3, + lat: coord.latitude, + lon: coord.longitude, + routes: undefined, + } +}) export default stopFactory