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