mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
462 lines
18 KiB
JavaScript
462 lines
18 KiB
JavaScript
|
|
import { test, expect } from '@playwright/test'
|
||
|
|
import { closeOnboardingModal } from '../../helpers/navigation.js'
|
||
|
|
import { navigateToMapsV2, waitForMapLibre, waitForLoadingComplete } from '../helpers/setup.js'
|
||
|
|
|
||
|
|
test.describe('Live Mode', () => {
|
||
|
|
test.beforeEach(async ({ page }) => {
|
||
|
|
await navigateToMapsV2(page)
|
||
|
|
await closeOnboardingModal(page)
|
||
|
|
await waitForMapLibre(page)
|
||
|
|
await waitForLoadingComplete(page)
|
||
|
|
|
||
|
|
// Wait for layers to be fully initialized
|
||
|
|
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?.layerManager?.layers?.recentPointLayer !== undefined
|
||
|
|
}, { timeout: 10000 })
|
||
|
|
|
||
|
|
await page.waitForTimeout(1000)
|
||
|
|
})
|
||
|
|
|
||
|
|
test.describe('Live Mode Toggle', () => {
|
||
|
|
test('should have live mode toggle in settings', async ({ page }) => {
|
||
|
|
// Open settings
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
// Click Settings tab
|
||
|
|
await page.locator('button[data-tab="settings"]').click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
// Verify Live Mode toggle exists
|
||
|
|
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
|
||
|
|
await expect(liveModeToggle).toBeVisible()
|
||
|
|
|
||
|
|
// Verify label text
|
||
|
|
const label = page.locator('label:has-text("Live Mode")')
|
||
|
|
await expect(label).toBeVisible()
|
||
|
|
|
||
|
|
// Verify description text
|
||
|
|
const description = page.locator('text=Show new points in real-time')
|
||
|
|
await expect(description).toBeVisible()
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should toggle live mode on and off', async ({ page }) => {
|
||
|
|
// Open settings
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
await page.locator('button[data-tab="settings"]').click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
|
||
|
|
|
||
|
|
// Get initial state
|
||
|
|
const initialState = await liveModeToggle.isChecked()
|
||
|
|
|
||
|
|
// Toggle it
|
||
|
|
await liveModeToggle.click()
|
||
|
|
await page.waitForTimeout(500)
|
||
|
|
|
||
|
|
// Verify state changed
|
||
|
|
const newState = await liveModeToggle.isChecked()
|
||
|
|
expect(newState).toBe(!initialState)
|
||
|
|
|
||
|
|
// Toggle back
|
||
|
|
await liveModeToggle.click()
|
||
|
|
await page.waitForTimeout(500)
|
||
|
|
|
||
|
|
// Verify state reverted
|
||
|
|
const finalState = await liveModeToggle.isChecked()
|
||
|
|
expect(finalState).toBe(initialState)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should show toast notification when toggling live mode', async ({ page }) => {
|
||
|
|
// Open settings
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
await page.locator('button[data-tab="settings"]').click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
|
||
|
|
const initialState = await liveModeToggle.isChecked()
|
||
|
|
|
||
|
|
// Toggle and watch for toast
|
||
|
|
await liveModeToggle.click()
|
||
|
|
|
||
|
|
// Wait for toast to appear
|
||
|
|
const expectedMessage = initialState ? 'Live mode disabled' : 'Live mode enabled'
|
||
|
|
const toast = page.locator('.toast, [role="alert"]').filter({ hasText: expectedMessage })
|
||
|
|
await expect(toast).toBeVisible({ timeout: 3000 })
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
test.describe('Realtime Controller', () => {
|
||
|
|
test('should initialize realtime controller when enabled', async ({ page }) => {
|
||
|
|
const realtimeControllerExists = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
return controller !== undefined
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(realtimeControllerExists).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should have access to maps--maplibre controller', async ({ page }) => {
|
||
|
|
const hasMapsController = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const realtimeController = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
const mapsController = realtimeController?.mapsV2Controller
|
||
|
|
return mapsController !== undefined && mapsController.map !== undefined
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(hasMapsController).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should initialize ActionCable channels', async ({ page }) => {
|
||
|
|
// Wait for channels to be set up
|
||
|
|
await page.waitForTimeout(2000)
|
||
|
|
|
||
|
|
const channelsInitialized = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
return controller?.channels !== undefined
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(channelsInitialized).toBe(true)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
test.describe('Connection Indicator', () => {
|
||
|
|
test('should have connection indicator element in DOM', async ({ page }) => {
|
||
|
|
// Connection indicator exists but is hidden by default
|
||
|
|
const indicator = page.locator('.connection-indicator')
|
||
|
|
|
||
|
|
// Should exist in DOM
|
||
|
|
await expect(indicator).toHaveCount(1)
|
||
|
|
|
||
|
|
// Should be hidden (not active) without real ActionCable connection
|
||
|
|
const isActive = await indicator.evaluate(el => el.classList.contains('active'))
|
||
|
|
expect(isActive).toBe(false)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should have connection status classes', async ({ page }) => {
|
||
|
|
const indicator = page.locator('.connection-indicator')
|
||
|
|
|
||
|
|
// Should have disconnected class by default (before connection)
|
||
|
|
const hasDisconnectedClass = await indicator.evaluate(el =>
|
||
|
|
el.classList.contains('disconnected')
|
||
|
|
)
|
||
|
|
|
||
|
|
expect(hasDisconnectedClass).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test.skip('should show connection indicator when ActionCable connects', async ({ page }) => {
|
||
|
|
// This test requires actual ActionCable connection
|
||
|
|
// The indicator becomes visible (.active class added) only when channels connect
|
||
|
|
|
||
|
|
// Wait for connection
|
||
|
|
await page.waitForTimeout(3000)
|
||
|
|
|
||
|
|
const indicator = page.locator('.connection-indicator')
|
||
|
|
|
||
|
|
// Should be visible with active class
|
||
|
|
await expect(indicator).toHaveClass(/active/)
|
||
|
|
await expect(indicator).toBeVisible()
|
||
|
|
})
|
||
|
|
|
||
|
|
test.skip('should show appropriate connection text when active', async ({ page }) => {
|
||
|
|
// This test requires actual ActionCable connection
|
||
|
|
// The indicator text shows via CSS ::before pseudo-element
|
||
|
|
|
||
|
|
// Wait for connection
|
||
|
|
await page.waitForTimeout(3000)
|
||
|
|
|
||
|
|
const indicatorText = page.locator('.connection-indicator .indicator-text')
|
||
|
|
|
||
|
|
// Should show either "Connected" or "Connecting..."
|
||
|
|
const text = await indicatorText.evaluate(el => {
|
||
|
|
return window.getComputedStyle(el, '::before').content.replace(/['"]/g, '')
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(['Connected', 'Connecting...']).toContain(text)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
test.describe('Point Handling', () => {
|
||
|
|
test('should have handleNewPoint method', async ({ page }) => {
|
||
|
|
const hasMethod = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
return typeof controller?.handleNewPoint === 'function'
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(hasMethod).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should have zoomToPoint method', async ({ page }) => {
|
||
|
|
const hasMethod = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
return typeof controller?.zoomToPoint === 'function'
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(hasMethod).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test.skip('should add new point to map when received', async ({ page }) => {
|
||
|
|
// This test requires actual ActionCable broadcast
|
||
|
|
// Skipped as it needs backend point creation
|
||
|
|
|
||
|
|
// Get initial point count
|
||
|
|
const initialCount = 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 pointsLayer = controller?.layerManager?.getLayer('points')
|
||
|
|
return pointsLayer?.data?.features?.length || 0
|
||
|
|
})
|
||
|
|
|
||
|
|
// Simulate point broadcast (would need real backend)
|
||
|
|
// const newPoint = [52.5200, 13.4050, 85, 10, '2025-01-01T12:00:00Z', 5, 999, 'Germany']
|
||
|
|
|
||
|
|
// Wait for point to be added
|
||
|
|
// await page.waitForTimeout(1000)
|
||
|
|
|
||
|
|
// Verify point was added
|
||
|
|
// const newCount = await page.evaluate(() => { ... })
|
||
|
|
// expect(newCount).toBe(initialCount + 1)
|
||
|
|
})
|
||
|
|
|
||
|
|
test.skip('should zoom to new point location', async ({ page }) => {
|
||
|
|
// This test requires actual ActionCable broadcast
|
||
|
|
// Skipped as it needs backend point creation
|
||
|
|
|
||
|
|
// Get initial map center
|
||
|
|
// Broadcast new point at specific location
|
||
|
|
// Verify map center changed to new point location
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
test.describe('Live Mode State Persistence', () => {
|
||
|
|
test('should maintain live mode state after toggling', async ({ page }) => {
|
||
|
|
// Open settings
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
await page.locator('button[data-tab="settings"]').click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
|
||
|
|
|
||
|
|
// Enable live mode
|
||
|
|
if (!await liveModeToggle.isChecked()) {
|
||
|
|
await liveModeToggle.click()
|
||
|
|
await page.waitForTimeout(500)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify it's enabled
|
||
|
|
expect(await liveModeToggle.isChecked()).toBe(true)
|
||
|
|
|
||
|
|
// Close and reopen settings
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
await page.locator('button[data-tab="settings"]').click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
// Should still be enabled
|
||
|
|
expect(await liveModeToggle.isChecked()).toBe(true)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
test.describe('Error Handling', () => {
|
||
|
|
test('should handle missing maps controller gracefully', async ({ page }) => {
|
||
|
|
// This is tested by the controller's defensive checks
|
||
|
|
const hasDefensiveChecks = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
|
||
|
|
// The controller should have the mapsV2Controller getter
|
||
|
|
return typeof controller?.mapsV2Controller !== 'undefined'
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(hasDefensiveChecks).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should handle missing points layer gracefully', async ({ page }) => {
|
||
|
|
// Console errors should not crash the app
|
||
|
|
let consoleErrors = []
|
||
|
|
page.on('console', msg => {
|
||
|
|
if (msg.type() === 'error') {
|
||
|
|
consoleErrors.push(msg.text())
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// Wait for initialization
|
||
|
|
await page.waitForTimeout(2000)
|
||
|
|
|
||
|
|
// Should not have critical errors
|
||
|
|
const hasCriticalErrors = consoleErrors.some(err =>
|
||
|
|
err.includes('TypeError') || err.includes('Cannot read')
|
||
|
|
)
|
||
|
|
|
||
|
|
expect(hasCriticalErrors).toBe(false)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
test.describe('Recent Point Display', () => {
|
||
|
|
test('should have recent point layer initialized', async ({ page }) => {
|
||
|
|
const hasRecentPointLayer = 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 recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||
|
|
return recentPointLayer !== undefined
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(hasRecentPointLayer).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('recent point layer should be hidden by default', async ({ page }) => {
|
||
|
|
const isHidden = 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 recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||
|
|
return recentPointLayer?.visible === false
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(isHidden).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('recent point layer can be shown programmatically', async ({ page }) => {
|
||
|
|
// This tests the core functionality: the layer can be made visible
|
||
|
|
// The toggle integration will work once assets are recompiled
|
||
|
|
|
||
|
|
const result = 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 recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||
|
|
|
||
|
|
if (!recentPointLayer) {
|
||
|
|
return { success: false, reason: 'layer not found' }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Test that show() works
|
||
|
|
recentPointLayer.show()
|
||
|
|
const isVisible = recentPointLayer.visible === true
|
||
|
|
|
||
|
|
// Clean up
|
||
|
|
recentPointLayer.hide()
|
||
|
|
|
||
|
|
return { success: isVisible, visible: isVisible }
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(result.success).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('recent point layer can be hidden programmatically', async ({ page }) => {
|
||
|
|
// This tests the core functionality: the layer can be hidden
|
||
|
|
const result = 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 recentPointLayer = controller?.layerManager?.getLayer('recentPoint')
|
||
|
|
|
||
|
|
if (!recentPointLayer) {
|
||
|
|
return { success: false, reason: 'layer not found' }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Show first, then hide to test the hide functionality
|
||
|
|
recentPointLayer.show()
|
||
|
|
recentPointLayer.hide()
|
||
|
|
const isHidden = recentPointLayer.visible === false
|
||
|
|
|
||
|
|
return { success: isHidden, hidden: isHidden }
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(result.success).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should have updateRecentPoint method', async ({ page }) => {
|
||
|
|
const hasMethod = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
return typeof controller?.updateRecentPoint === 'function'
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(hasMethod).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('should have updateRecentPointLayerVisibility method', async ({ page }) => {
|
||
|
|
const hasMethod = await page.evaluate(() => {
|
||
|
|
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
|
||
|
|
const app = window.Stimulus || window.Application
|
||
|
|
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
|
||
|
|
return typeof controller?.updateRecentPointLayerVisibility === 'function'
|
||
|
|
})
|
||
|
|
|
||
|
|
expect(hasMethod).toBe(true)
|
||
|
|
})
|
||
|
|
|
||
|
|
test.skip('should display recent point when new point is broadcast in live mode', async ({ page }) => {
|
||
|
|
// This test requires actual ActionCable broadcast
|
||
|
|
// Skipped as it needs backend point creation
|
||
|
|
|
||
|
|
// Open settings and enable live mode
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
await page.locator('button[data-tab="settings"]').click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
|
||
|
|
if (!await liveModeToggle.isChecked()) {
|
||
|
|
await liveModeToggle.click()
|
||
|
|
await page.waitForTimeout(500)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close settings
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
// Disable points layer to test that recent point is still visible
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
await page.locator('button[data-tab="layers"]').click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
const pointsToggle = page.locator('[data-action="change->maps--maplibre#togglePoints"]')
|
||
|
|
if (await pointsToggle.isChecked()) {
|
||
|
|
await pointsToggle.click()
|
||
|
|
await page.waitForTimeout(500)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close settings
|
||
|
|
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
|
||
|
|
await page.waitForTimeout(300)
|
||
|
|
|
||
|
|
// Simulate new point broadcast (would need real backend)
|
||
|
|
// const newPoint = [52.5200, 13.4050, 85, 10, '2025-01-01T12:00:00Z', 5, 999, 'Germany']
|
||
|
|
|
||
|
|
// Wait for point to be displayed
|
||
|
|
// await page.waitForTimeout(1000)
|
||
|
|
|
||
|
|
// Verify recent point is visible on map
|
||
|
|
// const hasRecentPoint = await page.evaluate(() => { ... })
|
||
|
|
// expect(hasRecentPoint).toBe(true)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|