Move v2 maps to maplibre namespace

This commit is contained in:
Eugene Burmakin 2025-12-01 21:56:06 +01:00
parent 39307e1bb3
commit 3662d4f4b3
85 changed files with 376 additions and 360 deletions

View file

@ -1,11 +1,12 @@
class MapsV2Controller < ApplicationController
before_action :authenticate_user!
layout 'map'
module Maps
class MaplibreController < ApplicationController
before_action :authenticate_user!
layout 'map'
def index
@start_at = parsed_start_at
@end_at = parsed_end_at
end
def index
@start_at = parsed_start_at
@end_at = parsed_end_at
end
private
@ -28,4 +29,5 @@ class MapsV2Controller < ApplicationController
def parsed_end_at
Time.zone.at(end_at)
end
end
end

View file

@ -0,0 +1,5 @@
class MapsController < ApplicationController
def index
redirect_to maps_maplibre_path
end
end

View file

@ -1,6 +1,6 @@
# Dawarich JavaScript Architecture
This document provides a comprehensive guide to the JavaScript architecture used in the Dawarich application, with a focus on the Maps V2 implementation.
This document provides a comprehensive guide to the JavaScript architecture used in the Dawarich application, with a focus on the Maps (MapLibre) implementation.
## Table of Contents
@ -9,13 +9,13 @@ This document provides a comprehensive guide to the JavaScript architecture used
- [Architecture Patterns](#architecture-patterns)
- [Directory Structure](#directory-structure)
- [Core Concepts](#core-concepts)
- [Maps V2 Architecture](#maps-v2-architecture)
- [Maps (MapLibre) Architecture](#maps-v2-architecture)
- [Creating New Features](#creating-new-features)
- [Best Practices](#best-practices)
## Overview
Dawarich uses a modern JavaScript architecture built on **Hotwire (Turbo + Stimulus)** for page interactions and **MapLibre GL JS** for map rendering. The Maps V2 implementation follows object-oriented principles with clear separation of concerns.
Dawarich uses a modern JavaScript architecture built on **Hotwire (Turbo + Stimulus)** for page interactions and **MapLibre GL JS** for map rendering. The Maps (MapLibre) implementation follows object-oriented principles with clear separation of concerns.
## Technology Stack
@ -61,7 +61,7 @@ export default class extends Controller {
**Purpose:** Encapsulate business logic and API communication
**Location:** `app/javascript/maps_v2/services/`
**Location:** `app/javascript/maps_maplibre/services/`
**Pattern:**
```javascript
@ -89,7 +89,7 @@ export class ApiClient {
**Purpose:** Manage map visualization layers
**Location:** `app/javascript/maps_v2/layers/`
**Location:** `app/javascript/maps_maplibre/layers/`
**Pattern:**
```javascript
@ -129,7 +129,7 @@ export class CustomLayer extends BaseLayer {
**Purpose:** Provide reusable helper functions
**Location:** `app/javascript/maps_v2/utils/`
**Location:** `app/javascript/maps_maplibre/utils/`
**Pattern:**
```javascript
@ -147,7 +147,7 @@ export const utilityInstance = new UtilityClass()
**Purpose:** Reusable UI components
**Location:** `app/javascript/maps_v2/components/`
**Location:** `app/javascript/maps_maplibre/components/`
**Pattern:**
```javascript
@ -164,15 +164,15 @@ export class PopupFactory {
app/javascript/
├── application.js # Entry point
├── controllers/ # Stimulus controllers
│ ├── maps_v2_controller.js # Main map controller
│ ├── maps_v2/ # Controller modules
│ ├── maps/maplibre_controller.js # Main map controller
│ ├── maps_maplibre/ # Controller modules
│ │ ├── layer_manager.js # Layer lifecycle management
│ │ ├── data_loader.js # API data fetching
│ │ ├── event_handlers.js # Map event handling
│ │ ├── filter_manager.js # Data filtering
│ │ └── date_manager.js # Date range management
│ └── ... # Other controllers
├── maps_v2/ # Maps V2 implementation
├── maps_maplibre/ # Maps (MapLibre) implementation
│ ├── layers/ # Map layer classes
│ │ ├── base_layer.js # Abstract base class
│ │ ├── points_layer.js # Point markers
@ -206,7 +206,7 @@ app/javascript/
### Manager Pattern
The Maps V2 controller delegates responsibilities to specialized managers:
The Maps (MapLibre) controller delegates responsibilities to specialized managers:
1. **LayerManager** - Layer lifecycle (add/remove/toggle/update)
2. **DataLoader** - API data fetching and transformation
@ -277,7 +277,7 @@ map.on('click', 'layer-id', (e) => {
})
```
## Maps V2 Architecture
## Maps (MapLibre) Architecture
### Layer Hierarchy
@ -393,7 +393,7 @@ All data is transformed to GeoJSON before rendering:
### Adding a New Layer
1. **Create layer class** in `app/javascript/maps_v2/layers/`:
1. **Create layer class** in `app/javascript/maps_maplibre/layers/`:
```javascript
import { BaseLayer } from './base_layer'
@ -422,10 +422,10 @@ export class NewLayer extends BaseLayer {
}
```
2. **Register in LayerManager** (`controllers/maps_v2/layer_manager.js`):
2. **Register in LayerManager** (`controllers/maps_maplibre/layer_manager.js`):
```javascript
import { NewLayer } from 'maps_v2/layers/new_layer'
import { NewLayer } from 'maps_maplibre/layers/new_layer'
// In addAllLayers method
_addNewLayer(dataGeoJSON) {
@ -528,7 +528,7 @@ export const newManager = new NewManager()
2. **Import and use:**
```javascript
import { NewUtility } from 'maps_v2/utils/new_utility'
import { NewUtility } from 'maps_maplibre/utils/new_utility'
const result = NewUtility.calculate(input)
```
@ -705,19 +705,19 @@ When updating features, follow this pattern:
### Complete Layer Implementation
See `app/javascript/maps_v2/layers/heatmap_layer.js` for a simple example.
See `app/javascript/maps_maplibre/layers/heatmap_layer.js` for a simple example.
### Complete Utility Implementation
See `app/javascript/maps_v2/utils/settings_manager.js` for state management.
See `app/javascript/maps_maplibre/utils/settings_manager.js` for state management.
### Complete Service Implementation
See `app/javascript/maps_v2/services/api_client.js` for API communication.
See `app/javascript/maps_maplibre/services/api_client.js` for API communication.
### Complete Controller Implementation
See `app/javascript/controllers/maps_v2_controller.js` for orchestration.
See `app/javascript/controllers/maps/maplibre_controller.js` for orchestration.
---

View file

@ -1,5 +1,5 @@
import { Controller } from '@hotwired/stimulus'
import { createCircle, calculateDistance } from 'maps_v2/utils/geometry'
import { createCircle, calculateDistance } from 'maps_maplibre/utils/geometry'
/**
* Area drawer controller

View file

@ -1,5 +1,5 @@
import { Controller } from '@hotwired/stimulus'
import { createRectangle } from 'maps_v2/utils/geometry'
import { createRectangle } from 'maps_maplibre/utils/geometry'
/**
* Area selector controller

View file

@ -1,8 +1,8 @@
import { SelectionLayer } from 'maps_v2/layers/selection_layer'
import { SelectedPointsLayer } from 'maps_v2/layers/selected_points_layer'
import { pointsToGeoJSON } from 'maps_v2/utils/geojson_transformers'
import { VisitCard } from 'maps_v2/components/visit_card'
import { Toast } from 'maps_v2/components/toast'
import { SelectionLayer } from 'maps_maplibre/layers/selection_layer'
import { SelectedPointsLayer } from 'maps_maplibre/layers/selected_points_layer'
import { pointsToGeoJSON } from 'maps_maplibre/utils/geojson_transformers'
import { VisitCard } from 'maps_maplibre/components/visit_card'
import { Toast } from 'maps_maplibre/components/toast'
/**
* Manages area selection and bulk operations for Maps V2

View file

@ -1,7 +1,7 @@
import { pointsToGeoJSON } from 'maps_v2/utils/geojson_transformers'
import { RoutesLayer } from 'maps_v2/layers/routes_layer'
import { createCircle } from 'maps_v2/utils/geometry'
import { performanceMonitor } from 'maps_v2/utils/performance_monitor'
import { pointsToGeoJSON } from 'maps_maplibre/utils/geojson_transformers'
import { RoutesLayer } from 'maps_maplibre/layers/routes_layer'
import { createCircle } from 'maps_maplibre/utils/geometry'
import { performanceMonitor } from 'maps_maplibre/utils/performance_monitor'
/**
* Handles loading and transforming data from API

View file

@ -1,7 +1,7 @@
import maplibregl from 'maplibre-gl'
import { PopupFactory } from 'maps_v2/components/popup_factory'
import { VisitPopupFactory } from 'maps_v2/components/visit_popup'
import { PhotoPopupFactory } from 'maps_v2/components/photo_popup'
import { PopupFactory } from 'maps_maplibre/components/popup_factory'
import { VisitPopupFactory } from 'maps_maplibre/components/visit_popup'
import { PhotoPopupFactory } from 'maps_maplibre/components/photo_popup'
/**
* Handles map interaction events (clicks, popups)

View file

@ -1,15 +1,15 @@
import { PointsLayer } from 'maps_v2/layers/points_layer'
import { RoutesLayer } from 'maps_v2/layers/routes_layer'
import { HeatmapLayer } from 'maps_v2/layers/heatmap_layer'
import { VisitsLayer } from 'maps_v2/layers/visits_layer'
import { PhotosLayer } from 'maps_v2/layers/photos_layer'
import { AreasLayer } from 'maps_v2/layers/areas_layer'
import { TracksLayer } from 'maps_v2/layers/tracks_layer'
import { PlacesLayer } from 'maps_v2/layers/places_layer'
import { FogLayer } from 'maps_v2/layers/fog_layer'
import { FamilyLayer } from 'maps_v2/layers/family_layer'
import { lazyLoader } from 'maps_v2/utils/lazy_loader'
import { performanceMonitor } from 'maps_v2/utils/performance_monitor'
import { PointsLayer } from 'maps_maplibre/layers/points_layer'
import { RoutesLayer } from 'maps_maplibre/layers/routes_layer'
import { HeatmapLayer } from 'maps_maplibre/layers/heatmap_layer'
import { VisitsLayer } from 'maps_maplibre/layers/visits_layer'
import { PhotosLayer } from 'maps_maplibre/layers/photos_layer'
import { AreasLayer } from 'maps_maplibre/layers/areas_layer'
import { TracksLayer } from 'maps_maplibre/layers/tracks_layer'
import { PlacesLayer } from 'maps_maplibre/layers/places_layer'
import { FogLayer } from 'maps_maplibre/layers/fog_layer'
import { FamilyLayer } from 'maps_maplibre/layers/family_layer'
import { lazyLoader } from 'maps_maplibre/utils/lazy_loader'
import { performanceMonitor } from 'maps_maplibre/utils/performance_monitor'
/**
* Manages all map layers lifecycle and visibility

View file

@ -1,6 +1,6 @@
import maplibregl from 'maplibre-gl'
import { Toast } from 'maps_v2/components/toast'
import { performanceMonitor } from 'maps_v2/utils/performance_monitor'
import { Toast } from 'maps_maplibre/components/toast'
import { performanceMonitor } from 'maps_maplibre/utils/performance_monitor'
/**
* Manages data loading and layer setup for the map

View file

@ -1,5 +1,5 @@
import maplibregl from 'maplibre-gl'
import { getMapStyle } from 'maps_v2/utils/style_manager'
import { getMapStyle } from 'maps_maplibre/utils/style_manager'
/**
* Handles map initialization for Maps V2

View file

@ -1,5 +1,5 @@
import { SettingsManager } from 'maps_v2/utils/settings_manager'
import { Toast } from 'maps_v2/components/toast'
import { SettingsManager } from 'maps_maplibre/utils/settings_manager'
import { Toast } from 'maps_maplibre/components/toast'
/**
* Manages places-related operations for Maps V2

View file

@ -1,6 +1,6 @@
import { SettingsManager } from 'maps_v2/utils/settings_manager'
import { Toast } from 'maps_v2/components/toast'
import { lazyLoader } from 'maps_v2/utils/lazy_loader'
import { SettingsManager } from 'maps_maplibre/utils/settings_manager'
import { Toast } from 'maps_maplibre/components/toast'
import { lazyLoader } from 'maps_maplibre/utils/lazy_loader'
/**
* Manages routes-related operations for Maps V2
@ -176,7 +176,7 @@ export class RoutesManager {
const distanceThresholdMeters = this.settings.metersBetweenRoutes || 500
const timeThresholdMinutes = this.settings.minutesBetweenRoutes || 60
const { calculateSpeed, getSpeedColor } = await import('maps_v2/utils/speed_colors')
const { calculateSpeed, getSpeedColor } = await import('maps_maplibre/utils/speed_colors')
const routesGeoJSON = await this.generateRoutesWithSpeedColors(
points,
@ -199,7 +199,7 @@ export class RoutesManager {
* Generate routes with speed coloring
*/
async generateRoutesWithSpeedColors(points, options, calculateSpeed, getSpeedColor) {
const { RoutesLayer } = await import('maps_v2/layers/routes_layer')
const { RoutesLayer } = await import('maps_maplibre/layers/routes_layer')
const useSpeedColors = this.settings.speedColoredRoutesEnabled || false
const speedColorScale = this.settings.speedColorScale || '0:#00ff00|15:#00ffff|30:#ff00ff|50:#ffff00|100:#ff3300'

View file

@ -1,6 +1,6 @@
import { SettingsManager } from 'maps_v2/utils/settings_manager'
import { getMapStyle } from 'maps_v2/utils/style_manager'
import { Toast } from 'maps_v2/components/toast'
import { SettingsManager } from 'maps_maplibre/utils/settings_manager'
import { getMapStyle } from 'maps_maplibre/utils/style_manager'
import { Toast } from 'maps_maplibre/components/toast'
/**
* Handles all settings-related operations for Maps V2

View file

@ -1,5 +1,5 @@
import { SettingsManager } from 'maps_v2/utils/settings_manager'
import { Toast } from 'maps_v2/components/toast'
import { SettingsManager } from 'maps_maplibre/utils/settings_manager'
import { Toast } from 'maps_maplibre/components/toast'
/**
* Manages visits-related operations for Maps V2

View file

@ -1,22 +1,22 @@
import { Controller } from '@hotwired/stimulus'
import { ApiClient } from 'maps_v2/services/api_client'
import { SettingsManager } from 'maps_v2/utils/settings_manager'
import { SearchManager } from 'maps_v2/utils/search_manager'
import { Toast } from 'maps_v2/components/toast'
import { performanceMonitor } from 'maps_v2/utils/performance_monitor'
import { CleanupHelper } from 'maps_v2/utils/cleanup_helper'
import { MapInitializer } from './maps_v2/map_initializer'
import { MapDataManager } from './maps_v2/map_data_manager'
import { LayerManager } from './maps_v2/layer_manager'
import { DataLoader } from './maps_v2/data_loader'
import { EventHandlers } from './maps_v2/event_handlers'
import { FilterManager } from './maps_v2/filter_manager'
import { DateManager } from './maps_v2/date_manager'
import { SettingsController } from './maps_v2/settings_manager'
import { AreaSelectionManager } from './maps_v2/area_selection_manager'
import { VisitsManager } from './maps_v2/visits_manager'
import { PlacesManager } from './maps_v2/places_manager'
import { RoutesManager } from './maps_v2/routes_manager'
import { ApiClient } from 'maps_maplibre/services/api_client'
import { SettingsManager } from 'maps_maplibre/utils/settings_manager'
import { SearchManager } from 'maps_maplibre/utils/search_manager'
import { Toast } from 'maps_maplibre/components/toast'
import { performanceMonitor } from 'maps_maplibre/utils/performance_monitor'
import { CleanupHelper } from 'maps_maplibre/utils/cleanup_helper'
import { MapInitializer } from './maplibre/map_initializer'
import { MapDataManager } from './maplibre/map_data_manager'
import { LayerManager } from './maplibre/layer_manager'
import { DataLoader } from './maplibre/data_loader'
import { EventHandlers } from './maplibre/event_handlers'
import { FilterManager } from './maplibre/filter_manager'
import { DateManager } from './maplibre/date_manager'
import { SettingsController } from './maplibre/settings_manager'
import { AreaSelectionManager } from './maplibre/area_selection_manager'
import { VisitsManager } from './maplibre/visits_manager'
import { PlacesManager } from './maplibre/places_manager'
import { RoutesManager } from './maplibre/routes_manager'
/**
* Main map controller for Maps V2

View file

@ -1,7 +1,7 @@
import { Controller } from '@hotwired/stimulus'
import { createMapChannel } from 'maps_v2/channels/map_channel'
import { WebSocketManager } from 'maps_v2/utils/websocket_manager'
import { Toast } from 'maps_v2/components/toast'
import { createMapChannel } from 'maps_maplibre/channels/map_channel'
import { WebSocketManager } from 'maps_maplibre/utils/websocket_manager'
import { Toast } from 'maps_maplibre/components/toast'
/**
* Real-time controller

View file

@ -1,5 +1,5 @@
import { Controller } from '@hotwired/stimulus'
import { Toast } from 'maps_v2/components/toast'
import { Toast } from 'maps_maplibre/components/toast'
/**
* Controller for visit creation modal in Maps V2

View file

@ -1,7 +1,7 @@
import consumer from '../../channels/consumer'
/**
* Create map channel subscription for maps_v2
* Create map channel subscription for maps_maplibre
* Wraps the existing FamilyLocationsChannel and other channels for real-time updates
* @param {Object} options - { received, connected, disconnected, enableLiveMode }
* @returns {Object} Subscriptions object with multiple channels

View file

@ -33,7 +33,7 @@ const LAYER_NAME_MAP = {
// Mapping between frontend settings and backend API keys
const BACKEND_SETTINGS_MAP = {
mapStyle: 'maps_v2_style',
mapStyle: 'maps_maplibre_style',
enabledMapLayers: 'enabled_map_layers'
}

View file

@ -31,7 +31,7 @@ async function loadStyleFile(styleName) {
}
// Fetch the style file from the public assets
const response = await fetch(`/maps_v2/styles/${styleName}.json`)
const response = await fetch(`/maps_maplibre/styles/${styleName}.json`)
if (!response.ok) {
throw new Error(`Failed to load style: ${styleName} (${response.status})`)
}

View file

@ -1,4 +1,4 @@
<div class="map-control-panel" data-maps-v2-target="settingsPanel" data-controller="map-panel">
<div class="map-control-panel" data-maps--maplibre-target="settingsPanel" data-controller="map-panel">
<!-- Vertical Icon Tabs (Left Side) -->
<div class="panel-tabs">
<button class="tab-btn active"
@ -51,7 +51,7 @@
<div class="panel-header">
<h3 class="panel-title" data-map-panel-target="title">Layers</h3>
<button class="btn btn-ghost btn-sm btn-circle"
data-action="click->maps-v2#toggleSettings"
data-action="click->maps--maplibre#toggleSettings"
title="Close panel">
<%= icon 'search' %>
</button>
@ -69,11 +69,11 @@
<input type="text"
placeholder="Enter name of a place"
class="input input-bordered w-full"
data-maps-v2-target="searchInput"
data-maps--maplibre-target="searchInput"
autocomplete="off" />
<!-- Search Results -->
<div class="absolute z-50 w-full mt-1 bg-base-100 rounded-lg shadow-lg border border-base-300 hidden max-h-full overflow-y-auto"
data-maps-v2-target="searchResults">
data-maps--maplibre-target="searchResults">
<!-- Results will be populated by SearchManager -->
</div>
</div>
@ -91,8 +91,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="pointsToggle"
data-action="change->maps-v2#togglePoints" />
data-maps--maplibre-target="pointsToggle"
data-action="change->maps--maplibre#togglePoints" />
<span class="label-text font-medium">Points</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show individual location points</p>
@ -105,36 +105,36 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="routesToggle"
data-action="change->maps-v2#toggleRoutes" />
data-maps--maplibre-target="routesToggle"
data-action="change->maps--maplibre#toggleRoutes" />
<span class="label-text font-medium">Routes</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show connected route lines</p>
</div>
<!-- Speed-Colored Routes Options (conditionally shown) -->
<div class="ml-14 space-y-3" data-maps-v2-target="routesOptions" style="display: none;">
<div class="ml-14 space-y-3" data-maps--maplibre-target="routesOptions" style="display: none;">
<div class="form-control">
<label class="label cursor-pointer py-2">
<span class="label-text text-sm">Color by speed</span>
<input type="checkbox"
class="toggle toggle-sm toggle-primary"
data-maps-v2-target="speedColoredToggle"
data-action="change->maps-v2#toggleSpeedColoredRoutes" />
data-maps--maplibre-target="speedColoredToggle"
data-action="change->maps--maplibre#toggleSpeedColoredRoutes" />
</label>
</div>
<!-- Speed Color Scale Editor (shown when speed colors enabled) -->
<div class="hidden" data-maps-v2-target="speedColorScaleContainer">
<div class="hidden" data-maps--maplibre-target="speedColorScaleContainer">
<button type="button"
class="btn btn-sm btn-outline w-full"
data-action="click->maps-v2#openSpeedColorEditor">
data-action="click->maps--maplibre#openSpeedColorEditor">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
</svg>
Edit Color Gradient
</button>
<input type="hidden" data-maps-v2-target="speedColorScaleInput" value="" />
<input type="hidden" data-maps--maplibre-target="speedColorScaleInput" value="" />
</div>
</div>
@ -145,8 +145,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="heatmapToggle"
data-action="change->maps-v2#toggleHeatmap" />
data-maps--maplibre-target="heatmapToggle"
data-action="change->maps--maplibre#toggleHeatmap" />
<span class="label-text font-medium">Heatmap</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show density heatmap</p>
@ -159,23 +159,23 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="visitsToggle"
data-action="change->maps-v2#toggleVisits" />
data-maps--maplibre-target="visitsToggle"
data-action="change->maps--maplibre#toggleVisits" />
<span class="label-text font-medium">Visits</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show detected area visits</p>
</div>
<!-- Visits Search (conditionally shown) -->
<div class="ml-14 space-y-2" data-maps-v2-target="visitsSearch" style="display: none;">
<div class="ml-14 space-y-2" data-maps--maplibre-target="visitsSearch" style="display: none;">
<input type="text"
id="visits-search"
placeholder="Filter by name..."
class="input input-sm input-bordered w-full"
data-action="input->maps-v2#searchVisits" />
data-action="input->maps--maplibre#searchVisits" />
<select class="select select-bordered w-full"
data-action="change->maps-v2#filterVisits">
data-action="change->maps--maplibre#filterVisits">
<option value="all">All Visits</option>
<option value="confirmed">Confirmed Only</option>
<option value="suggested">Suggested Only</option>
@ -189,21 +189,21 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="placesToggle"
data-action="change->maps-v2#togglePlaces" />
data-maps--maplibre-target="placesToggle"
data-action="change->maps--maplibre#togglePlaces" />
<span class="label-text font-medium">Places</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show your saved places</p>
</div>
<!-- Places Tags (conditionally shown) -->
<div class="ml-14 space-y-2" data-maps-v2-target="placesFilters" style="display: none;">
<div class="ml-14 space-y-2" data-maps--maplibre-target="placesFilters" style="display: none;">
<div class="form-control">
<label class="label cursor-pointer justify-start gap-2">
<input type="checkbox"
class="toggle toggle-sm"
data-maps-v2-target="enableAllPlaceTagsToggle"
data-action="change->maps-v2#toggleAllPlaceTags">
data-maps--maplibre-target="enableAllPlaceTagsToggle"
data-action="change->maps--maplibre#toggleAllPlaceTags">
<span class="label-text text-sm">Enable All Tags</span>
</label>
</div>
@ -218,7 +218,7 @@
name="place_tag_ids[]"
value="untagged"
class="checkbox checkbox-xs hidden peer"
data-action="change->maps-v2#filterPlacesByTags">
data-action="change->maps--maplibre#filterPlacesByTags">
<span class="badge badge-sm badge-outline transition-all peer-checked:scale-105"
style="border-color: #94a3b8; color: #94a3b8;"
data-checked-style="background-color: #94a3b8; color: white;">
@ -232,7 +232,7 @@
name="place_tag_ids[]"
value="<%= tag.id %>"
class="checkbox checkbox-xs hidden peer"
data-action="change->maps-v2#filterPlacesByTags">
data-action="change->maps--maplibre#filterPlacesByTags">
<span class="badge badge-sm badge-outline transition-all peer-checked:scale-105"
style="border-color: <%= tag.color %>; color: <%= tag.color %>;"
data-checked-style="background-color: <%= tag.color %>; color: white;">
@ -254,8 +254,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="photosToggle"
data-action="change->maps-v2#togglePhotos" />
data-maps--maplibre-target="photosToggle"
data-action="change->maps--maplibre#togglePhotos" />
<span class="label-text font-medium">Photos</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show geotagged photos</p>
@ -268,8 +268,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="areasToggle"
data-action="change->maps-v2#toggleAreas" />
data-maps--maplibre-target="areasToggle"
data-action="change->maps--maplibre#toggleAreas" />
<span class="label-text font-medium">Areas</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show defined areas</p>
@ -282,8 +282,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="tracksToggle"
data-action="change->maps-v2#toggleTracks" />
data-maps--maplibre-target="tracksToggle"
data-action="change->maps--maplibre#toggleTracks" />
<span class="label-text font-medium">Tracks</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show saved tracks</p>
@ -296,8 +296,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="fogToggle"
data-action="change->maps-v2#toggleFog" />
data-maps--maplibre-target="fogToggle"
data-action="change->maps--maplibre#toggleFog" />
<span class="label-text font-medium">Fog of War</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show explored areas</p>
@ -310,8 +310,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-maps-v2-target="scratchToggle"
data-action="change->maps-v2#toggleScratch" />
data-maps--maplibre-target="scratchToggle"
data-action="change->maps--maplibre#toggleScratch" />
<span class="label-text font-medium">Scratch Map</span>
</label>
<p class="text-sm text-base-content/60 ml-14">Show scratched countries</p>
@ -322,7 +322,7 @@
<!-- Settings Tab -->
<div class="tab-content" data-tab-content="settings" data-map-panel-target="tabContent">
<form data-action="submit->maps-v2#updateAdvancedSettings" class="space-y-4">
<form data-action="submit->maps--maplibre#updateAdvancedSettings" class="space-y-4">
<!-- Map Style -->
<div class="form-control w-full">
<label class="label">
@ -330,7 +330,7 @@
</label>
<select class="select select-bordered w-full"
name="mapStyle"
data-action="change->maps-v2#updateMapStyle">
data-action="change->maps--maplibre#updateMapStyle">
<option value="light" selected>Light</option>
<option value="dark">Dark</option>
<option value="white">White</option>
@ -354,8 +354,8 @@
step="10"
value="100"
class="range range-sm"
data-maps-v2-target="routeOpacityRange"
data-action="input->maps-v2#updateRouteOpacity" />
data-maps--maplibre-target="routeOpacityRange"
data-action="input->maps--maplibre#updateRouteOpacity" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>10%</span>
<span>50%</span>
@ -369,7 +369,7 @@
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Fog of War Radius</span>
<span class="label-text-alt" data-maps-v2-target="fogRadiusValue">1000m</span>
<span class="label-text-alt" data-maps--maplibre-target="fogRadiusValue">1000m</span>
</label>
<input type="range"
name="fogOfWarRadius"
@ -378,7 +378,7 @@
step="5"
value="1000"
class="range range-sm"
data-action="input->maps-v2#updateFogRadiusDisplay" />
data-action="input->maps--maplibre#updateFogRadiusDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>5m</span>
<span>1000m</span>
@ -390,7 +390,7 @@
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Fog of War Threshold</span>
<span class="label-text-alt" data-maps-v2-target="fogThresholdValue">1</span>
<span class="label-text-alt" data-maps--maplibre-target="fogThresholdValue">1</span>
</label>
<input type="range"
name="fogOfWarThreshold"
@ -399,7 +399,7 @@
step="1"
value="1"
class="range range-sm"
data-action="input->maps-v2#updateFogThresholdDisplay" />
data-action="input->maps--maplibre#updateFogThresholdDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>1</span>
<span>5</span>
@ -414,7 +414,7 @@
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Meters Between Routes</span>
<span class="label-text-alt" data-maps-v2-target="metersBetweenValue">500m</span>
<span class="label-text-alt" data-maps--maplibre-target="metersBetweenValue">500m</span>
</label>
<input type="range"
name="metersBetweenRoutes"
@ -423,7 +423,7 @@
step="100"
value="500"
class="range range-sm"
data-action="input->maps-v2#updateMetersBetweenDisplay" />
data-action="input->maps--maplibre#updateMetersBetweenDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>100m</span>
<span>2500m</span>
@ -435,7 +435,7 @@
<div class="form-control w-full">
<label class="label">
<span class="label-text font-medium">Minutes Between Routes</span>
<span class="label-text-alt" data-maps-v2-target="minutesBetweenValue">60min</span>
<span class="label-text-alt" data-maps--maplibre-target="minutesBetweenValue">60min</span>
</label>
<input type="range"
name="minutesBetweenRoutes"
@ -444,7 +444,7 @@
step="1"
value="60"
class="range range-sm"
data-action="input->maps-v2#updateMinutesBetweenDisplay" />
data-action="input->maps--maplibre#updateMinutesBetweenDisplay" />
<div class="w-full flex justify-between text-xs px-2 mt-1">
<span>1min</span>
<span>90min</span>
@ -499,8 +499,8 @@
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox"
class="toggle toggle-primary"
data-action="change->maps-v2-realtime#toggleLiveMode"
data-maps-v2-realtime-target="liveModeToggle" />
data-action="change->maps--maplibre-realtime#toggleLiveMode"
data-maps--maplibre-realtime-target="liveModeToggle" />
<span class="label-text font-medium">Live Mode</span>
</label>
<p class="text-sm text-base-content/60 mt-1">Show new points in real-time</p>
@ -517,7 +517,7 @@
<!-- Reset Settings -->
<button type="button"
class="btn btn-sm btn-outline btn-block"
data-action="click->maps-v2#resetSettings">
data-action="click->maps--maplibre#resetSettings">
<%= icon 'rotate-ccw' %>
Reset to Defaults
</button>
@ -532,7 +532,7 @@
<!-- Create a Visit Button -->
<button type="button"
class="btn btn-sm btn-outline"
data-action="click->maps-v2#startCreateVisit">
data-action="click->maps--maplibre#startCreateVisit">
<%= icon 'map-pin-check' %>
Create a Visit
</button>
@ -540,7 +540,7 @@
<!-- Create a Place Button -->
<button type="button"
class="btn btn-sm btn-outline"
data-action="click->maps-v2#startCreatePlace">
data-action="click->maps--maplibre#startCreatePlace">
<%= icon 'map-pin-plus' %>
Create a Place
</button>
@ -548,8 +548,8 @@
<!-- Select Area Button -->
<button type="button"
class="btn btn-sm btn-outline"
data-maps-v2-target="selectAreaButton"
data-action="click->maps-v2#startSelectArea">
data-maps--maplibre-target="selectAreaButton"
data-action="click->maps--maplibre#startSelectArea">
<%= icon 'square-dashed-mouse-pointer' %>
Select Area
</button>
@ -557,29 +557,29 @@
<!-- Create Area Button -->
<button type="button"
class="btn btn-sm btn-outline"
data-action="click->maps-v2#startCreateArea">
data-action="click->maps--maplibre#startCreateArea">
<%= icon 'circle-plus' %>
Create an Area
</button>
</div>
<!-- Selection Actions (shown after area is selected) -->
<div class="hidden mt-4 space-y-2" data-maps-v2-target="selectionActions">
<div class="hidden mt-4 space-y-2" data-maps--maplibre-target="selectionActions">
<button type="button"
class="btn btn-sm btn-outline btn-error btn-block"
data-action="click->maps-v2#deleteSelectedPoints"
data-maps-v2-target="deletePointsButton">
data-action="click->maps--maplibre#deleteSelectedPoints"
data-maps--maplibre-target="deletePointsButton">
<%= icon 'trash-2' %>
<span data-maps-v2-target="deleteButtonText">Delete Selected Points</span>
<span data-maps--maplibre-target="deleteButtonText">Delete Selected Points</span>
</button>
<!-- Selected Visits Container -->
<div class="hidden mt-4 max-h-full overflow-y-auto" data-maps-v2-target="selectedVisitsContainer">
<div class="hidden mt-4 max-h-full overflow-y-auto" data-maps--maplibre-target="selectedVisitsContainer">
<!-- Visit cards will be dynamically inserted here -->
</div>
<!-- Bulk Actions for Visits -->
<div class="hidden" data-maps-v2-target="selectedVisitsBulkActions">
<div class="hidden" data-maps--maplibre-target="selectedVisitsBulkActions">
<!-- Bulk action buttons will be dynamically inserted here -->
</div>
</div>

View file

@ -2,16 +2,16 @@
<%= render 'shared/map/date_navigation_v2', start_at: @start_at, end_at: @end_at %>
<div id="maps-v2-container"
data-controller="maps-v2 area-drawer maps-v2-realtime"
data-maps-v2-api-key-value="<%= current_user.api_key %>"
data-maps-v2-start-date-value="<%= @start_at.to_s %>"
data-maps-v2-end-date-value="<%= @end_at.to_s %>"
data-maps-v2-realtime-enabled-value="true"
<div id="maps-maplibre-container"
data-controller="maps--maplibre area-drawer maps--maplibre-realtime"
data-maps--maplibre-api-key-value="<%= current_user.api_key %>"
data-maps--maplibre-start-date-value="<%= @start_at.to_s %>"
data-maps--maplibre-end-date-value="<%= @end_at.to_s %>"
data-maps--maplibre-realtime-enabled-value="true"
style="width: 100%; height: 100%; position: relative;">
<!-- Map container takes full width and height -->
<div data-maps-v2-target="container" class="maps-v2-container" style="width: 100%; height: 100%;"></div>
<div data-maps--maplibre-target="container" class="maps-maplibre-container" style="width: 100%; height: 100%;"></div>
<!-- Connection indicator -->
<div class="connection-indicator disconnected">
@ -21,7 +21,7 @@
<!-- Settings button (top-left corner) -->
<div class="absolute top-4 left-4 z-10">
<button data-action="click->maps-v2#toggleSettings"
<button data-action="click->maps--maplibre#toggleSettings"
class="btn btn-sm btn-primary"
title="Open map settings">
<%= icon 'square-pen' %>
@ -30,19 +30,19 @@
</div>
<!-- Loading overlay -->
<div data-maps-v2-target="loading" class="loading-overlay hidden">
<div data-maps--maplibre-target="loading" class="loading-overlay hidden">
<div class="loading-spinner"></div>
<div class="loading-text" data-maps-v2-target="loadingText">Loading points...</div>
<div class="loading-text" data-maps--maplibre-target="loadingText">Loading points...</div>
</div>
<!-- Settings panel -->
<%= render 'maps_v2/settings_panel' %>
<%= render 'maps/maplibre/settings_panel' %>
<!-- Visit creation modal -->
<%= render 'maps_v2/visit_creation_modal' %>
<%= render 'maps/maplibre/visit_creation_modal' %>
<!-- Area creation modal -->
<%= render 'maps_v2/area_creation_modal' %>
<%= render 'maps/maplibre/area_creation_modal' %>
<!-- Place creation modal (shared) -->
<%= render 'shared/place_creation_modal' %>

View file

@ -17,12 +17,12 @@
<div
data-map-controls-target="panel"
class="hidden lg:!block bg-base-100 rounded-lg shadow-lg p-4 mt-2 lg:mt-0">
<%= form_with url: maps_v2_path(import_id: params[:import_id]), method: :get do |f| %>
<%= form_with url: maps_maplibre_path(import_id: params[:import_id]), method: :get do |f| %>
<div class="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4 lg:items-end">
<div class="w-full lg:w-1/12">
<div class="flex flex-col space-y-2">
<span class="tooltip" data-tip="<%= human_date(start_at - 1.day) %>">
<%= link_to maps_v2_path(start_at: start_at - 1.day, end_at: end_at - 1.day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" do %>
<%= link_to maps_maplibre_path(start_at: start_at - 1.day, end_at: end_at - 1.day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" do %>
<%= icon 'chevron-left' %>
<% end %>
</span>
@ -37,7 +37,7 @@
<div class="w-full lg:w-1/12">
<div class="flex flex-col space-y-2">
<span class="tooltip" data-tip="<%= human_date(start_at + 1.day) %>">
<%= link_to maps_v2_path(start_at: start_at + 1.day, end_at: end_at + 1.day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" do %>
<%= link_to maps_maplibre_path(start_at: start_at + 1.day, end_at: end_at + 1.day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" do %>
<%= icon 'chevron-right' %>
<% end %>
</span>
@ -51,18 +51,18 @@
<div class="w-full lg:w-1/12">
<div class="flex flex-col space-y-2 text-center">
<%= link_to "Today",
maps_v2_path(start_at: Time.current.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]),
maps_maplibre_path(start_at: Time.current.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]),
class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" %>
</div>
</div>
<div class="w-full lg:w-2/12">
<div class="flex flex-col space-y-2 text-center">
<%= link_to "Last 7 days", maps_v2_path(start_at: 1.week.ago.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" %>
<%= link_to "Last 7 days", maps_maplibre_path(start_at: 1.week.ago.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" %>
</div>
</div>
<div class="w-full lg:w-2/12">
<div class="flex flex-col space-y-2 text-center">
<%= link_to "Last month", maps_v2_path(start_at: 1.month.ago.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" %>
<%= link_to "Last month", maps_maplibre_path(start_at: 1.month.ago.beginning_of_day, end_at: Time.current.end_of_day, import_id: params[:import_id]), class: "btn btn-sm border border-base-300 hover:btn-ghost w-full" %>
</div>
</div>
</div>

View file

@ -4,7 +4,7 @@
pin_all_from 'app/javascript/channels', under: 'channels'
pin_all_from 'app/javascript/maps', under: 'maps'
pin_all_from 'app/javascript/maps_v2', under: 'maps_v2'
pin_all_from 'app/javascript/maps_maplibre', under: 'maps_maplibre'
pin 'application', preload: true
pin '@rails/actioncable', to: 'actioncable.esm.js'

View file

@ -110,10 +110,19 @@ Rails.application.routes.draw do
resources :metrics, only: [:index]
# V1 (Leaflet) - legacy
get 'map', to: 'map#index'
# Maps V2
get '/maps_v2', to: 'maps_v2#index', as: :maps_v2
# Main maps entry - redirects to MapLibre
get '/maps', to: 'maps#index', as: :maps
# Maps namespace
namespace :maps do
get '/maplibre', to: 'maplibre#index', as: :maplibre
end
# Backward compatibility redirect
get '/maps_v2', to: redirect('/maps/maplibre')
namespace :api do
namespace :v1 do

View file

@ -7,7 +7,7 @@
* @param {Page} page - Playwright page object
*/
export async function navigateToMapsV2(page) {
await page.goto('/maps_v2');
await page.goto('/maps/maplibre');
}
/**
@ -44,18 +44,18 @@ export async function waitForMapLibre(page, timeout = 10000) {
// Wait for map instance to exist and style to be loaded
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]');
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-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
// Check if map exists and style is loaded (more reliable than loaded())
return controller?.map && controller.map.isStyleLoaded();
}, { timeout: 15000 });
// Wait for loading overlay to be hidden
await page.waitForFunction(() => {
const loading = document.querySelector('[data-maps-v2-target="loading"]');
const loading = document.querySelector('[data-maps--maplibre-target="loading"]');
return loading && loading.classList.contains('hidden');
}, { timeout: 15000 });
}
@ -67,14 +67,14 @@ export async function waitForMapLibre(page, timeout = 10000) {
*/
export async function hasMapInstance(page) {
return await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]');
const element = document.querySelector('[data-controller*="maps--maplibre"]');
if (!element) return false;
// Get Stimulus controller instance
const app = window.Stimulus || window.Application;
if (!app) return false;
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
return controller && controller.map !== undefined;
});
}
@ -86,13 +86,13 @@ export async function hasMapInstance(page) {
*/
export async function getMapZoom(page) {
return await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]');
const element = document.querySelector('[data-controller*="maps--maplibre"]');
if (!element) return null;
const app = window.Stimulus || window.Application;
if (!app) return null;
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
return controller?.map?.getZoom() || null;
});
}
@ -104,13 +104,13 @@ export async function getMapZoom(page) {
*/
export async function getMapCenter(page) {
return await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]');
const element = document.querySelector('[data-controller*="maps--maplibre"]');
if (!element) return null;
const app = window.Stimulus || window.Application;
if (!app) return null;
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
if (!controller?.map) return null;
const center = controller.map.getCenter();
@ -125,13 +125,13 @@ export async function getMapCenter(page) {
*/
export async function getPointsSourceData(page) {
return await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]');
const element = document.querySelector('[data-controller*="maps--maplibre"]');
if (!element) return { hasSource: false, featureCount: 0, features: [] };
const app = window.Stimulus || window.Application;
if (!app) return { hasSource: false, featureCount: 0, features: [] };
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
if (!controller?.map) return { hasSource: false, featureCount: 0, features: [] };
const source = controller.map.getSource('points-source');
@ -154,13 +154,13 @@ export async function getPointsSourceData(page) {
*/
export async function hasLayer(page, layerId) {
return await page.evaluate((id) => {
const element = document.querySelector('[data-controller*="maps-v2"]');
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-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
if (!controller?.map) return false;
return controller.map.getLayer(id) !== undefined;
@ -174,7 +174,7 @@ export async function hasLayer(page, layerId) {
* @param {number} y - Y coordinate
*/
export async function clickMapAt(page, x, y) {
const mapContainer = page.locator('[data-maps-v2-target="container"]');
const mapContainer = page.locator('[data-maps--maplibre-target="container"]');
await mapContainer.click({ position: { x, y } });
}
@ -184,7 +184,7 @@ export async function clickMapAt(page, x, y) {
*/
export async function waitForLoadingComplete(page) {
await page.waitForFunction(() => {
const loading = document.querySelector('[data-maps-v2-target="loading"]');
const loading = document.querySelector('[data-maps--maplibre-target="loading"]');
return loading && loading.classList.contains('hidden');
}, { timeout: 15000 });
}
@ -207,13 +207,13 @@ export async function hasPopup(page) {
*/
export async function getLayerVisibility(page, layerId) {
return await page.evaluate((id) => {
const element = document.querySelector('[data-controller*="maps-v2"]');
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-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
if (!controller?.map) return false;
const visibility = controller.map.getLayoutProperty(id, 'visibility');
@ -228,13 +228,13 @@ export async function getLayerVisibility(page, layerId) {
*/
export async function getRoutesSourceData(page) {
return await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]');
const element = document.querySelector('[data-controller*="maps--maplibre"]');
if (!element) return { hasSource: false, featureCount: 0, features: [] };
const app = window.Stimulus || window.Application;
if (!app) return { hasSource: false, featureCount: 0, features: [] };
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2');
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre');
if (!controller?.map) return { hasSource: false, featureCount: 0, features: [] };
const source = controller.map.getSource('routes-source');

View file

@ -5,7 +5,7 @@ import { waitForMapLibre, waitForLoadingComplete } from '../helpers/setup.js'
test.describe('Area Selection in Maps V2', () => {
test.beforeEach(async ({ page }) => {
// Navigate to Maps V2 with specific date range that has data
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForMapLibre(page)
await waitForLoadingComplete(page)
@ -15,17 +15,17 @@ test.describe('Area Selection in Maps V2', () => {
test('should enable area selection mode when clicking Select Area button', async ({ page }) => {
// Open settings panel and switch to Tools tab
await page.click('[data-action="click->maps-v2#toggleSettings"]')
await page.click('[data-action="click->maps--maplibre#toggleSettings"]')
await page.click('button[data-tab="tools"]')
// Click Select Area button
await page.click('[data-maps-v2-target="selectAreaButton"]')
await page.click('[data-maps--maplibre-target="selectAreaButton"]')
// Wait a moment for UI to update
await page.waitForTimeout(100)
// Verify the button changes to Cancel Selection
const selectButton = page.locator('[data-maps-v2-target="selectAreaButton"]')
const selectButton = page.locator('[data-maps--maplibre-target="selectAreaButton"]')
await expect(selectButton).toContainText('Cancel Selection', { timeout: 2000 })
// Verify cursor changes to crosshair (via canvas style)
@ -39,20 +39,20 @@ test.describe('Area Selection in Maps V2', () => {
test('should draw selection rectangle when dragging mouse', async ({ page }) => {
// Open settings panel and switch to Tools tab
await page.click('[data-action="click->maps-v2#toggleSettings"]')
await page.click('[data-action="click->maps--maplibre#toggleSettings"]')
await page.click('button[data-tab="tools"]')
// Click Select Area button
await page.click('[data-maps-v2-target="selectAreaButton"]')
await page.click('[data-maps--maplibre-target="selectAreaButton"]')
// Wait for selection mode to be enabled
await page.waitForTimeout(500)
// Check if selection layer has been added to map
const hasSelectionLayer = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller.areaSelectionManager?.selectionLayer !== undefined
})
expect(hasSelectionLayer).toBeTruthy()
@ -77,11 +77,11 @@ test.describe('Area Selection in Maps V2', () => {
test('should show selection actions when points are selected', async ({ page }) => {
// Open settings panel and switch to Tools tab
await page.click('[data-action="click->maps-v2#toggleSettings"]')
await page.click('[data-action="click->maps--maplibre#toggleSettings"]')
await page.click('button[data-tab="tools"]')
// Click Select Area button
await page.click('[data-maps-v2-target="selectAreaButton"]')
await page.click('[data-maps--maplibre-target="selectAreaButton"]')
// Get map canvas
const canvas = page.locator('canvas.maplibregl-canvas')
@ -103,30 +103,30 @@ test.describe('Area Selection in Maps V2', () => {
await page.waitForTimeout(1000)
// If points were found, verify UI updates
const selectionActions = page.locator('[data-maps-v2-target="selectionActions"]')
const selectionActions = page.locator('[data-maps--maplibre-target="selectionActions"]')
const isVisible = await selectionActions.isVisible().catch(() => false)
if (isVisible) {
// Verify delete button is visible and shows count
const deleteButton = page.locator('[data-maps-v2-target="deleteButtonText"]')
const deleteButton = page.locator('[data-maps--maplibre-target="deleteButtonText"]')
await expect(deleteButton).toBeVisible()
// Wait for button text to update with count
await expect(deleteButton).toContainText(/Delete \d+ Points?/, { timeout: 2000 })
// Verify the Select Area button has changed to Cancel Selection (at top of tools)
const selectButton = page.locator('[data-maps-v2-target="selectAreaButton"]')
const selectButton = page.locator('[data-maps--maplibre-target="selectAreaButton"]')
await expect(selectButton).toContainText('Cancel Selection')
}
})
test('should cancel area selection', async ({ page }) => {
// Open settings panel and switch to Tools tab
await page.click('[data-action="click->maps-v2#toggleSettings"]')
await page.click('[data-action="click->maps--maplibre#toggleSettings"]')
await page.click('button[data-tab="tools"]')
// Click Select Area button
await page.click('[data-maps-v2-target="selectAreaButton"]')
await page.click('[data-maps--maplibre-target="selectAreaButton"]')
// Wait for selection mode
await page.waitForTimeout(500)
@ -150,12 +150,12 @@ test.describe('Area Selection in Maps V2', () => {
await page.waitForTimeout(500)
// Check if selection actions are visible
const selectionActions = page.locator('[data-maps-v2-target="selectionActions"]')
const selectionActions = page.locator('[data-maps--maplibre-target="selectionActions"]')
const isVisible = await selectionActions.isVisible().catch(() => false)
if (isVisible) {
// Click Cancel button (the red one at the top that replaced Select Area)
const cancelButton = page.locator('[data-maps-v2-target="selectAreaButton"]')
const cancelButton = page.locator('[data-maps--maplibre-target="selectAreaButton"]')
await expect(cancelButton).toContainText('Cancel Selection')
await cancelButton.click()
@ -171,11 +171,11 @@ test.describe('Area Selection in Maps V2', () => {
test('should display delete confirmation dialog', async ({ page }) => {
// Open settings panel and switch to Tools tab
await page.click('[data-action="click->maps-v2#toggleSettings"]')
await page.click('[data-action="click->maps--maplibre#toggleSettings"]')
await page.click('button[data-tab="tools"]')
// Click Select Area button
await page.click('[data-maps-v2-target="selectAreaButton"]')
await page.click('[data-maps--maplibre-target="selectAreaButton"]')
// Get map canvas
const canvas = page.locator('canvas.maplibregl-canvas')
@ -196,7 +196,7 @@ test.describe('Area Selection in Maps V2', () => {
await page.waitForTimeout(500)
// Check if selection actions are visible
const selectionActions = page.locator('[data-maps-v2-target="selectionActions"]')
const selectionActions = page.locator('[data-maps--maplibre-target="selectionActions"]')
const isVisible = await selectionActions.isVisible().catch(() => false)
if (isVisible) {
@ -210,7 +210,7 @@ test.describe('Area Selection in Maps V2', () => {
})
// Click Delete button (text now includes count like "Delete 100 Points")
await page.locator('[data-maps-v2-target="deletePointsButton"]').click()
await page.locator('[data-maps--maplibre-target="deletePointsButton"]').click()
// Wait for dialog to be handled
await page.waitForTimeout(1000)
@ -228,11 +228,11 @@ test.describe('Area Selection in Maps V2', () => {
// by verifying the API call is made with the correct parameters when selecting an area
// Open settings panel and switch to Tools tab
await page.click('[data-action="click->maps-v2#toggleSettings"]')
await page.click('[data-action="click->maps--maplibre#toggleSettings"]')
await page.click('button[data-tab="tools"]')
// Click Select Area button
await page.click('[data-maps-v2-target="selectAreaButton"]')
await page.click('[data-maps--maplibre-target="selectAreaButton"]')
await page.waitForTimeout(500)
// Get map canvas
@ -269,11 +269,11 @@ test.describe('Area Selection in Maps V2', () => {
test('should add selected points layer to map when points are selected', async ({ page }) => {
// Open settings panel and switch to Tools tab
await page.click('[data-action="click->maps-v2#toggleSettings"]')
await page.click('[data-action="click->maps--maplibre#toggleSettings"]')
await page.click('button[data-tab="tools"]')
// Click Select Area button
await page.click('[data-maps-v2-target="selectAreaButton"]')
await page.click('[data-maps--maplibre-target="selectAreaButton"]')
// Get map canvas
const canvas = page.locator('canvas.maplibregl-canvas')
@ -295,9 +295,9 @@ test.describe('Area Selection in Maps V2', () => {
// Check if selected points layer exists
const hasSelectedPointsLayer = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.areaSelectionManager?.selectedPointsLayer !== undefined
})
@ -305,9 +305,9 @@ test.describe('Area Selection in Maps V2', () => {
if (hasSelectedPointsLayer) {
// Verify layer is on the map
const layerExistsOnMap = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('selected-points') !== undefined
})
expect(layerExistsOnMap).toBeTruthy()

View file

@ -18,7 +18,7 @@ test.describe('Map Core', () => {
test.describe('Initialization', () => {
test('loads map container', async ({ page }) => {
const mapContainer = page.locator('[data-maps-v2-target="container"]')
const mapContainer = page.locator('[data-maps--maplibre-target="container"]')
await expect(mapContainer).toBeVisible()
})
@ -33,7 +33,7 @@ test.describe('Map Core', () => {
})
test('has valid initial center and zoom', async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForMapLibre(page)
await waitForLoadingComplete(page)
@ -55,7 +55,7 @@ test.describe('Map Core', () => {
test.describe('Loading States', () => {
test('shows loading indicator during data fetch', async ({ page }) => {
const loading = page.locator('[data-maps-v2-target="loading"]')
const loading = page.locator('[data-maps--maplibre-target="loading"]')
const navigationPromise = page.reload({ waitUntil: 'domcontentloaded' })
@ -83,7 +83,7 @@ test.describe('Map Core', () => {
test.describe('Data Bounds', () => {
test('fits map bounds to loaded data', async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForMapLibre(page)
await waitForLoadingComplete(page)
@ -96,7 +96,7 @@ test.describe('Map Core', () => {
test.describe('Lifecycle', () => {
test('cleans up and reinitializes on navigation', async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForLoadingComplete(page)
@ -114,7 +114,7 @@ test.describe('Map Core', () => {
})
test('reloads data when changing date range', async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForLoadingComplete(page)

View file

@ -9,7 +9,7 @@ import {
test.describe('Map Interactions', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForLoadingComplete(page)
await page.waitForTimeout(500)
@ -57,7 +57,7 @@ test.describe('Map Interactions', () => {
test.describe('Hover Effects', () => {
test('map container is interactive', async ({ page }) => {
const mapContainer = page.locator('[data-maps-v2-target="container"]')
const mapContainer = page.locator('[data-maps--maplibre-target="container"]')
await expect(mapContainer).toBeVisible()
})
})

View file

@ -3,12 +3,12 @@ import { closeOnboardingModal } from '../../../helpers/navigation.js'
test.describe('Advanced Layers', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/maps_v2')
await page.goto('/maps/maplibre')
await page.evaluate(() => {
localStorage.removeItem('dawarich-maps-v2-settings')
localStorage.removeItem('dawarich-maps--maplibre-settings')
})
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await page.waitForTimeout(2000)
})
@ -16,7 +16,7 @@ test.describe('Advanced Layers', () => {
test.describe('Fog of War', () => {
test('fog layer is disabled by default', async ({ page }) => {
const fogEnabled = await page.evaluate(() => {
const settings = JSON.parse(localStorage.getItem('dawarich-maps-v2-settings') || '{}')
const settings = JSON.parse(localStorage.getItem('dawarich-maps--maplibre-settings') || '{}')
return settings.fogEnabled
})

View file

@ -14,7 +14,7 @@ test.describe('Areas Layer', () => {
test.describe('Toggle', () => {
test('areas layer toggle exists', async ({ page }) => {
// Open settings panel
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -27,7 +27,7 @@ test.describe('Areas Layer', () => {
test('can toggle areas layer', async ({ page }) => {
// Open settings panel
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -46,7 +46,7 @@ test.describe('Areas Layer', () => {
test.describe('Area Creation', () => {
test('should have Create an Area button in Tools tab', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Tools tab
@ -60,7 +60,7 @@ test.describe('Areas Layer', () => {
test('should change cursor to crosshair when Create an Area is clicked', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="tools"]').click()
await page.waitForTimeout(200)
@ -79,7 +79,7 @@ test.describe('Areas Layer', () => {
test('should show area preview while drawing', async ({ page }) => {
// Enable creation mode
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="tools"]').click()
await page.waitForTimeout(200)
@ -97,9 +97,9 @@ test.describe('Areas Layer', () => {
// Verify draw layers exist
const hasDrawLayers = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
const map = controller?.map
return map && map.getSource('draw-source') !== undefined
})
@ -108,7 +108,7 @@ test.describe('Areas Layer', () => {
test('should open modal when area is drawn', async ({ page }) => {
// Enable creation mode
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="tools"]').click()
await page.waitForTimeout(200)
@ -133,7 +133,7 @@ test.describe('Areas Layer', () => {
test('should display radius and location in modal', async ({ page }) => {
// Enable creation mode and draw area
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="tools"]').click()
await page.waitForTimeout(200)
@ -167,7 +167,7 @@ test.describe('Areas Layer', () => {
test('should create area and enable layer when submitted', async ({ page }) => {
// Draw area
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="tools"]').click()
await page.waitForTimeout(200)
@ -224,7 +224,7 @@ test.describe('Areas Layer', () => {
await page.waitForTimeout(1000)
// Verify areas layer is now enabled
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="layers"]').click()
await page.waitForTimeout(200)

View file

@ -3,7 +3,7 @@ import { closeOnboardingModal } from '../../../helpers/navigation.js'
test.describe('Heatmap Layer', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await page.waitForTimeout(2000)
})
@ -22,18 +22,18 @@ test.describe('Heatmap Layer', () => {
// Wait for heatmap layer to be created
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
if (!element) return false
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('heatmap') !== undefined
}, { timeout: 3000 }).catch(() => false)
const hasHeatmap = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
if (!element) return false
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('heatmap') !== undefined
})
@ -70,7 +70,7 @@ test.describe('Heatmap Layer', () => {
await page.waitForTimeout(500)
const settings = await page.evaluate(() => {
return localStorage.getItem('dawarich-maps-v2-settings')
return localStorage.getItem('dawarich-maps--maplibre-settings')
})
const parsed = JSON.parse(settings)

View file

@ -18,7 +18,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should have Tools tab with Create a Place button', async ({ page }) => {
// Click settings button
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Tools tab
@ -32,7 +32,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should enable place creation mode when Create a Place is clicked', async ({ page }) => {
// Open Tools tab
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="tools"]').click()
await page.waitForTimeout(200)
@ -51,7 +51,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should open modal when map is clicked in creation mode', async ({ page }) => {
// Enable creation mode
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
await page.locator('button[data-tab="tools"]').click()
await page.waitForTimeout(200)
@ -74,7 +74,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should have Places toggle in settings', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -92,7 +92,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should show tag filters when Places toggle is enabled with all tags enabled by default', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -105,11 +105,11 @@ test.describe('Places Layer in Maps V2', () => {
await page.waitForTimeout(1000)
// Verify filters are visible
const placesFilters = page.locator('[data-maps-v2-target="placesFilters"]')
const placesFilters = page.locator('[data-maps--maplibre-target="placesFilters"]')
await expect(placesFilters).toBeVisible()
// Verify "Enable All Tags" toggle is enabled by default
const enableAllToggle = page.locator('input[data-maps-v2-target="enableAllPlaceTagsToggle"]')
const enableAllToggle = page.locator('input[data-maps--maplibre-target="enableAllPlaceTagsToggle"]')
await expect(enableAllToggle).toBeChecked()
// Verify all tag checkboxes are checked by default
@ -127,7 +127,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should toggle tag filter styling when clicked', async ({ page }) => {
// Open settings and enable Places
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -139,8 +139,8 @@ test.describe('Places Layer in Maps V2', () => {
await page.waitForTimeout(1000)
// Get first tag badge (in Places filters section) - click badge since checkbox is hidden
const firstBadge = page.locator('[data-maps-v2-target="placesFilters"] .badge').first()
const firstCheckbox = page.locator('[data-maps-v2-target="placesFilters"] input[name="place_tag_ids[]"]').first()
const firstBadge = page.locator('[data-maps--maplibre-target="placesFilters"] .badge').first()
const firstCheckbox = page.locator('[data-maps--maplibre-target="placesFilters"] input[name="place_tag_ids[]"]').first()
// Check initial state (should be checked by default)
await expect(firstCheckbox).toBeChecked()
@ -160,7 +160,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should hide tag filters when Places toggle is disabled', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -175,14 +175,14 @@ test.describe('Places Layer in Maps V2', () => {
await page.waitForTimeout(300)
// Verify filters are hidden
const placesFilters = page.locator('[data-maps-v2-target="placesFilters"]')
const placesFilters = page.locator('[data-maps--maplibre-target="placesFilters"]')
const isVisible = await placesFilters.isVisible()
expect(isVisible).toBe(false)
})
test('can toggle places layer', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -201,7 +201,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should show popup when clicking on a place marker', async ({ page }) => {
// Enable Places layer
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -213,7 +213,7 @@ test.describe('Places Layer in Maps V2', () => {
await page.waitForTimeout(1000)
// Close settings to make map clickable
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Try to click on a place marker (if any exist)
@ -233,7 +233,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should sync Enable All Tags toggle with individual tag checkboxes', async ({ page }) => {
// Open settings and enable Places
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -244,14 +244,14 @@ test.describe('Places Layer in Maps V2', () => {
await placesToggle.check()
await page.waitForTimeout(1000)
const enableAllToggle = page.locator('input[data-maps-v2-target="enableAllPlaceTagsToggle"]')
const enableAllToggle = page.locator('input[data-maps--maplibre-target="enableAllPlaceTagsToggle"]')
// Initially all tags should be enabled
await expect(enableAllToggle).toBeChecked()
// Click first badge to uncheck it (checkbox is hidden, must click badge)
const firstBadge = page.locator('[data-maps-v2-target="placesFilters"] .badge').first()
const firstCheckbox = page.locator('[data-maps-v2-target="placesFilters"] input[name="place_tag_ids[]"]').first()
const firstBadge = page.locator('[data-maps--maplibre-target="placesFilters"] .badge').first()
const firstCheckbox = page.locator('[data-maps--maplibre-target="placesFilters"] input[name="place_tag_ids[]"]').first()
await firstBadge.click()
await page.waitForTimeout(300)
@ -269,7 +269,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should enable/disable all tags when Enable All Tags toggle is clicked', async ({ page }) => {
// Open settings and enable Places
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -280,7 +280,7 @@ test.describe('Places Layer in Maps V2', () => {
await placesToggle.check()
await page.waitForTimeout(1000)
const enableAllToggle = page.locator('input[data-maps-v2-target="enableAllPlaceTagsToggle"]')
const enableAllToggle = page.locator('input[data-maps--maplibre-target="enableAllPlaceTagsToggle"]')
// Disable all tags
await enableAllToggle.uncheck()
@ -305,7 +305,7 @@ test.describe('Places Layer in Maps V2', () => {
test('should show no places when all tags are unchecked', async ({ page }) => {
// Open settings and enable Places
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(200)
// Click Layers tab
@ -317,7 +317,7 @@ test.describe('Places Layer in Maps V2', () => {
await page.waitForTimeout(1000)
// Disable all tags
const enableAllToggle = page.locator('input[data-maps-v2-target="enableAllPlaceTagsToggle"]')
const enableAllToggle = page.locator('input[data-maps--maplibre-target="enableAllPlaceTagsToggle"]')
await enableAllToggle.uncheck()
await page.waitForTimeout(1000)

View file

@ -9,7 +9,7 @@ import {
test.describe('Points Layer', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForLoadingComplete(page)
await page.waitForTimeout(1500)
@ -19,9 +19,9 @@ test.describe('Points Layer', () => {
test('displays points layer', async ({ page }) => {
// Wait for points layer to be added
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('points') !== undefined
}, { timeout: 10000 }).catch(() => false)
@ -31,9 +31,9 @@ test.describe('Points Layer', () => {
test('loads and displays point data', async ({ page }) => {
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getSource('points-source') !== undefined
}, { timeout: 15000 }).catch(() => false)
@ -47,9 +47,9 @@ test.describe('Points Layer', () => {
test('points source contains valid GeoJSON features', async ({ page }) => {
// Wait for source to be added
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getSource('points-source') !== undefined
}, { timeout: 10000 }).catch(() => false)

View file

@ -11,7 +11,7 @@ import {
test.describe('Routes Layer', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForMapLibre(page)
await waitForLoadingComplete(page)
@ -21,11 +21,11 @@ test.describe('Routes Layer', () => {
test.describe('Layer Existence', () => {
test('routes layer exists on map', async ({ page }) => {
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
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-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('routes') !== undefined
}, { timeout: 10000 }).catch(() => false)
@ -37,11 +37,11 @@ test.describe('Routes Layer', () => {
test.describe('Data Source', () => {
test('routes source has data', async ({ page }) => {
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
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-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getSource('routes-source') !== undefined
}, { timeout: 20000 })
@ -92,22 +92,22 @@ test.describe('Routes Layer', () => {
test.describe('Styling', () => {
test('routes have solid color', async ({ page }) => {
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
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-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('routes') !== undefined
}, { timeout: 20000 })
const routeLayerInfo = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
if (!element) return null
const app = window.Stimulus || window.Application
if (!app) return null
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
if (!controller?.map) return null
const layer = controller.map.getLayer('routes')
@ -132,21 +132,21 @@ test.describe('Routes Layer', () => {
test.describe('Layer Order', () => {
test('routes layer renders below points layer', async ({ page }) => {
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('routes') !== undefined &&
controller?.map?.getLayer('points') !== undefined
}, { timeout: 10000 })
const layerOrder = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
if (!element) return null
const app = window.Stimulus || window.Application
if (!app) return null
const controller = app.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
if (!controller?.map) return null
const style = controller.map.getStyle()

View file

@ -73,7 +73,7 @@ test.describe('Visits Layer', () => {
await page.waitForTimeout(500)
// Verify settings panel closed
const settingsPanel = page.locator('[data-maps-v2-target="settingsPanel"]')
const settingsPanel = page.locator('[data-maps--maplibre-target="settingsPanel"]')
const hasPanelOpenClass = await settingsPanel.evaluate((el) => el.classList.contains('open'))
expect(hasPanelOpenClass).toBe(false)

View file

@ -6,7 +6,7 @@ test.describe('Map Performance', () => {
test('map loads within acceptable time', async ({ page }) => {
const startTime = Date.now()
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForMapLibre(page)
await waitForLoadingComplete(page)
@ -19,7 +19,7 @@ test.describe('Map Performance', () => {
})
test('handles large datasets efficiently', async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-01T00:00&end_at=2025-10-31T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-01T00:00&end_at=2025-10-31T23:59')
await closeOnboardingModal(page)
const startTime = Date.now()

View file

@ -4,7 +4,7 @@ import { waitForMapLibre, waitForLoadingComplete } from '../helpers/setup.js'
test.describe('Location Search', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await waitForMapLibre(page)
await waitForLoadingComplete(page)
@ -18,7 +18,7 @@ test.describe('Location Search', () => {
await page.waitForTimeout(400)
// Search tab should be active by default
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
await expect(searchInput).toBeVisible()
await expect(searchInput).toHaveAttribute('placeholder', 'Enter name of a place')
})
@ -27,7 +27,7 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
await expect(resultsContainer).toBeAttached()
await expect(resultsContainer).toHaveClass(/hidden/)
})
@ -38,14 +38,14 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
// Type a search query
await searchInput.fill('New')
await page.waitForTimeout(500) // Wait for debounce
// Results container should become visible (or show loading)
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Wait for results to appear
await page.waitForTimeout(1000)
@ -63,8 +63,8 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Type single character
await searchInput.fill('N')
@ -78,8 +78,8 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Type search query
await searchInput.fill('New York')
@ -100,18 +100,18 @@ test.describe('Location Search', () => {
await page.waitForTimeout(1000)
const hasSearchManager = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
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-v2')
const controller = app.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.searchManager !== undefined
})
// Search manager should exist if search targets are present
const hasSearchTargets = await page.locator('[data-maps-v2-target="searchInput"]').count()
const hasSearchTargets = await page.locator('[data-maps--maplibre-target="searchInput"]').count()
if (hasSearchTargets > 0) {
expect(hasSearchManager).toBe(true)
}
@ -121,7 +121,7 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
await expect(searchInput).toHaveAttribute('autocomplete', 'off')
})
})
@ -131,8 +131,8 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Search for a location
await searchInput.fill('Sterndamm')
@ -157,8 +157,8 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Search and select location
await searchInput.fill('Sterndamm')
@ -191,8 +191,8 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Search and select location
await searchInput.fill('Sterndamm')
@ -236,8 +236,8 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Search and select location
await searchInput.fill('Sterndamm')
@ -288,7 +288,7 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const resultsContainer = page.locator('[data-maps-v2-target="searchResults"]')
const resultsContainer = page.locator('[data-maps--maplibre-target="searchResults"]')
// Check max-height class is set appropriately (max-h-96)
const hasMaxHeight = await resultsContainer.evaluate(el => {
@ -305,7 +305,7 @@ test.describe('Location Search', () => {
await page.click('button[title="Open map settings"]')
await page.waitForTimeout(400)
const searchInput = page.locator('[data-maps-v2-target="searchInput"]')
const searchInput = page.locator('[data-maps--maplibre-target="searchInput"]')
// Focus input with keyboard
await searchInput.focus()

View file

@ -4,14 +4,14 @@ import { getLayerVisibility } from '../helpers/setup.js'
test.describe('Map Settings', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/maps_v2?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await page.goto('/maps/maplibre?start_at=2025-10-15T00:00&end_at=2025-10-15T23:59')
await closeOnboardingModal(page)
await page.waitForTimeout(2000)
})
test.describe('Settings Panel', () => {
test('opens and closes settings panel', async ({ page }) => {
const panel = page.locator('[data-maps-v2-target="settingsPanel"]')
const panel = page.locator('[data-maps--maplibre-target="settingsPanel"]')
// Verify panel exists but is not open initially
await expect(panel).toBeVisible()
@ -62,9 +62,9 @@ test.describe('Map Settings', () => {
test('points layer visibility matches toggle state', async ({ page }) => {
// Wait for points layer to exist
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('points') !== undefined
}, { timeout: 5000 }).catch(() => false)
@ -84,9 +84,9 @@ test.describe('Map Settings', () => {
test('routes layer visibility matches toggle state', async ({ page }) => {
// Wait for routes layer to exist
await page.waitForFunction(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('routes') !== undefined
}, { timeout: 5000 }).catch(() => false)
@ -182,7 +182,7 @@ test.describe('Map Settings', () => {
const initialState = await pointsToggle.isChecked()
const settings = await page.evaluate(() => {
return localStorage.getItem('dawarich-maps-v2-settings')
return localStorage.getItem('dawarich-maps--maplibre-settings')
})
expect(settings).toBeTruthy()

View file

@ -14,9 +14,9 @@ test.describe('Realtime Family Tracking', () => {
test.skip('family layer exists but is hidden by default', async ({ page }) => {
// Family layer is created but hidden until ActionCable data arrives
const layerExists = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
return controller?.map?.getLayer('family') !== undefined
})

View file

@ -14,7 +14,7 @@ test.describe('Live Mode', () => {
test.describe('Live Mode Toggle', () => {
test('should have live mode toggle in settings', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(300)
// Click Settings tab
@ -22,7 +22,7 @@ test.describe('Live Mode', () => {
await page.waitForTimeout(300)
// Verify Live Mode toggle exists
const liveModeToggle = page.locator('[data-maps-v2-realtime-target="liveModeToggle"]')
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
await expect(liveModeToggle).toBeVisible()
// Verify label text
@ -36,12 +36,12 @@ test.describe('Live Mode', () => {
test('should toggle live mode on and off', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
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-v2-realtime-target="liveModeToggle"]')
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
// Get initial state
const initialState = await liveModeToggle.isChecked()
@ -65,12 +65,12 @@ test.describe('Live Mode', () => {
test('should show toast notification when toggling live mode', async ({ page }) => {
// Open settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
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-v2-realtime-target="liveModeToggle"]')
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
const initialState = await liveModeToggle.isChecked()
// Toggle and watch for toast
@ -86,20 +86,20 @@ test.describe('Live Mode', () => {
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-v2-realtime"]')
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2-realtime')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
return controller !== undefined
})
expect(realtimeControllerExists).toBe(true)
})
test('should have access to maps-v2 controller', async ({ page }) => {
test('should have access to maps--maplibre controller', async ({ page }) => {
const hasMapsController = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2-realtime"]')
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
const app = window.Stimulus || window.Application
const realtimeController = app?.getControllerForElementAndIdentifier(element, 'maps-v2-realtime')
const realtimeController = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
const mapsController = realtimeController?.mapsV2Controller
return mapsController !== undefined && mapsController.map !== undefined
})
@ -112,9 +112,9 @@ test.describe('Live Mode', () => {
await page.waitForTimeout(2000)
const channelsInitialized = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2-realtime"]')
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2-realtime')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
return controller?.channels !== undefined
})
@ -181,9 +181,9 @@ test.describe('Live Mode', () => {
test.describe('Point Handling', () => {
test('should have handleNewPoint method', async ({ page }) => {
const hasMethod = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2-realtime"]')
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2-realtime')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
return typeof controller?.handleNewPoint === 'function'
})
@ -192,9 +192,9 @@ test.describe('Live Mode', () => {
test('should have zoomToPoint method', async ({ page }) => {
const hasMethod = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2-realtime"]')
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2-realtime')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
return typeof controller?.zoomToPoint === 'function'
})
@ -207,9 +207,9 @@ test.describe('Live Mode', () => {
// Get initial point count
const initialCount = await page.evaluate(() => {
const element = document.querySelector('[data-controller*="maps-v2"]')
const element = document.querySelector('[data-controller*="maps--maplibre"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre')
const pointsLayer = controller?.layerManager?.getLayer('points')
return pointsLayer?.data?.features?.length || 0
})
@ -238,12 +238,12 @@ test.describe('Live Mode', () => {
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-v2#toggleSettings"]').first().click()
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-v2-realtime-target="liveModeToggle"]')
const liveModeToggle = page.locator('[data-maps--maplibre-realtime-target="liveModeToggle"]')
// Enable live mode
if (!await liveModeToggle.isChecked()) {
@ -255,9 +255,9 @@ test.describe('Live Mode', () => {
expect(await liveModeToggle.isChecked()).toBe(true)
// Close and reopen settings
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
await page.locator('[data-action="click->maps--maplibre#toggleSettings"]').first().click()
await page.waitForTimeout(300)
await page.locator('[data-action="click->maps-v2#toggleSettings"]').first().click()
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)
@ -271,9 +271,9 @@ test.describe('Live Mode', () => {
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-v2-realtime"]')
const element = document.querySelector('[data-controller*="maps--maplibre-realtime"]')
const app = window.Stimulus || window.Application
const controller = app?.getControllerForElementAndIdentifier(element, 'maps-v2-realtime')
const controller = app?.getControllerForElementAndIdentifier(element, 'maps--maplibre-realtime')
// The controller should have the mapsV2Controller getter
return typeof controller?.mapsV2Controller !== 'undefined'