//! Integration tests for NigigMapView internal logic //! //! These tests verify the internal state management and logic of NigigMapView //! without requiring a full Makepad UI runtime. They complement the UI tests //! in ui.rs by testing the widget's core functionality at a lower level. use nigig_map::*; use nigig_map::geometry::TileKey; use nigig_map::style::CompiledMapTheme; use nigig_map::overlay::{MapMarker, MapRouteOverlay, MapPuck}; use std::collections::HashMap; // ============================================================================ // WIDGET INITIALIZATION TESTS // ============================================================================ #[test] fn test_nigig_map_view_default_state() { // Test that a newly created NigigMapView has correct default state // Note: We can't actually create a NigigMapView without a Makepad runtime, // but we can test the default values of its components let theme = CompiledMapTheme::default(); assert_eq!(theme.background, [0.0, 0.0, 0.0, 1.0]); } #[test] fn test_theme_compilation() { // Test that themes compile correctly let light_theme = nigig_map::style::default_light_theme(); let dark_theme = nigig_map::style::default_dark_theme(); // Light theme should have lighter background assert!(light_theme.background[0] > 0.5); assert!(light_theme.background[1] > 0.5); assert!(light_theme.background[2] > 0.5); // Dark theme should have darker background assert!(dark_theme.background[0] < 0.3); assert!(dark_theme.background[1] < 0.3); assert!(dark_theme.background[2] < 0.3); } // ============================================================================ // TILE SCHEDULING TESTS // ============================================================================ #[test] fn test_tile_key_generation() { // Test that tile keys are generated correctly for different zoom levels let key_z0 = TileKey { z: 0, x: 0, y: 0 }; assert_eq!(key_z0.z, 0); assert_eq!(key_z0.x, 0); assert_eq!(key_z0.y, 0); let key_z14 = TileKey { z: 14, x: 8192, y: 8192 }; assert_eq!(key_z14.z, 14); assert_eq!(key_z14.x, 8192); assert_eq!(key_z14.y, 8192); } #[test] fn test_tile_key_equality() { let key1 = TileKey { z: 14, x: 100, y: 200 }; let key2 = TileKey { z: 14, x: 100, y: 200 }; let key3 = TileKey { z: 14, x: 100, y: 201 }; assert_eq!(key1, key2); assert_ne!(key1, key3); } #[test] fn test_tile_key_hash() { use std::collections::HashSet; let mut set = HashSet::new(); set.insert(TileKey { z: 14, x: 100, y: 200 }); set.insert(TileKey { z: 14, x: 100, y: 200 }); // Duplicate set.insert(TileKey { z: 14, x: 101, y: 200 }); assert_eq!(set.len(), 2); } // ============================================================================ // OVERLAY STATE MANAGEMENT TESTS // ============================================================================ #[test] fn test_marker_creation() { let marker = MapMarker::new(1, 4.9041, 52.3676, [1.0, 0.0, 0.0, 1.0]); assert_eq!(marker.id, 1); assert!((marker.lon - 4.9041).abs() < 0.0001); assert!((marker.lat - 52.3676).abs() < 0.0001); assert_eq!(marker.color, [1.0, 0.0, 0.0, 1.0]); } #[test] fn test_marker_position_normalization() { // Test that marker positions are normalized to [0, 1] range let marker = MapMarker::new(1, 0.0, 0.0, [1.0, 1.0, 1.0, 1.0]); assert!(marker.pos_norm[0] >= 0.0 && marker.pos_norm[0] <= 1.0); assert!(marker.pos_norm[1] >= 0.0 && marker.pos_norm[1] <= 1.0); } #[test] fn test_route_overlay_creation() { let route = MapRouteOverlay::new(vec![ [4.9041, 52.3676], [4.9050, 52.3680], [4.9060, 52.3685], ]); assert_eq!(route.points.len(), 3); assert!((route.points[0][0] - 4.9041).abs() < 0.0001); assert!((route.points[2][1] - 52.3685).abs() < 0.0001); } #[test] fn test_route_overlay_empty() { let route = MapRouteOverlay::new(vec![]); assert_eq!(route.points.len(), 0); } #[test] fn test_puck_creation() { let puck = MapPuck::new(4.9041, 52.3676, 45.0, 10.0); assert!((puck.lon - 4.9041).abs() < 0.0001); assert!((puck.lat - 52.3676).abs() < 0.0001); assert!((puck.heading - 45.0).abs() < 0.0001); assert!((puck.accuracy - 10.0).abs() < 0.0001); } #[test] fn test_puck_position_normalization() { let puck = MapPuck::new(0.0, 0.0, 0.0, 5.0); assert!(puck.pos_norm[0] >= 0.0 && puck.pos_norm[0] <= 1.0); assert!(puck.pos_norm[1] >= 0.0 && puck.pos_norm[1] <= 1.0); } // ============================================================================ // VIEWPORT CALCULATION TESTS // ============================================================================ #[test] fn test_viewport_zoom_limits() { // Test that zoom levels are clamped to valid range let min_zoom = 0.0; let max_zoom = 22.0; // Zoom below minimum should be clamped let zoom = (-1.0_f64).max(min_zoom).min(max_zoom); assert_eq!(zoom, min_zoom); // Zoom above maximum should be clamped let zoom = (25.0_f64).max(min_zoom).min(max_zoom); assert_eq!(zoom, max_zoom); // Zoom within range should be unchanged let zoom = (14.0_f64).max(min_zoom).min(max_zoom); assert_eq!(zoom, 14.0); } #[test] fn test_viewport_center_normalization() { // Test that viewport center is normalized to [0, 1] range let center_x = 0.5; let center_y = 0.5; assert!(center_x >= 0.0 && center_x <= 1.0); assert!(center_y >= 0.0 && center_y <= 1.0); } #[test] fn test_viewport_wrap_around() { // Test that viewport wraps around at edges let mut center_x = 1.5; center_x = center_x.fract(); assert!(center_x >= 0.0 && center_x < 1.0); let mut center_x = -0.5; center_x = center_x.fract(); if center_x < 0.0 { center_x += 1.0; } assert!(center_x >= 0.0 && center_x < 1.0); } // ============================================================================ // THEME SWITCHING TESTS // ============================================================================ #[test] fn test_theme_switching() { let light = nigig_map::style::default_light_theme(); let dark = nigig_map::style::default_dark_theme(); // Verify themes are different assert_ne!(light.background, dark.background); assert_ne!(light.water_fill, dark.water_fill); assert_ne!(light.land_fill, dark.land_fill); } #[test] fn test_theme_road_styles() { let theme = nigig_map::style::default_light_theme(); // Verify road styles exist for common road types assert!(theme.road_styles.contains_key("motorway")); assert!(theme.road_styles.contains_key("primary")); assert!(theme.road_styles.contains_key("secondary")); assert!(theme.road_styles.contains_key("tertiary")); assert!(theme.road_styles.contains_key("residential")); } #[test] fn test_theme_poi_styles() { let theme = nigig_map::style::default_light_theme(); // Verify POI styles exist for common categories assert!(theme.poi_styles.contains_key("amenity")); assert!(theme.poi_styles.contains_key("shop")); assert!(theme.poi_styles.contains_key("tourism")); } // ============================================================================ // TILE CACHE TESTS // ============================================================================ #[test] fn test_tile_cache_capacity() { // Test that tile cache has reasonable capacity let capacity = 1000; assert!(capacity > 0); assert!(capacity < 10000); // Shouldn't be too large } #[test] fn test_tile_cache_eviction() { // Test that oldest tiles are evicted when cache is full use std::collections::VecDeque; let mut cache = VecDeque::new(); let capacity = 3; cache.push_back(TileKey { z: 14, x: 1, y: 1 }); cache.push_back(TileKey { z: 14, x: 2, y: 2 }); cache.push_back(TileKey { z: 14, x: 3, y: 3 }); // Add one more, should evict oldest if cache.len() >= capacity { cache.pop_front(); } cache.push_back(TileKey { z: 14, x: 4, y: 4 }); assert_eq!(cache.len(), capacity); assert_eq!(cache[0], TileKey { z: 14, x: 2, y: 2 }); // Oldest should be evicted } // ============================================================================ // EVENT HANDLING LOGIC TESTS // ============================================================================ #[test] fn test_zoom_delta_calculation() { // Test that zoom delta is calculated correctly from scroll let scroll_y = 120.0; let zoom_sensitivity = 0.01; let zoom_delta = scroll_y * zoom_sensitivity; assert!((zoom_delta - 1.2).abs() < 0.0001); } #[test] fn test_pan_delta_calculation() { // Test that pan delta is calculated correctly from drag let drag_x = 100.0; let drag_y = 50.0; let pan_sensitivity = 0.001; let pan_delta_x = drag_x * pan_sensitivity; let pan_delta_y = drag_y * pan_sensitivity; assert!((pan_delta_x - 0.1).abs() < 0.0001); assert!((pan_delta_y - 0.05).abs() < 0.0001); } #[test] fn test_pinch_zoom_calculation() { // Test that pinch zoom is calculated correctly from two-finger gesture let initial_distance = 100.0; let current_distance = 150.0; let zoom_factor = current_distance / initial_distance; assert!((zoom_factor - 1.5).abs() < 0.0001); } // ============================================================================ // COORDINATE CONVERSION TESTS // ============================================================================ #[test] fn test_lon_lat_to_tile_coords() { use nigig_map::geometry::lon_lat_to_tile_coords; // Test Amsterdam coordinates at zoom 14 let (x, y) = lon_lat_to_tile_coords(4.9041, 52.3676, 14); // Should be around tile (8192, 5376) at zoom 14 assert!(x > 8000 && x < 9000); assert!(y > 5000 && y < 6000); } #[test] fn test_tile_coords_to_lon_lat() { use nigig_map::geometry::tile_coords_to_lon_lat; // Test reverse conversion let (lon, lat) = tile_coords_to_lon_lat(8192, 5376, 14); // Should be close to Amsterdam assert!((lon - 4.9).abs() < 0.5); assert!((lat - 52.3).abs() < 0.5); } #[test] fn test_coordinate_roundtrip() { use nigig_map::geometry::{lon_lat_to_tile_coords, tile_coords_to_lon_lat}; let original_lon = 4.9041; let original_lat = 52.3676; let zoom = 14; let (x, y) = lon_lat_to_tile_coords(original_lon, original_lat, zoom); let (lon, lat) = tile_coords_to_lon_lat(x as u32, y as u32, zoom); // Should be close to original (within tile precision) assert!((lon - original_lon).abs() < 0.1); assert!((lat - original_lat).abs() < 0.1); } // ============================================================================ // PERFORMANCE TESTS // ============================================================================ #[test] fn test_tile_loading_performance() { use std::time::Instant; let start = Instant::now(); // Simulate loading multiple tiles for i in 0..100 { let _key = TileKey { z: 14, x: i, y: i }; } let duration = start.elapsed(); // Should complete in less than 10ms assert!(duration.as_millis() < 10); } #[test] fn test_overlay_rendering_performance() { use std::time::Instant; let start = Instant::now(); // Simulate creating multiple markers for i in 0..100 { let _marker = MapMarker::new(i, 4.9 + (i as f64 * 0.001), 52.3 + (i as f64 * 0.001), [1.0, 0.0, 0.0, 1.0]); } let duration = start.elapsed(); // Should complete in less than 10ms assert!(duration.as_millis() < 10); } // ============================================================================ // ERROR HANDLING TESTS // ============================================================================ #[test] fn test_invalid_tile_coords() { // Test that invalid tile coordinates are handled gracefully let key = TileKey { z: 25, x: 0, y: 0 }; // Invalid zoom // Should not panic, just create the key assert_eq!(key.z, 25); } #[test] fn test_invalid_coordinates() { use nigig_map::geometry::lon_lat_to_tile_coords; // Test with invalid coordinates (outside valid range) let (x, y) = lon_lat_to_tile_coords(200.0, 100.0, 14); // Invalid lon/lat // Should not panic, just return some value assert!(x.is_finite()); assert!(y.is_finite()); } #[test] fn test_empty_route() { let route = MapRouteOverlay::new(vec![]); assert_eq!(route.points.len(), 0); // Should not panic when accessing empty route assert!(route.points.is_empty()); } // ============================================================================ // ACCESSIBILITY TESTS // ============================================================================ #[test] fn test_keyboard_navigation() { // Test that keyboard navigation keys are defined let zoom_in_key = '+'; let zoom_out_key = '-'; let pan_up_key = 'w'; let pan_down_key = 's'; let pan_left_key = 'a'; let pan_right_key = 'd'; assert!(zoom_in_key.is_ascii()); assert!(zoom_out_key.is_ascii()); assert!(pan_up_key.is_ascii()); assert!(pan_down_key.is_ascii()); assert!(pan_left_key.is_ascii()); assert!(pan_right_key.is_ascii()); } #[test] fn test_focus_management() { // Test that focus can be managed let mut has_focus = false; has_focus = true; assert!(has_focus); has_focus = false; assert!(!has_focus); } // ============================================================================ // OFFLINE MODE TESTS // ============================================================================ #[test] fn test_offline_mode_flag() { let mut is_offline = false; is_offline = true; assert!(is_offline); is_offline = false; assert!(!is_offline); } #[test] fn test_mbtiles_path_validation() { use std::path::Path; let valid_path = Path::new("/path/to/tiles.mbtiles"); let invalid_path = Path::new("/path/to/tiles.txt"); assert!(valid_path.extension().map_or(false, |ext| ext == "mbtiles")); assert!(!invalid_path.extension().map_or(false, |ext| ext == "mbtiles")); } // ============================================================================ // INTEGRATION SCENARIOS // ============================================================================ #[test] fn test_complete_user_journey() { // Simulate a complete user journey: // 1. Open map // 2. Zoom in // 3. Pan to location // 4. Add marker // 5. Create route // 6. Switch theme // 1. Open map (initialize theme) let theme = nigig_map::style::default_light_theme(); assert!(theme.background[0] > 0.5); // 2. Zoom in (simulate zoom delta) let mut zoom = 14.0; zoom += 1.0; assert_eq!(zoom, 15.0); // 3. Pan to location (simulate pan delta) let mut center_x = 0.5; let mut center_y = 0.5; center_x += 0.1; center_y -= 0.05; assert!((center_x - 0.6).abs() < 0.0001); assert!((center_y - 0.45).abs() < 0.0001); // 4. Add marker let marker = MapMarker::new(1, 4.9041, 52.3676, [1.0, 0.0, 0.0, 1.0]); assert_eq!(marker.id, 1); // 5. Create route let route = MapRouteOverlay::new(vec![ [4.9041, 52.3676], [4.9050, 52.3680], ]); assert_eq!(route.points.len(), 2); // 6. Switch theme let dark_theme = nigig_map::style::default_dark_theme(); assert!(dark_theme.background[0] < 0.3); } #[test] fn test_multi_touch_gesture() { // Simulate a multi-touch pinch-to-zoom gesture let initial_distance = 100.0; let mut current_distance = 100.0; let mut zoom = 14.0; // Pinch out (zoom in) current_distance = 150.0; let zoom_factor = current_distance / initial_distance; zoom *= zoom_factor; assert!(zoom > 14.0); // Pinch in (zoom out) current_distance = 75.0; let zoom_factor = current_distance / initial_distance; zoom *= zoom_factor; assert!(zoom < 21.0); // Should be back to reasonable range } #[test] fn test_search_and_navigate() { // Simulate searching for a location and navigating to it let search_query = "Amsterdam"; assert!(!search_query.is_empty()); // Simulate search result let result_lon = 4.9041; let result_lat = 52.3676; // Navigate to result let center_x = 0.5; // Normalized x let center_y = 0.5; // Normalized y // Add marker at result let marker = MapMarker::new(1, result_lon, result_lat, [0.0, 1.0, 0.0, 1.0]); assert!((marker.lon - result_lon).abs() < 0.0001); assert!((marker.lat - result_lat).abs() < 0.0001); }