admin管理员组文章数量:1133745
I have a React component ( Next JS ) :
"use client"
import { getEnv } from "@/actions/env"
import Layers from "@/features/solar-analytics/panel-installation/map/components/layers"
import mapboxgl, { LngLatLike } from "mapbox-gl"
import "mapbox-gl/dist/mapbox-gl.css"
import { useQueryState } from "nuqs"
import React, { useEffect, useRef, useState } from "react"
import { useSelector } from "react-redux"
import { RootState } from "@/redux/store"
import Compass from "@/components/core/compass"
import { createBuildingModelsLayers } from "./layers/3d-building-model"
import { createFootPrintLayers } from "./layers/foot-print"
import { createParcelLayers } from "./layers/parcel"
/* eslint-disable no-console */
type MapViewerProps = {
zoom?: number
className?: string
}
type MapFlyOptions = {
center: [number, number]
zoom: number
pitch: number
essential: boolean
duration: number
speed: number
screenSpeed: number
maxDuration: number
}
export const defaultLocation: [number, number] = [10.4515, 51.1657]
export const MapViewer: React.FC<MapViewerProps> = ({ zoom = 5, className = "h-full w-full" }) => {
const [lng] = useQueryState("lng")
const [lat] = useQueryState("lat")
const [bearing, setBearing] = useQueryState("bearing")
const [layer] = useQueryState("layer")
const mapContainer = useRef<HTMLDivElement>(null)
const mapRef = useRef<mapboxgl.Map | null>(null)
const [mapLoaded, setMapLoaded] = useState(false)
const footPrintGeometry = useSelector((state: RootState) => state.geometry.footPrintGeometry)
const parcelGeometry = useSelector((state: RootState) => state.geometry.parcelGeometry)
const buildingModels = useSelector((state: RootState) => state.geometry.buildingModels)
const footPrintLayersRef = useRef<{
sourceIds: string[]
layerIds: string[]
labelSourceIds: string[]
labelIds: string[]
areaLabelSourceIds: string[]
areaLabelIds: string[]
}>({
sourceIds: [],
layerIds: [],
labelSourceIds: [],
labelIds: [],
areaLabelSourceIds: [],
areaLabelIds: [],
})
useEffect(() => {
const init = async () => {
try {
const env = await getEnv()
const token = env.NEXT_PUBLIC_DOCKER_MAPBOX_ACCESS_TOKEN!
await initializeMap(token)
} catch (error) {
console.error("Failed to initialize map:", error)
}
}
init()
return () => {
if (mapRef.current) {
mapRef.current.remove()
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
navigateToLocation(mapRef, lng as string, lat as string)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lng, lat])
useEffect(() => {
if (!mapRef.current || !parcelGeometry || parcelGeometry.length === 0) return
// Get the ID of the first footPrintGeometry layer
const firstFootPrintGeometrylayer =
footPrintLayersRef.current.layerIds.length > 0 ? footPrintLayersRef.current.layerIds[0] : undefined
const { bounds, cleanup } = createParcelLayers(mapRef.current, parcelGeometry, firstFootPrintGeometrylayer)
if (!bounds.isEmpty()) {
mapRef.current.fitBounds(bounds, {
maxZoom: 20,
pitch: 60,
})
}
return cleanup
}, [parcelGeometry])
useEffect(() => {
if (!mapRef.current || !footPrintGeometry || footPrintGeometry.length === 0) return
const { bounds, layerIds, cleanup } = createFootPrintLayers(mapRef.current, footPrintGeometry)
// Update footPrintLayersRef with the new layer IDs
footPrintLayersRef.current = {
sourceIds: layerIds.map((layer) => layer.sourceId),
layerIds: layerIds.map((layer) => layer.layerId),
labelSourceIds: [],
labelIds: [],
areaLabelSourceIds: [],
areaLabelIds: [],
}
if (!bounds.isEmpty()) {
mapRef.current.fitBounds(bounds, {
maxZoom: 20,
pitch: 60,
})
}
return cleanup
}, [footPrintGeometry])
useEffect(() => {
let layerIds: string[] = []
const init = async () => {
layerIds = await createBuildingModelsLayers(mapRef, buildingModels)
}
init()
return () => {
const map = mapRef.current
if (!map) return
layerIds.forEach((id) => {
// Check if map and its style still exist before cleanup
// This prevents "Cannot read properties of undefined" errors that occur
// When trying to navigate to the next page after the map component has been destroyed
if (map.style) {
if (map.getLayer(id)) map.removeLayer(id)
}
})
}
}, [buildingModels])
// Set the layer based on the URL query parameter (layer)
useEffect(() => {
if (!mapRef.current || !mapLoaded) return
if (layer === "wms") {
addWMS()
} else {
removeWMSLayer()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [layer, mapLoaded])
const initializeMap = async (token: string) => {
if (!mapContainer.current) return
const initialCenter = lng && lat ? [parseFloat(lng), parseFloat(lat)] : defaultLocation
mapboxgl.accessToken = token
const map = new mapboxgl.Map({
container: mapContainer.current,
style: "mapbox://styles/mapbox/satellite-streets-v12",
center: initialCenter as LngLatLike,
zoom,
antialias: true,
bearing: +bearing!,
})
map.on("rotate", () => {
const bearing = map.getBearing();
setBearing(`${bearing}`);
});
map.on("load", () => {
setMapLoaded(true)
})
mapRef.current = map
}
const navigateToLocation = (mapRef: React.MutableRefObject<mapboxgl.Map | null>, lng: string, lat: string) => {
if (!lng || !lat || !mapRef.current) return
const newLocation: [number, number] = [parseFloat(lng), parseFloat(lat)]
const isDefaultLocation = newLocation[0] === defaultLocation[0] && newLocation[1] === defaultLocation[1]
const options: MapFlyOptions = {
center: newLocation,
zoom: isDefaultLocation ? 5 : 20,
pitch: isDefaultLocation ? 0 : 60,
essential: true,
duration: 2000,
speed: 1.2,
screenSpeed: 1.5,
maxDuration: 3000,
}
mapRef.current.flyTo(options)
}
const addWMS = () => {
if (!mapRef.current?.getSource("wms-source")) {
mapRef.current?.addSource("wms-source", {
type: "raster",
tiles: [
";VERSION=1.1.0&REQUEST=GetMap&LAYERS=orthophoto_germany&STYLES=default&SRS=EPSG:3857&WIDTH=256&HEIGHT=256&FORMAT=image/jpeg&BBOX={bbox-epsg-3857}",
],
tileSize: 256,
})
mapRef.current?.addLayer({
id: "wms-layer",
type: "raster",
source: "wms-source",
paint: {},
})
}
moveWMSLayerToTop()
}
const moveWMSLayerToTop = () => {
const map = mapRef.current
if (map) {
const style = map.getStyle()
if (style) {
// Move the WMS layer to the top of all layers
const lastLayerId = style?.layers[style?.layers.length - 1].id
if (lastLayerId) {
map.moveLayer("wms-layer", lastLayerId)
}
}
}
}
const removeWMSLayer = () => {
const map = mapRef.current
if (map) {
if (map.getLayer("wms-layer")) {
map.removeLayer("wms-layer")
map.removeSource("wms-source")
}
}
}
return (
<>
<div ref={mapContainer} className={className} />
<div className='z-50 absolute bottom-20 right-2 flex flex-col gap-2 items-center'>
<Compass bearing={+bearing!} />
<Layers />
</div>
</>
)
}
and I have e2e test with playwright:
import test, { expect } from "@playwright/test"
test.describe("Map Viewer (Home Page)", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/")
await page.waitForLoadState("networkidle")
})
test("should exist on the page", async ({ page }) => {
await expect(page).toHaveURL("/?bearing=0&layer=mapbox")
})
test("should render map container", async ({ page }) => {
await expect(page.locator(".mapboxgl-canvas")).toBeVisible()
})
test("should display compass", async ({ page }) => {
await expect(page.getByTestId("map-compass")).toBeVisible()
})
test("should display layers", async ({ page }) => {
await expect(page.getByTestId("map-layers")).toBeVisible()
})
test("should update URL when selecting wms layer", async ({ page }) => {
await page.getByTestId("wms-layer-card").click()
await expect(page).toHaveURL("/?bearing=0&layer=wms")
})
test("should update URL when selecting mapbox layer", async ({ page }) => {
await page.getByTestId("mapbox-layer-card").click()
await expect(page).toHaveURL("/?bearing=0&layer=mapbox")
})
})
These tests are passed without any issue.
No, I wanted to add a case when the map is rotated ( bearing changes ) , it should be changed in the URL.
I tried with Claude and GPT but it did not work.
test("should update URL when rotating the map to 45 degrees", async ({ page }) => {
// Use page.evaluate to set the bearing to 45
await page.evaluate(() => {
if (window.mapRef) {
window.mapRef.setBearing(45); // Rotate the map to 45 degrees
}
});
// Wait for the URL to update
await page.waitForTimeout(500);
// Assert the URL includes bearing=45
await expect(page).toHaveURL(/bearing=45/);
});
The mapRef is not exposed globally.
本文标签: typescriptHow to simulate mapbox rotation ( bearing ) in Playwright E2E testStack Overflow
版权声明:本文标题:typescript - How to simulate map-box rotation ( bearing ) in Playwright E2E test? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736786456a1952888.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论