import { test, expect } from '@playwright/test' import { closeOnboardingModal } from '../../helpers/navigation.js' import { navigateToMapsV2WithDate, waitForLoadingComplete, clickMapAt, hasPopup } from '../helpers/setup.js' test.describe('Map Interactions', () => { test.beforeEach(async ({ page }) => { await page.goto('/map/v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59') await closeOnboardingModal(page) await waitForLoadingComplete(page) await page.waitForTimeout(500) }) test.describe('Point Clicks', () => { test('shows popup when clicking on point', async ({ page }) => { await page.waitForTimeout(1000) // Try clicking at different positions to find a point const positions = [ { x: 400, y: 300 }, { x: 500, y: 300 }, { x: 600, y: 400 }, { x: 350, y: 250 } ] let popupFound = false for (const pos of positions) { try { await clickMapAt(page, pos.x, pos.y) await page.waitForTimeout(500) if (await hasPopup(page)) { popupFound = true break } } catch (error) { // Click might fail if map is still loading console.log(`Click at ${pos.x},${pos.y} failed: ${error.message}`) } } if (popupFound) { const popup = page.locator('.maplibregl-popup') await expect(popup).toBeVisible() const popupContent = page.locator('.point-popup') await expect(popupContent).toBeVisible() } else { console.log('No point clicked (points might be clustered or sparse)') } }) }) test.describe('Hover Effects', () => { test('map container is interactive', async ({ page }) => { const mapContainer = page.locator('[data-maps--maplibre-target="container"]') await expect(mapContainer).toBeVisible() }) }) test.describe('Route Interactions', () => { test('route hover layer exists', async ({ page }) => { await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') return controller?.map?.getLayer('routes-hover') !== undefined }, { timeout: 10000 }).catch(() => false) const hasHoverLayer = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') return controller?.map?.getLayer('routes-hover') !== undefined }) expect(hasHoverLayer).toBe(true) }) test('route hover shows yellow highlight', async ({ page }) => { // Wait for routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length > 0 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Get first route's bounding box and hover over its center const routeCenter = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length) return null const route = source._data.features[0] const coords = route.geometry.coordinates // Get middle coordinate of route const midCoord = coords[Math.floor(coords.length / 2)] // Project to pixel coordinates const point = controller.map.project(midCoord) return { x: point.x, y: point.y } }) if (routeCenter) { // Get the canvas element and hover over the route const canvas = page.locator('.maplibregl-canvas') await canvas.hover({ position: { x: routeCenter.x, y: routeCenter.y } }) await page.waitForTimeout(500) // Check if hover source has data (route is highlighted) const isHighlighted = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const hoverSource = controller.map.getSource('routes-hover-source') return hoverSource && hoverSource._data?.features?.length > 0 }) expect(isHighlighted).toBe(true) // Check for emoji markers (start 🚥 and end 🏁) const startMarker = page.locator('.route-emoji-marker:has-text("🚥")') const endMarker = page.locator('.route-emoji-marker:has-text("🏁")') await expect(startMarker).toBeVisible() await expect(endMarker).toBeVisible() } }) test('route click opens info panel with route details', async ({ page }) => { // Wait for routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length > 0 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Get first route's center and click on it const routeCenter = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length) return null const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] const point = controller.map.project(midCoord) return { x: point.x, y: point.y } }) if (routeCenter) { // Click on the route const canvas = page.locator('.maplibregl-canvas') await canvas.click({ position: { x: routeCenter.x, y: routeCenter.y } }) await page.waitForTimeout(500) // Check if info panel is visible const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') await expect(infoDisplay).not.toHaveClass(/hidden/) // Check if info panel has route information title const infoTitle = page.locator('[data-maps--maplibre-target="infoTitle"]') await expect(infoTitle).toHaveText('Route Information') // Check if route details are displayed const infoContent = page.locator('[data-maps--maplibre-target="infoContent"]') const content = await infoContent.textContent() expect(content).toContain('Start:') expect(content).toContain('End:') expect(content).toContain('Duration:') expect(content).toContain('Distance:') expect(content).toContain('Points:') // Check for emoji markers (start 🚥 and end 🏁) const startMarker = page.locator('.route-emoji-marker:has-text("🚥")') const endMarker = page.locator('.route-emoji-marker:has-text("🏁")') await expect(startMarker).toBeVisible() await expect(endMarker).toBeVisible() } }) test('clicked route stays highlighted after mouse moves away', async ({ page }) => { // Wait for routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length > 0 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Click on a route const routeCenter = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length) return null const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] const point = controller.map.project(midCoord) return { x: point.x, y: point.y } }) if (routeCenter) { const canvas = page.locator('.maplibregl-canvas') await canvas.click({ position: { x: routeCenter.x, y: routeCenter.y } }) await page.waitForTimeout(500) // Move mouse away from route await canvas.hover({ position: { x: 100, y: 100 } }) await page.waitForTimeout(500) // Check if route is still highlighted const isStillHighlighted = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const hoverSource = controller.map.getSource('routes-hover-source') return hoverSource && hoverSource._data?.features?.length > 0 }) expect(isStillHighlighted).toBe(true) // Check if info panel is still visible const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') await expect(infoDisplay).not.toHaveClass(/hidden/) } }) test('clicking elsewhere on map deselects route', async ({ page }) => { // Wait for routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length > 0 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Click on a route first const routeCenter = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length) return null const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] const point = controller.map.project(midCoord) return { x: point.x, y: point.y } }) if (routeCenter) { const canvas = page.locator('.maplibregl-canvas') await canvas.click({ position: { x: routeCenter.x, y: routeCenter.y } }) await page.waitForTimeout(500) // Verify route is selected const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') await expect(infoDisplay).not.toHaveClass(/hidden/) // Click elsewhere on map (far from route) await canvas.click({ position: { x: 100, y: 100 } }) await page.waitForTimeout(500) // Check if route is deselected (hover source cleared) const isDeselected = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const hoverSource = controller.map.getSource('routes-hover-source') return hoverSource && hoverSource._data?.features?.length === 0 }) expect(isDeselected).toBe(true) // Check if info panel is hidden await expect(infoDisplay).toHaveClass(/hidden/) } }) test('clicking close button on info panel deselects route', async ({ page }) => { // Wait for routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length > 0 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Click on a route const routeCenter = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length) return null const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] const point = controller.map.project(midCoord) return { x: point.x, y: point.y } }) if (routeCenter) { const canvas = page.locator('.maplibregl-canvas') await canvas.click({ position: { x: routeCenter.x, y: routeCenter.y } }) await page.waitForTimeout(500) // Verify info panel is open const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') await expect(infoDisplay).not.toHaveClass(/hidden/) // Click the close button const closeButton = page.locator('button[data-action="click->maps--maplibre#closeInfo"]') await closeButton.click() await page.waitForTimeout(500) // Check if route is deselected const isDeselected = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const hoverSource = controller.map.getSource('routes-hover-source') return hoverSource && hoverSource._data?.features?.length === 0 }) expect(isDeselected).toBe(true) // Check if info panel is hidden await expect(infoDisplay).toHaveClass(/hidden/) } }) test('route cursor changes to pointer on hover', async ({ page }) => { // Wait for routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length > 0 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Hover over a route const routeCenter = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length) return null const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] const point = controller.map.project(midCoord) return { x: point.x, y: point.y } }) if (routeCenter) { const canvas = page.locator('.maplibregl-canvas') await canvas.hover({ position: { x: routeCenter.x, y: routeCenter.y } }) await page.waitForTimeout(300) // Check cursor style const cursor = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') return controller.map.getCanvas().style.cursor }) expect(cursor).toBe('pointer') } }) test('hovering over different route while one is selected shows both highlighted', async ({ page }) => { // Wait for multiple routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length >= 2 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Get centers of two different routes const routeCenters = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length >= 2) return null const route1 = source._data.features[0] const route2 = source._data.features[1] const coords1 = route1.geometry.coordinates const coords2 = route2.geometry.coordinates const midCoord1 = coords1[Math.floor(coords1.length / 2)] const midCoord2 = coords2[Math.floor(coords2.length / 2)] const point1 = controller.map.project(midCoord1) const point2 = controller.map.project(midCoord2) return { route1: { x: point1.x, y: point1.y }, route2: { x: point2.x, y: point2.y } } }) if (routeCenters) { const canvas = page.locator('.maplibregl-canvas') // Click on first route to select it await canvas.click({ position: { x: routeCenters.route1.x, y: routeCenters.route1.y } }) await page.waitForTimeout(500) // Verify first route is selected const infoDisplay = page.locator('[data-maps--maplibre-target="infoDisplay"]') await expect(infoDisplay).not.toHaveClass(/hidden/) // Hover over second route await canvas.hover({ position: { x: routeCenters.route2.x, y: routeCenters.route2.y } }) await page.waitForTimeout(500) // Check that hover source now has 2 features (both routes highlighted) const featureCount = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const hoverSource = controller.map.getSource('routes-hover-source') return hoverSource && hoverSource._data?.features?.length }) expect(featureCount).toBe(2) // Move mouse away from both routes await canvas.hover({ position: { x: 100, y: 100 } }) await page.waitForTimeout(500) // Check that only selected route remains highlighted (1 feature) const featureCountAfterLeave = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const hoverSource = controller.map.getSource('routes-hover-source') return hoverSource && hoverSource._data?.features?.length }) expect(featureCountAfterLeave).toBe(1) // Check that markers are present for the selected route only const markerCount = await page.locator('.route-emoji-marker').count() expect(markerCount).toBe(2) // Start and end marker for selected route } }) test('clicking elsewhere removes emoji markers', async ({ page }) => { // Wait for routes to be loaded await page.waitForFunction(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') if (!element) return false const app = window.Stimulus || window.Application if (!app) return false const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller?.map?.getSource('routes-source') return source && source._data?.features?.length > 0 }, { timeout: 20000 }) await page.waitForTimeout(1000) // Click on a route const routeCenter = await page.evaluate(() => { const element = document.querySelector('[data-controller*="maps--maplibre"]') const app = window.Stimulus || window.Application const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre') const source = controller.map.getSource('routes-source') if (!source._data?.features?.length) return null const route = source._data.features[0] const coords = route.geometry.coordinates const midCoord = coords[Math.floor(coords.length / 2)] const point = controller.map.project(midCoord) return { x: point.x, y: point.y } }) if (routeCenter) { const canvas = page.locator('.maplibregl-canvas') await canvas.click({ position: { x: routeCenter.x, y: routeCenter.y } }) await page.waitForTimeout(500) // Verify markers are present let markerCount = await page.locator('.route-emoji-marker').count() expect(markerCount).toBe(2) // Click elsewhere on map await canvas.click({ position: { x: 100, y: 100 } }) await page.waitForTimeout(500) // Verify markers are removed markerCount = await page.locator('.route-emoji-marker').count() expect(markerCount).toBe(0) } }) }) })