dawarich/e2e/v2/map/interactions.spec.js
2026-01-06 20:07:20 +01:00

602 lines
24 KiB
JavaScript

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)
}
})
})
})