From 750272f6c3e418c37d2f1f641865b6f79d78f078 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Mon, 20 Oct 2025 14:22:20 +0300 Subject: [PATCH 01/12] [Core] Integration tests in progress --- .../xcschemes/MapboxNavigation.xcscheme | 10 ++++ Package.resolved | 4 +- Package.swift | 14 +++++ ...MapboxCoreNavigationIntegrationTests.swift | 54 +++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/MapboxNavigation.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/MapboxNavigation.xcscheme index 730db16d9d4..61a09e03161 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/MapboxNavigation.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/MapboxNavigation.xcscheme @@ -28,6 +28,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + Bool in + print(">>>> progress") + return true + } + + expectation(forNotification: .routeControllerDidRefreshRoute, object: navigation.router) { (notification) -> Bool in + print(">>>> refresh") + return true + } + + expectation(forNotification: .routeControllerDidUpdateAlternatives, object: navigation.router) { (notification) -> Bool in + print(">>>> alternatives") + return true + } + + let simulationLocations = route.shape!.coordinates.simulationLocations + simulationLocations.forEach { + navigation.locationManager(navigation.locationManager, didUpdateLocations: [$0]) + } + + waitForExpectations(timeout: waitForInterval) { XCTAssertNil($0) } + } +} + +extension [CLLocationCoordinate2D] { + var simulationLocations: [CLLocation] { + let now = Date() + return enumerated() + .map { + CLLocation( + coordinate: $0.element, + altitude: -1, + horizontalAccuracy: 10, + verticalAccuracy: -1, + course: -1, + speed: 10, + timestamp: now + $0.offset + ) + } + } } From 636dfa426e332c861a3d10d945dab93bbcc23ae0 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 21 Oct 2025 16:16:05 +0300 Subject: [PATCH 02/12] [Core] Integration tests in progress --- .../Fixtures/profile-route-original.json | 2821 +++++++++++++++++ .../Fixtures/profile-route-refresh.json | 222 ++ ...MapboxCoreNavigationIntegrationTests.swift | 54 - .../RouteRefreshIntegrationTests.swift | 131 + 4 files changed, 3174 insertions(+), 54 deletions(-) create mode 100644 Sources/TestHelper/Fixtures/profile-route-original.json create mode 100644 Sources/TestHelper/Fixtures/profile-route-refresh.json create mode 100644 Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift diff --git a/Sources/TestHelper/Fixtures/profile-route-original.json b/Sources/TestHelper/Fixtures/profile-route-original.json new file mode 100644 index 00000000000..2e52c3f3eb8 --- /dev/null +++ b/Sources/TestHelper/Fixtures/profile-route-original.json @@ -0,0 +1,2821 @@ +{ + "routes": [ + { + "weight_typical": 239.038, + "duration_typical": 233.851, + "weight_name": "auto", + "weight": 280.498, + "duration": 275.311, + "distance": 1023.197, + "legs": [ + { + "via_waypoints": [], + "admins": [ + { + "iso_3166_1_alpha3": "USA", + "iso_3166_1": "US" + } + ], + "annotation": { + "maxspeed": [ + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + null, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 9, + 9, + 9, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 9, + 9, + 9, + 9, + 9 + ], + "duration": [ + 4.925, + 2.508, + 3.984, + 49.449, + 6.208, + 2.814, + 7.618, + 11.645, + 1.27, + 4.589, + 4.451, + 5.257, + 1.886, + 1.29, + 3.917, + 8.37, + 1.03, + 9.997, + 3.259, + 12.57, + 0.678, + 17.408, + 1.254, + 4.87, + 2.253, + 6.12, + 5.868, + 1.343, + 3.608, + 6.267, + 25.647, + 12.808, + 36.206, + 3.944 + ] + }, + "weight_typical": 239.038, + "duration_typical": 233.851, + "weight": 280.498, + "duration": 275.311, + "steps": [ + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Drive northwest on West 47th Street. Then, in 200 meters, Turn right onto 8th Avenue.", + "announcement": "Drive northwest on West 47th Street. Then, in 200 meters, Turn right onto 8th Avenue.", + "distanceAlongGeometry": 235.775 + }, + { + "ssmlAnnouncement": "Turn right onto 8th Avenue.", + "announcement": "Turn right onto 8th Avenue.", + "distanceAlongGeometry": 61.667 + } + ], + "intersections": [ + { + "entry": [ + true + ], + "bearings": [ + 299 + ], + "duration": 4.925, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 4.925, + "geometry_index": 0, + "location": [ + -73.985023, + 40.759391 + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -73.985137, + 40.759438 + ], + "geometry_index": 1, + "admin_index": 0, + "weight": 2.202, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 0.505, + "turn_weight": 0.2, + "duration": 2.508, + "bearings": [ + 15, + 119, + 298 + ], + "out": 2, + "in": 1, + "entry": [ + false, + false, + true + ] + }, + { + "entry": [ + false, + false, + false, + true + ], + "in": 1, + "bearings": [ + 15, + 118, + 194, + 298 + ], + "duration": 53.434, + "turn_weight": 2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 3, + "weight": 55.432, + "geometry_index": 2, + "location": [ + -73.985207, + 40.759466 + ] + }, + { + "entry": [ + true, + false, + true + ], + "in": 1, + "bearings": [ + 27, + 119, + 299 + ], + "duration": 6.208, + "turn_weight": 0.2, + "turn_duration": 0.005, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 2, + "weight": 6.403, + "geometry_index": 4, + "location": [ + -73.98706, + 40.760234 + ] + }, + { + "bearings": [ + 31, + 119, + 213, + 299 + ], + "entry": [ + false, + false, + false, + true + ], + "in": 1, + "turn_weight": 2, + "turn_duration": 0.005, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 3, + "geometry_index": 5, + "location": [ + -73.987346, + 40.760354 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "8th Avenue" + } + ], + "type": "turn", + "modifier": "right", + "text": "8th Avenue" + }, + "distanceAlongGeometry": 235.775 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "depart", + "instruction": "Drive northwest on West 47th Street.", + "bearing_after": 299, + "bearing_before": 0, + "location": [ + -73.985023, + 40.759391 + ] + }, + "speedLimitSign": "mutcd", + "name": "West 47th Street", + "weight_typical": 57.449, + "duration_typical": 53.567, + "duration": 69.888, + "distance": 235.775, + "driving_side": "right", + "weight": 73.77, + "mode": "driving", + "geometry": "}dwvlA|~tblC}AbFw@jCoBtGok@bjBoFzPmB`G" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In 200 meters, Turn right onto West 50th Street.", + "announcement": "In 200 meters, Turn right onto West 50th Street.", + "distanceAlongGeometry": 226.377 + }, + { + "ssmlAnnouncement": "Turn right onto West 50th Street.", + "announcement": "Turn right onto West 50th Street.", + "distanceAlongGeometry": 71.111 + } + ], + "intersections": [ + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.987475, + 40.760409 + ], + "geometry_index": 6, + "admin_index": 0, + "weight": 7.46, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 6.158, + "turn_weight": 6, + "duration": 7.618, + "bearings": [ + 29, + 119, + 208, + 298 + ], + "out": 0, + "in": 1, + "entry": [ + true, + false, + false, + true + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "location": [ + -73.987429, + 40.760473 + ], + "geometry_index": 7, + "admin_index": 0, + "weight": 12.633, + "is_urban": true, + "mapbox_streets_v8": { + "class": "secondary" + }, + "turn_duration": 0.012, + "turn_weight": 1, + "duration": 11.645, + "bearings": [ + 29, + 118, + 209, + 298 + ], + "out": 0, + "in": 2, + "entry": [ + true, + false, + false, + false + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "location": [ + -73.987058, + 40.760981 + ], + "geometry_index": 8, + "admin_index": 0, + "weight": 2.258, + "is_urban": true, + "mapbox_streets_v8": { + "class": "secondary" + }, + "turn_duration": 0.012, + "turn_weight": 1, + "duration": 1.27, + "bearings": [ + 29, + 121, + 209, + 301 + ], + "out": 0, + "in": 2, + "entry": [ + true, + false, + false, + false + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.987018, + 40.761036 + ], + "geometry_index": 9, + "admin_index": 0, + "weight": 2.577, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 3.012, + "turn_weight": 1, + "duration": 4.589, + "bearings": [ + 29, + 120, + 209, + 298 + ], + "out": 0, + "in": 2, + "entry": [ + true, + true, + false, + false + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 27, + 118, + 209, + 296 + ], + "duration": 4.451, + "turn_weight": 1, + "turn_duration": 0.029, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 5.423, + "geometry_index": 10, + "location": [ + -73.986968, + 40.761105 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 29, + 207 + ], + "duration": 5.257, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 5.257, + "geometry_index": 11, + "location": [ + -73.986834, + 40.761301 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 29, + 209 + ], + "duration": 1.886, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 1.886, + "geometry_index": 12, + "location": [ + -73.986665, + 40.76153 + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 27, + 120, + 209, + 300 + ], + "duration": 1.29, + "turn_weight": 1, + "turn_duration": 0.029, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 2.262, + "geometry_index": 13, + "location": [ + -73.986604, + 40.761612 + ] + }, + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.986566, + 40.761668 + ], + "geometry_index": 14, + "admin_index": 0, + "weight": 1.904, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 3.013, + "turn_weight": 1, + "duration": 3.917, + "bearings": [ + 29, + 120, + 207, + 298 + ], + "out": 0, + "in": 2, + "entry": [ + true, + false, + false, + true + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 29, + 120, + 209, + 300 + ], + "duration": 8.37, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 9.358, + "geometry_index": 15, + "location": [ + -73.986525, + 40.761723 + ] + }, + { + "bearings": [ + 29, + 118, + 209, + 298 + ], + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 16, + "location": [ + -73.986147, + 40.762232 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "West 50th Street" + } + ], + "type": "turn", + "modifier": "right", + "text": "West 50th Street" + }, + "distanceAlongGeometry": 239.711 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto 8th Avenue.", + "modifier": "right", + "bearing_after": 29, + "bearing_before": 299, + "location": [ + -73.987475, + 40.760409 + ] + }, + "speedLimitSign": "mutcd", + "name": "8th Avenue", + "weight_typical": 54.716, + "duration_typical": 53.004, + "duration": 51.323, + "distance": 239.711, + "driving_side": "right", + "weight": 53.036, + "mode": "driving", + "geometry": "qdyvlAdxyblC_C{Aw^eVmBoAiCcBgKkGiMqIcDyBoBkAmBqAy^sV{B{A" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In 500 meters, You will arrive at Dropped Pin #1.", + "announcement": "In 500 meters, You will arrive at Dropped Pin #1.", + "distanceAlongGeometry": 534.378 + }, + { + "ssmlAnnouncement": "You have arrived at Dropped Pin #1.", + "announcement": "You have arrived at Dropped Pin #1.", + "distanceAlongGeometry": 55.556 + } + ], + "intersections": [ + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.986101, + 40.762294 + ], + "geometry_index": 17, + "admin_index": 0, + "weight": 7.525, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 6.472, + "turn_weight": 4, + "duration": 9.997, + "bearings": [ + 30, + 119, + 209, + 298 + ], + "out": 1, + "in": 2, + "entry": [ + true, + true, + false, + false + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 28, + 119, + 208, + 299 + ], + "duration": 15.829, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 16.818, + "geometry_index": 18, + "location": [ + -73.985959, + 40.762234 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 119, + 208, + 299 + ], + "duration": 18.086, + "turn_weight": 0.1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 18.174, + "geometry_index": 20, + "location": [ + -73.985321, + 40.761966 + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 27, + 120, + 207, + 299 + ], + "duration": 1.254, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 2.242, + "geometry_index": 22, + "location": [ + -73.984176, + 40.761484 + ] + }, + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.984098, + 40.76145 + ], + "geometry_index": 23, + "admin_index": 0, + "weight": 5.092, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 3.031, + "turn_weight": 1, + "duration": 7.123, + "bearings": [ + 25, + 118, + 207, + 300 + ], + "out": 1, + "in": 3, + "entry": [ + false, + true, + true, + false + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 28, + 119, + 207, + 298 + ], + "duration": 6.12, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 7.108, + "geometry_index": 25, + "location": [ + -73.983932, + 40.761382 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 119, + 299 + ], + "duration": 7.211, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 7.211, + "geometry_index": 26, + "location": [ + -73.983686, + 40.761278 + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 30, + 119, + 208, + 299 + ], + "duration": 3.608, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 4.596, + "geometry_index": 28, + "location": [ + -73.983395, + 40.761156 + ] + }, + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.98325, + 40.761095 + ], + "geometry_index": 29, + "admin_index": 0, + "weight": 4.255, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 3.012, + "turn_weight": 1, + "duration": 6.267, + "bearings": [ + 28, + 120, + 208, + 299 + ], + "out": 1, + "in": 3, + "entry": [ + false, + true, + true, + false + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 30, + 119, + 208, + 300 + ], + "duration": 25.647, + "turn_weight": 1, + "turn_duration": 0.01, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 26.637, + "geometry_index": 30, + "location": [ + -73.983138, + 40.761047 + ] + }, + { + "entry": [ + true, + false, + false + ], + "in": 2, + "bearings": [ + 119, + 208, + 299 + ], + "duration": 49.014, + "turn_weight": 0.1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 49.102, + "geometry_index": 31, + "location": [ + -73.98225, + 40.760677 + ] + }, + { + "bearings": [ + 32, + 119, + 213, + 299 + ], + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "geometry_index": 33, + "location": [ + -73.980554, + 40.759968 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "You will arrive at Dropped Pin #1" + } + ], + "type": "arrive", + "modifier": "straight", + "text": "You will arrive at Dropped Pin #1" + }, + "distanceAlongGeometry": 547.712 + }, + { + "primary": { + "components": [ + { + "type": "text", + "text": "You have arrived at Dropped Pin #1" + } + ], + "type": "arrive", + "modifier": "straight", + "text": "You have arrived at Dropped Pin #1" + }, + "distanceAlongGeometry": 55.556 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto West 50th Street.", + "modifier": "right", + "bearing_after": 119, + "bearing_before": 29, + "location": [ + -73.986101, + 40.762294 + ] + }, + "speedLimitSign": "mutcd", + "name": "West 50th Street", + "weight_typical": 126.873, + "duration_typical": 127.281, + "duration": 154.1, + "distance": 547.712, + "driving_side": "right", + "weight": 153.692, + "mode": "driving", + "geometry": "kz|vlAhbwblCvB{GlBeGhLu^b@sA~[}cAbA{Cz@uCjAuDnEkNdEyMl@kBxBaH~A_FbVov@pJuZv_@imApBoG" + }, + { + "voiceInstructions": [], + "intersections": [ + { + "bearings": [ + 299 + ], + "entry": [ + true + ], + "in": 0, + "admin_index": 0, + "geometry_index": 34, + "location": [ + -73.980418, + 40.759911 + ] + } + ], + "bannerInstructions": [], + "speedLimitUnit": "mph", + "maneuver": { + "type": "arrive", + "instruction": "You have arrived at Dropped Pin #1.", + "bearing_after": 0, + "bearing_before": 119, + "location": [ + -73.980418, + 40.759911 + ] + }, + "speedLimitSign": "mutcd", + "name": "West 50th Street", + "weight_typical": 0, + "duration_typical": 0, + "duration": 0, + "distance": 0, + "driving_side": "right", + "weight": 0, + "mode": "driving", + "geometry": "mexvlAb_lblC??" + } + ], + "distance": 1023.197, + "summary": "8th Avenue, West 50th Street" + } + ], + "geometry": "}dwvlA|~tblC}AbFw@jCoBtGok@bjBoFzPmB`G_C{Aw^eVmBoAiCcBgKkGiMqIcDyBoBkAmBqAy^sV{B{AvB{GlBeGhLu^b@sA~[}cAbA{Cz@uCjAuDnEkNdEyMl@kBxBaH~A_FbVov@pJuZv_@imApBoG", + "voiceLocale": "en-US", + "refresh_ttl": 21600 + }, + { + "weight_typical": 277.958, + "duration_typical": 241.305, + "weight_name": "auto", + "weight": 296.94, + "duration": 260.288, + "distance": 1024.206, + "legs": [ + { + "via_waypoints": [], + "admins": [ + { + "iso_3166_1_alpha3": "USA", + "iso_3166_1": "US" + } + ], + "annotation": { + "maxspeed": [ + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + null, + null, + 0, + 0, + 0, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 4, + 4, + 4, + 0, + 0, + 0 + ], + "duration": [ + 4.925, + 2.508, + 3.984, + 49.449, + 6.208, + 2.814, + 7.618, + 11.645, + 1.27, + 3.579, + 28.5, + 8.236, + 0.773, + 1.63, + 0.748, + 1.587, + 1.65, + 8.403, + 2.889, + 3.9, + 26.011, + 2.774, + 16.977, + 12.564, + 2.781, + 13.117, + 16.716, + 1.845, + 7.744, + 6.162, + 1.283 + ] + }, + "weight_typical": 277.958, + "duration_typical": 241.305, + "weight": 296.94, + "duration": 260.288, + "steps": [ + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Drive northwest on West 47th Street. Then, in 200 meters, Turn right onto 8th Avenue.", + "announcement": "Drive northwest on West 47th Street. Then, in 200 meters, Turn right onto 8th Avenue.", + "distanceAlongGeometry": 235.775 + }, + { + "ssmlAnnouncement": "Turn right onto 8th Avenue. Then Turn right onto West 48th Street.", + "announcement": "Turn right onto 8th Avenue. Then Turn right onto West 48th Street.", + "distanceAlongGeometry": 78.111 + } + ], + "intersections": [ + { + "entry": [ + true + ], + "bearings": [ + 299 + ], + "duration": 4.925, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 4.925, + "geometry_index": 0, + "location": [ + -73.985023, + 40.759391 + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -73.985137, + 40.759438 + ], + "geometry_index": 1, + "admin_index": 0, + "weight": 2.202, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 0.505, + "turn_weight": 0.2, + "duration": 2.508, + "bearings": [ + 15, + 119, + 298 + ], + "out": 2, + "in": 1, + "entry": [ + false, + false, + true + ] + }, + { + "entry": [ + false, + false, + false, + true + ], + "in": 1, + "bearings": [ + 15, + 118, + 194, + 298 + ], + "duration": 53.434, + "turn_weight": 2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 3, + "weight": 55.432, + "geometry_index": 2, + "location": [ + -73.985207, + 40.759466 + ] + }, + { + "entry": [ + true, + false, + true + ], + "in": 1, + "bearings": [ + 27, + 119, + 299 + ], + "duration": 6.208, + "turn_weight": 0.2, + "turn_duration": 0.005, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 2, + "weight": 6.403, + "geometry_index": 4, + "location": [ + -73.98706, + 40.760234 + ] + }, + { + "bearings": [ + 31, + 119, + 213, + 299 + ], + "entry": [ + false, + false, + false, + true + ], + "in": 1, + "turn_weight": 2, + "turn_duration": 0.005, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 3, + "geometry_index": 5, + "location": [ + -73.987346, + 40.760354 + ] + } + ], + "bannerInstructions": [ + { + "sub": { + "components": [ + { + "type": "text", + "text": "West 48th Street" + } + ], + "type": "turn", + "modifier": "right", + "text": "West 48th Street" + }, + "primary": { + "components": [ + { + "type": "text", + "text": "8th Avenue" + } + ], + "type": "turn", + "modifier": "right", + "text": "8th Avenue" + }, + "distanceAlongGeometry": 235.775 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "depart", + "instruction": "Drive northwest on West 47th Street.", + "bearing_after": 299, + "bearing_before": 0, + "location": [ + -73.985023, + 40.759391 + ] + }, + "speedLimitSign": "mutcd", + "name": "West 47th Street", + "weight_typical": 57.449, + "duration_typical": 53.567, + "duration": 69.888, + "distance": 235.775, + "driving_side": "right", + "weight": 73.77, + "mode": "driving", + "geometry": "}dwvlA|~tblC}AbFw@jCoBtGok@bjBoFzPmB`G" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "Turn right onto West 48th Street.", + "announcement": "Turn right onto West 48th Street.", + "distanceAlongGeometry": 71.111 + } + ], + "intersections": [ + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.987475, + 40.760409 + ], + "geometry_index": 6, + "admin_index": 0, + "weight": 7.46, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 6.158, + "turn_weight": 6, + "duration": 7.618, + "bearings": [ + 29, + 119, + 208, + 298 + ], + "out": 0, + "in": 1, + "entry": [ + true, + false, + false, + true + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "location": [ + -73.987429, + 40.760473 + ], + "geometry_index": 7, + "admin_index": 0, + "weight": 12.633, + "is_urban": true, + "mapbox_streets_v8": { + "class": "secondary" + }, + "turn_duration": 0.012, + "turn_weight": 1, + "duration": 11.645, + "bearings": [ + 29, + 118, + 209, + 298 + ], + "out": 0, + "in": 2, + "entry": [ + true, + false, + false, + false + ] + }, + { + "bearings": [ + 29, + 121, + 209, + 301 + ], + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "turn_weight": 1, + "lanes": [ + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + } + ], + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 8, + "location": [ + -73.987058, + 40.760981 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "West 48th Street" + } + ], + "type": "turn", + "modifier": "right", + "text": "West 48th Street" + }, + "distanceAlongGeometry": 79.728 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto 8th Avenue.", + "modifier": "right", + "bearing_after": 29, + "bearing_before": 299, + "location": [ + -73.987475, + 40.760409 + ] + }, + "speedLimitSign": "mutcd", + "name": "8th Avenue", + "weight_typical": 23.106, + "duration_typical": 21.288, + "duration": 20.533, + "distance": 79.728, + "driving_side": "right", + "weight": 22.351, + "mode": "driving", + "geometry": "qdyvlAdxyblC_C{Aw^eVmBoA" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In 500 meters, Turn left onto 6th Avenue.", + "announcement": "In 500 meters, Turn left onto 6th Avenue.", + "distanceAlongGeometry": 534.376 + }, + { + "ssmlAnnouncement": "Turn left onto 6th Avenue, Avenue of the Americas.", + "announcement": "Turn left onto 6th Avenue, Avenue of the Americas.", + "distanceAlongGeometry": 78.111 + } + ], + "intersections": [ + { + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -73.987018, + 40.761036 + ], + "geometry_index": 9, + "admin_index": 0, + "weight": 10.5, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 1.079, + "turn_weight": 8, + "duration": 3.579, + "bearings": [ + 30, + 119, + 209, + 298 + ], + "out": 1, + "in": 2, + "entry": [ + true, + true, + false, + false + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 31, + 119, + 210, + 299 + ], + "duration": 28.5, + "turn_weight": 2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 30.499, + "geometry_index": 10, + "location": [ + -73.986888, + 40.760982 + ] + }, + { + "entry": [ + true, + true, + false + ], + "in": 2, + "bearings": [ + 31, + 119, + 299 + ], + "duration": 9.01, + "turn_weight": 0.2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 9.208, + "geometry_index": 11, + "location": [ + -73.985411, + 40.76036 + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 15, + 120, + 196, + 299 + ], + "duration": 1.63, + "turn_weight": 2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 3.628, + "geometry_index": 13, + "location": [ + -73.984943, + 40.760165 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 118, + 300 + ], + "duration": 0.748, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 0.748, + "geometry_index": 14, + "location": [ + -73.984859, + 40.760129 + ] + }, + { + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -73.98482, + 40.760113 + ], + "geometry_index": 15, + "admin_index": 0, + "weight": 4.734, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 0.502, + "turn_weight": 2, + "duration": 3.236, + "bearings": [ + 46, + 119, + 158, + 232, + 298 + ], + "out": 1, + "in": 4, + "entry": [ + false, + true, + true, + true, + false + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "right" + ], + "valid": false, + "active": false + } + ], + "location": [ + -73.984694, + 40.76006 + ], + "geometry_index": 17, + "admin_index": 0, + "weight": 10.401, + "is_urban": true, + "mapbox_streets_v8": { + "class": "street" + }, + "turn_duration": 0.002, + "turn_weight": 2, + "duration": 8.403, + "bearings": [ + 18, + 119, + 199, + 299 + ], + "out": 1, + "in": 3, + "entry": [ + false, + true, + false, + false + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "right" + ], + "valid": false, + "active": false + } + ], + "location": [ + -73.984307, + 40.759897 + ], + "geometry_index": 18, + "admin_index": 0, + "weight": 4.887, + "is_urban": true, + "mapbox_streets_v8": { + "class": "street" + }, + "turn_duration": 0.002, + "turn_weight": 2, + "duration": 2.889, + "bearings": [ + 31, + 119, + 210, + 299 + ], + "out": 1, + "in": 3, + "entry": [ + false, + true, + false, + false + ] + }, + { + "lanes": [ + { + "indications": [ + "straight" + ], + "valid_indication": "straight", + "valid": true, + "active": true + }, + { + "indications": [ + "straight" + ], + "valid": false, + "active": false + }, + { + "indications": [ + "right" + ], + "valid": false, + "active": false + } + ], + "mapbox_streets_v8": { + "class": "street" + }, + "location": [ + -73.984174, + 40.759841 + ], + "geometry_index": 19, + "admin_index": 0, + "weight": 6.398, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 0.502, + "turn_weight": 3, + "duration": 3.9, + "bearings": [ + 30, + 119, + 208, + 299 + ], + "out": 1, + "in": 3, + "entry": [ + false, + true, + true, + false + ] + }, + { + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "bearings": [ + 27, + 119, + 207, + 299 + ], + "duration": 26.011, + "turn_weight": 2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 28.009, + "geometry_index": 20, + "location": [ + -73.984057, + 40.759791 + ] + }, + { + "entry": [ + false, + true, + true, + false + ], + "in": 3, + "bearings": [ + 28, + 119, + 208, + 299 + ], + "duration": 19.751, + "turn_weight": 2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "weight": 21.749, + "geometry_index": 21, + "location": [ + -73.983161, + 40.759409 + ] + }, + { + "entry": [ + true, + false + ], + "in": 1, + "bearings": [ + 119, + 299 + ], + "duration": 12.564, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 12.564, + "geometry_index": 23, + "location": [ + -73.982136, + 40.75898 + ] + }, + { + "bearings": [ + 28, + 120, + 208, + 299 + ], + "entry": [ + false, + true, + false, + false + ], + "in": 3, + "turn_weight": 2, + "turn_duration": 0.002, + "mapbox_streets_v8": { + "class": "street" + }, + "is_urban": true, + "admin_index": 0, + "out": 1, + "geometry_index": 24, + "location": [ + -73.981484, + 40.758707 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "6th Avenue" + }, + { + "type": "text", + "text": "/" + }, + { + "type": "text", + "text": "Avenue of the Americas" + } + ], + "type": "turn", + "modifier": "left", + "text": "6th Avenue / Avenue of the Americas" + }, + "distanceAlongGeometry": 547.71 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn right onto West 48th Street.", + "modifier": "right", + "bearing_after": 119, + "bearing_before": 29, + "location": [ + -73.987018, + 40.761036 + ] + }, + "speedLimitSign": "mutcd", + "name": "West 48th Street", + "weight_typical": 150.435, + "duration_typical": 125.333, + "duration": 123.001, + "distance": 547.71, + "driving_side": "right", + "weight": 148.102, + "mode": "driving", + "geometry": "wkzvlAr{xblCjBcGze@i{AbJwY`@oAfAgD^mAh@cB~@wCdIeWnBiGbBiFzV_w@vB_H`Vav@`Pwg@zB}G" + }, + { + "voiceInstructions": [ + { + "ssmlAnnouncement": "In 200 meters, You will arrive at Dropped Pin #1.", + "announcement": "In 200 meters, You will arrive at Dropped Pin #1.", + "distanceAlongGeometry": 147.66 + }, + { + "ssmlAnnouncement": "You have arrived at Dropped Pin #1.", + "announcement": "You have arrived at Dropped Pin #1.", + "distanceAlongGeometry": 55.556 + } + ], + "intersections": [ + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.981341, + 40.758645 + ], + "geometry_index": 25, + "admin_index": 0, + "weight": 17.025, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 11.092, + "turn_weight": 15, + "duration": 13.117, + "bearings": [ + 29, + 120, + 208, + 300 + ], + "out": 0, + "in": 3, + "entry": [ + true, + true, + false, + false + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 29, + 120, + 209, + 298 + ], + "duration": 16.716, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 17.704, + "geometry_index": 26, + "location": [ + -73.981296, + 40.758707 + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 29, + 120, + 209, + 298 + ], + "duration": 1.845, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 2.833, + "geometry_index": 27, + "location": [ + -73.980924, + 40.759218 + ] + }, + { + "mapbox_streets_v8": { + "class": "secondary" + }, + "location": [ + -73.980883, + 40.759274 + ], + "geometry_index": 28, + "admin_index": 0, + "weight": 5.732, + "is_urban": true, + "traffic_signal": true, + "turn_duration": 3.012, + "turn_weight": 1, + "duration": 7.744, + "bearings": [ + 29, + 120, + 209, + 298 + ], + "out": 0, + "in": 2, + "entry": [ + true, + false, + false, + true + ] + }, + { + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "bearings": [ + 29, + 120, + 209, + 300 + ], + "duration": 6.162, + "turn_weight": 1, + "turn_duration": 0.012, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "weight": 7.15, + "geometry_index": 29, + "location": [ + -73.980702, + 40.759522 + ] + }, + { + "bearings": [ + 28, + 120, + 209, + 300 + ], + "entry": [ + true, + false, + false, + false + ], + "in": 2, + "turn_weight": 1, + "turn_duration": 0.01, + "mapbox_streets_v8": { + "class": "secondary" + }, + "is_urban": true, + "admin_index": 0, + "out": 0, + "geometry_index": 30, + "location": [ + -73.980466, + 40.759844 + ] + } + ], + "bannerInstructions": [ + { + "primary": { + "components": [ + { + "type": "text", + "text": "You will arrive at Dropped Pin #1" + } + ], + "type": "arrive", + "modifier": "straight", + "text": "You will arrive at Dropped Pin #1" + }, + "distanceAlongGeometry": 160.993 + }, + { + "primary": { + "components": [ + { + "type": "text", + "text": "You have arrived at Dropped Pin #1" + } + ], + "type": "arrive", + "modifier": "straight", + "text": "You have arrived at Dropped Pin #1" + }, + "distanceAlongGeometry": 55.556 + } + ], + "speedLimitUnit": "mph", + "maneuver": { + "type": "turn", + "instruction": "Turn left onto 6th Avenue/Avenue of the Americas.", + "modifier": "left", + "bearing_after": 29, + "bearing_before": 120, + "location": [ + -73.981341, + 40.758645 + ] + }, + "speedLimitSign": "mutcd", + "name": "6th Avenue; Avenue of the Americas", + "weight_typical": 46.967, + "duration_typical": 41.117, + "duration": 46.867, + "distance": 160.993, + "driving_side": "right", + "weight": 52.717, + "mode": "driving", + "geometry": "ivuvlAxxmblC{ByA}^gVoBqAoNiJcSwMeC_B" + }, + { + "voiceInstructions": [], + "intersections": [ + { + "bearings": [ + 208 + ], + "entry": [ + true + ], + "in": 0, + "admin_index": 0, + "geometry_index": 31, + "location": [ + -73.980418, + 40.759911 + ] + } + ], + "bannerInstructions": [], + "speedLimitUnit": "mph", + "maneuver": { + "type": "arrive", + "instruction": "You have arrived at Dropped Pin #1.", + "bearing_after": 0, + "bearing_before": 28, + "location": [ + -73.980418, + 40.759911 + ] + }, + "speedLimitSign": "mutcd", + "name": "6th Avenue; Avenue of the Americas", + "weight_typical": 0, + "duration_typical": 0, + "duration": 0, + "distance": 0, + "driving_side": "right", + "weight": 0, + "mode": "driving", + "geometry": "mexvlAb_lblC??" + } + ], + "distance": 1024.206, + "summary": "West 47th Street, West 48th Street" + } + ], + "geometry": "}dwvlA|~tblC}AbFw@jCoBtGok@bjBoFzPmB`G_C{Aw^eVmBoAjBcGze@i{AbJwY`@oAfAgD^mAh@cB~@wCdIeWnBiGbBiFzV_w@vB_H`Vav@`Pwg@zB}G{ByA}^gVoBqAoNiJcSwMeC_B", + "voiceLocale": "en-US", + "refresh_ttl": 21600 + } + ], + "waypoints": [ + { + "time_zone": { + "abbreviation": "EDT", + "identifier": "America/New_York", + "offset": "-04:00" + }, + "distance": 0.202, + "name": "West 47th Street", + "location": [ + -73.985023, + 40.759391 + ] + }, + { + "time_zone": { + "abbreviation": "EDT", + "identifier": "America/New_York", + "offset": "-04:00" + }, + "distance": 2.511, + "name": "West 50th Street", + "location": [ + -73.980418, + 40.759911 + ] + } + ], + "code": "Ok", + "uuid": "qwm5vFrFhdXrygmsyXdBwMlRafY1goGte1E89oqgN15Yipdmh0E77g==_eu-west-1" +} diff --git a/Sources/TestHelper/Fixtures/profile-route-refresh.json b/Sources/TestHelper/Fixtures/profile-route-refresh.json new file mode 100644 index 00000000000..171981d7941 --- /dev/null +++ b/Sources/TestHelper/Fixtures/profile-route-refresh.json @@ -0,0 +1,222 @@ +{ + "code": "Ok", + "route": { + "legs": [ + { + "annotation": { + "maxspeed": [ + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + }, + { + "speed": 40, + "unit": "km/h" + } + ], + "congestion_numeric": [ + null, + null, + null, + null, + null, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "duration": [ + 2.814, + 2.508, + 3.984, + 49.449, + 8.276, + 3.75, + 7.618, + 11.645, + 1.27, + 4.589, + 4.451, + 5.257, + 1.886, + 1.29, + 3.917, + 8.37, + 1.03, + 8.715, + 2.078, + 7.999, + 0.678, + 17.408, + 1.254, + 4.87, + 2.253, + 6.12, + 5.868, + 1.343, + 3.608, + 5.182, + 17.102, + 8.543, + 24.137, + 2.633 + ] + } + } + ], + "refresh_ttl": 86282 + } +} \ No newline at end of file diff --git a/Tests/MapboxCoreNavigationIntegrationTests/MapboxCoreNavigationIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/MapboxCoreNavigationIntegrationTests.swift index e40a01ab067..7c71fe6d421 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/MapboxCoreNavigationIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/MapboxCoreNavigationIntegrationTests.swift @@ -668,58 +668,4 @@ class MapboxCoreNavigationIntegrationTests: TestCase { wait(for: [routeControllerProgressExpectation], timeout: waitForInterval) } - - func testRouteRefreshWithDefaultDrivingTrafficProfile() { - RouteControllerProactiveReroutingInterval = 1 - - navigation = MapboxNavigationService( - indexedRouteResponse: indexedRouteResponse, - customRoutingProvider: MapboxRoutingProvider(.offline), - credentials: Fixture.credentials, - locationSource: DummyLocationManager(), - simulating: .never - ) - navigation.router.refreshesRoute = true - navigation.start() - - expectation(forNotification: .routeControllerProgressDidChange, object: navigation.router) { (notification) -> Bool in - print(">>>> progress") - return true - } - - expectation(forNotification: .routeControllerDidRefreshRoute, object: navigation.router) { (notification) -> Bool in - print(">>>> refresh") - return true - } - - expectation(forNotification: .routeControllerDidUpdateAlternatives, object: navigation.router) { (notification) -> Bool in - print(">>>> alternatives") - return true - } - - let simulationLocations = route.shape!.coordinates.simulationLocations - simulationLocations.forEach { - navigation.locationManager(navigation.locationManager, didUpdateLocations: [$0]) - } - - waitForExpectations(timeout: waitForInterval) { XCTAssertNil($0) } - } -} - -extension [CLLocationCoordinate2D] { - var simulationLocations: [CLLocation] { - let now = Date() - return enumerated() - .map { - CLLocation( - coordinate: $0.element, - altitude: -1, - horizontalAccuracy: 10, - verticalAccuracy: -1, - course: -1, - speed: 10, - timestamp: now + $0.offset - ) - } - } } diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift new file mode 100644 index 00000000000..c7ad0affaaa --- /dev/null +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -0,0 +1,131 @@ +import XCTest +import CoreLocation +import MapboxDirections +import Turf +import TestHelper +@testable import MapboxCoreNavigation +import OHHTTPStubs + +class RouteRefreshIntegrationTests: TestCase { + var navigation: MapboxNavigationService! + + override func setUp() { + super.setUp() + HTTPStubs.stubRequests( + passingTest: { request -> Bool in + request.url?.absoluteString.contains("directions-refresh") ?? false + }) { request -> HTTPStubsResponse in + HTTPStubsResponse( + data: Fixture.JSONFromFileNamed(name: "profile-route-refresh"), + statusCode: 200, + headers: ["Content-Type":"application/json"] + ) + } + } + + override func tearDown() { + HTTPStubs.removeAllStubs() + navigation = nil + super.tearDown() + } + + func testRouteRefreshWithDefaultDrivingTrafficProfile() { + RouteControllerProactiveReroutingInterval = 2 + let indexedRouteResponse = RouteResponse.mockedIndexRouteResponse + let locationManager = ReplayLocationManager( + locations: RouteResponse.mockedRoute.simulationLocations + ) + + locationManager.speedMultiplier = 1 + navigation = MapboxNavigationService( + indexedRouteResponse: indexedRouteResponse, + customRoutingProvider: MapboxRoutingProvider(.online), + credentials: Fixture.credentials, + locationSource: locationManager, + simulating: .never + ) + navigation.router.refreshesRoute = true + + expectation(forNotification: .routeControllerProgressDidChange, object: navigation.router) { (notification) -> Bool in + guard + let routeProgress = notification.userInfo?[RouteController.NotificationUserInfoKey.routeProgressKey] as? RouteProgress + else { + return false + } + + guard + let location = notification.userInfo?[RouteController.NotificationUserInfoKey.locationKey] as? CLLocation + else { + return false + } + + print(">>>> prog \(location.coordinate)") + return true + } + + expectation(forNotification: .routeControllerDidRefreshRoute, object: navigation.router) { (notification) -> Bool in + print(">>>> refr") + return true + } + + expectation(forNotification: .routeControllerDidUpdateAlternatives, object: navigation.router) { (notification) -> Bool in + print(">>>> alte") + return true + } + + navigation.start() + locationManager.startUpdatingLocation() + waitForExpectations(timeout: 100) { XCTAssertNil($0) } + } +} + +fileprivate extension Route { + var simulationLocations: [CLLocation] { + shape! + .coordinates + .map { CLLocation(latitude: $0.latitude, longitude: $0.longitude) } + .shiftedToPresent() + .qualified() + } +} + +fileprivate extension NavigationRouteOptions { + static func mockedOptions( + _ profile: ProfileIdentifier = .automobileAvoidingTraffic + ) -> NavigationRouteOptions { + NavigationRouteOptions( + coordinates: [ + .origin, + .destiantion + ], + profileIdentifier: profile + ) + } +} + +fileprivate extension RouteResponse { + static var mockedRoute: Route { + mockedResponse.routes![0] + } + + static var mockedResponse: RouteResponse { + Fixture.routeResponse( + from: "profile-route-original", + options: NavigationRouteOptions.mockedOptions() + ) + } + + static var mockedIndexRouteResponse: IndexedRouteResponse { + IndexedRouteResponse(routeResponse: mockedResponse, routeIndex: 0) + } +} + +fileprivate extension CLLocationCoordinate2D { + static var origin: CLLocationCoordinate2D { + .init(latitude: -73.98778274913309, longitude: 40.76050975068355) + } + + static var destiantion: CLLocationCoordinate2D { + .init(latitude: -73.98039053825985, longitude: 40.75988085727627) + } +} From 1214aab20fe92364ffc5e79eae0d669cfed73128 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 21 Oct 2025 16:58:22 +0300 Subject: [PATCH 03/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index c7ad0affaaa..c1d3acda9b2 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -7,8 +7,6 @@ import TestHelper import OHHTTPStubs class RouteRefreshIntegrationTests: TestCase { - var navigation: MapboxNavigationService! - override func setUp() { super.setUp() HTTPStubs.stubRequests( @@ -25,11 +23,41 @@ class RouteRefreshIntegrationTests: TestCase { override func tearDown() { HTTPStubs.removeAllStubs() - navigation = nil super.tearDown() } func testRouteRefreshWithDefaultDrivingTrafficProfile() { + simulateRoute(with: .automobileAvoidingTraffic) + } + + func testRouteRefreshWithCustomDrivingTrafficProfile() { + simulateRoute(with: .custom) + } + + private func simulateRoute(with profile: ProfileIdentifier) { + let (locationManager, navigation) = navigatorAndLocationManager(with: profile) + expectation( + forNotification: .routeControllerDidRefreshRoute, + object: navigation.router + ) { (notification) -> Bool in + return true + } + + expectation( + forNotification: .routeControllerDidUpdateAlternatives, + object: navigation.router + ) { (notification) -> Bool in + return true + } + + navigation.start() + locationManager.startUpdatingLocation() + waitForExpectations(timeout: .defaultDelay) { XCTAssertNil($0) } + } + + private func navigatorAndLocationManager( + with profile: ProfileIdentifier + ) -> (ReplayLocationManager, MapboxNavigationService) { RouteControllerProactiveReroutingInterval = 2 let indexedRouteResponse = RouteResponse.mockedIndexRouteResponse let locationManager = ReplayLocationManager( @@ -37,7 +65,7 @@ class RouteRefreshIntegrationTests: TestCase { ) locationManager.speedMultiplier = 1 - navigation = MapboxNavigationService( + let navigation = MapboxNavigationService( indexedRouteResponse: indexedRouteResponse, customRoutingProvider: MapboxRoutingProvider(.online), credentials: Fixture.credentials, @@ -45,37 +73,7 @@ class RouteRefreshIntegrationTests: TestCase { simulating: .never ) navigation.router.refreshesRoute = true - - expectation(forNotification: .routeControllerProgressDidChange, object: navigation.router) { (notification) -> Bool in - guard - let routeProgress = notification.userInfo?[RouteController.NotificationUserInfoKey.routeProgressKey] as? RouteProgress - else { - return false - } - - guard - let location = notification.userInfo?[RouteController.NotificationUserInfoKey.locationKey] as? CLLocation - else { - return false - } - - print(">>>> prog \(location.coordinate)") - return true - } - - expectation(forNotification: .routeControllerDidRefreshRoute, object: navigation.router) { (notification) -> Bool in - print(">>>> refr") - return true - } - - expectation(forNotification: .routeControllerDidUpdateAlternatives, object: navigation.router) { (notification) -> Bool in - print(">>>> alte") - return true - } - - navigation.start() - locationManager.startUpdatingLocation() - waitForExpectations(timeout: 100) { XCTAssertNil($0) } + return (locationManager, navigation) } } @@ -129,3 +127,11 @@ fileprivate extension CLLocationCoordinate2D { .init(latitude: -73.98039053825985, longitude: 40.75988085727627) } } + +fileprivate extension TimeInterval { + static let defaultDelay: Self = 5 +} + +fileprivate extension ProfileIdentifier { + static let custom: ProfileIdentifier = .init(rawValue: "custom/driving-traffic") +} From e74f77fecac8f27cf7b46a8d5d476a9887fdb07f Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 21 Oct 2025 17:01:54 +0300 Subject: [PATCH 04/12] [Core] Integration tests in progress --- Package.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Package.swift b/Package.swift index e9d403b3630..ed8953025a9 100644 --- a/Package.swift +++ b/Package.swift @@ -107,8 +107,6 @@ let package = Package( "MapboxCoreNavigation", "TestHelper", "OHHTTPStubs", - "CarPlayTestHelper", - "SnapshotTesting", ], exclude: [ "Info.plist", From 296c31243a37c8b9d9be4002c9b3693c23eafef1 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 21 Oct 2025 17:19:09 +0300 Subject: [PATCH 05/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index c1d3acda9b2..11087e7e527 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -27,22 +27,28 @@ class RouteRefreshIntegrationTests: TestCase { } func testRouteRefreshWithDefaultDrivingTrafficProfile() { - simulateRoute(with: .automobileAvoidingTraffic) + simulateRoute(with: .automobileAvoidingTraffic, shouldRefresh: true) } func testRouteRefreshWithCustomDrivingTrafficProfile() { - simulateRoute(with: .custom) + simulateRoute(with: .custom, shouldRefresh: true) } - private func simulateRoute(with profile: ProfileIdentifier) { + func testRouteRefreshWithWalkingProfile() { + simulateRoute(with: .walking, shouldRefresh: false) + } + + private func simulateRoute(with profile: ProfileIdentifier, shouldRefresh: Bool = true) { let (locationManager, navigation) = navigatorAndLocationManager(with: profile) - expectation( + let refreshExpectation = expectation( forNotification: .routeControllerDidRefreshRoute, object: navigation.router ) { (notification) -> Bool in return true } + refreshExpectation.isInverted = shouldRefresh + expectation( forNotification: .routeControllerDidUpdateAlternatives, object: navigation.router @@ -59,9 +65,10 @@ class RouteRefreshIntegrationTests: TestCase { with profile: ProfileIdentifier ) -> (ReplayLocationManager, MapboxNavigationService) { RouteControllerProactiveReroutingInterval = 2 - let indexedRouteResponse = RouteResponse.mockedIndexRouteResponse + + let indexedRouteResponse = RouteResponse.mockedIndexRouteResponse(profile: profile) let locationManager = ReplayLocationManager( - locations: RouteResponse.mockedRoute.simulationLocations + locations: indexedRouteResponse.routeResponse.routes![0].simulationLocations ) locationManager.speedMultiplier = 1 @@ -72,7 +79,6 @@ class RouteRefreshIntegrationTests: TestCase { locationSource: locationManager, simulating: .never ) - navigation.router.refreshesRoute = true return (locationManager, navigation) } } @@ -89,7 +95,7 @@ fileprivate extension Route { fileprivate extension NavigationRouteOptions { static func mockedOptions( - _ profile: ProfileIdentifier = .automobileAvoidingTraffic + _ profile: ProfileIdentifier ) -> NavigationRouteOptions { NavigationRouteOptions( coordinates: [ @@ -102,19 +108,20 @@ fileprivate extension NavigationRouteOptions { } fileprivate extension RouteResponse { - static var mockedRoute: Route { - mockedResponse.routes![0] - } - - static var mockedResponse: RouteResponse { + static func mockedResponse(profile: ProfileIdentifier) -> RouteResponse { Fixture.routeResponse( from: "profile-route-original", - options: NavigationRouteOptions.mockedOptions() + options: NavigationRouteOptions.mockedOptions(profile) ) } - static var mockedIndexRouteResponse: IndexedRouteResponse { - IndexedRouteResponse(routeResponse: mockedResponse, routeIndex: 0) + static func mockedIndexRouteResponse( + profile: ProfileIdentifier + ) -> IndexedRouteResponse { + IndexedRouteResponse( + routeResponse: mockedResponse(profile: profile), + routeIndex: 0 + ) } } From b6553a73207811fd812be5f1fa997de3929d9afa Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 21 Oct 2025 17:19:58 +0300 Subject: [PATCH 06/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index 11087e7e527..9425df893a1 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -47,7 +47,7 @@ class RouteRefreshIntegrationTests: TestCase { return true } - refreshExpectation.isInverted = shouldRefresh + refreshExpectation.isInverted = !shouldRefresh expectation( forNotification: .routeControllerDidUpdateAlternatives, From ef968d57af7bb9c2e1fb2a37762767d34498be79 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 28 Oct 2025 17:10:35 +0300 Subject: [PATCH 07/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 169 +++++++++++++++++- 1 file changed, 162 insertions(+), 7 deletions(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index 9425df893a1..9236f1efa68 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -27,18 +27,46 @@ class RouteRefreshIntegrationTests: TestCase { } func testRouteRefreshWithDefaultDrivingTrafficProfile() { - simulateRoute(with: .automobileAvoidingTraffic, shouldRefresh: true) + simulateOnRoute(with: .automobileAvoidingTraffic, shouldRefresh: true) } func testRouteRefreshWithCustomDrivingTrafficProfile() { - simulateRoute(with: .custom, shouldRefresh: true) + simulateOnRoute(with: .custom, shouldRefresh: true) } func testRouteRefreshWithWalkingProfile() { - simulateRoute(with: .walking, shouldRefresh: false) + simulateOnRoute(with: .walking, shouldRefresh: false) } - private func simulateRoute(with profile: ProfileIdentifier, shouldRefresh: Bool = true) { + func testReRouteDefaultParametersDefaultDrivingTrafficProfile() { + simulateOffRoute( + with: .mockedOptions(.automobileAvoidingTraffic), + expectationKey: "RerouteDefaultParametersDefaultProfile") { options in + XCTAssert(options.profileIdentifier == .automobileAvoidingTraffic) + } + } + + func testReRouteCustomParametersCustomDrivingTrafficProfile() { + simulateOffRoute( + with: .mockedCustomOptions(.custom), + expectationKey: "RerouteCustomParametersDefaultProfile") { options in + let customOptions = options as! CustomRouteOptions + XCTAssert(customOptions.profileIdentifier == .custom) + XCTAssert(customOptions.urlQueryItems == [.customItem]) + } + } + + func testReRouteCustomParametersDefaultDrivingTrafficProfile() { + simulateOffRoute( + with: .mockedCustomOptions(.automobileAvoidingTraffic), + expectationKey: "RerouteCustomParametersCustomProfile") { options in + let customOptions = options as! CustomRouteOptions + XCTAssert(customOptions.profileIdentifier == .automobileAvoidingTraffic) + XCTAssert(customOptions.urlQueryItems == [.customItem]) + } + } + + private func simulateOnRoute(with profile: ProfileIdentifier, shouldRefresh: Bool = true) { let (locationManager, navigation) = navigatorAndLocationManager(with: profile) let refreshExpectation = expectation( forNotification: .routeControllerDidRefreshRoute, @@ -61,14 +89,38 @@ class RouteRefreshIntegrationTests: TestCase { waitForExpectations(timeout: .defaultDelay) { XCTAssertNil($0) } } + private func simulateOffRoute( + with options: NavigationRouteOptions, + expectationKey: String, + validation: @escaping (NavigationRouteOptions) -> Void + ) { + let response = RouteResponse.mockedIndexRouteResponse(options: options) + let simulationLocations = response.routeResponse.routes![0].simulationOffRouteLocations + let (locationManager, navigation) = navigatorAndLocationManager( + with: response, + simulationLocations: simulationLocations + ) + + let expection = expectation(description: expectationKey) + MapboxRoutingProvider.__testRoutesStub = { (options, completionHandler) in + validation(options as! NavigationRouteOptions) + expection.fulfill() + return nil + } + + navigation.start() + locationManager.startUpdatingLocation() + waitForExpectations(timeout: .defaultDelay) { XCTAssertNil($0) } + } + private func navigatorAndLocationManager( - with profile: ProfileIdentifier + with profile: ProfileIdentifier, ) -> (ReplayLocationManager, MapboxNavigationService) { RouteControllerProactiveReroutingInterval = 2 let indexedRouteResponse = RouteResponse.mockedIndexRouteResponse(profile: profile) let locationManager = ReplayLocationManager( - locations: indexedRouteResponse.routeResponse.routes![0].simulationLocations + locations: indexedRouteResponse.routeResponse.routes![0].simulationOnRouteLocations ) locationManager.speedMultiplier = 1 @@ -81,16 +133,47 @@ class RouteRefreshIntegrationTests: TestCase { ) return (locationManager, navigation) } + + private func navigatorAndLocationManager( + with indexedRouteResponse: IndexedRouteResponse, + simulationLocations: [CLLocation] + ) -> (ReplayLocationManager, MapboxNavigationService) { + RouteControllerProactiveReroutingInterval = 2 + let locationManager = ReplayLocationManager(locations: simulationLocations) + locationManager.speedMultiplier = 5 + let navigation = MapboxNavigationService( + indexedRouteResponse: indexedRouteResponse, + customRoutingProvider: MapboxRoutingProvider(.online), + credentials: Fixture.credentials, + locationSource: locationManager, + simulating: .never + ) + return (locationManager, navigation) + } } fileprivate extension Route { - var simulationLocations: [CLLocation] { + var simulationOnRouteLocations: [CLLocation] { shape! .coordinates .map { CLLocation(latitude: $0.latitude, longitude: $0.longitude) } .shiftedToPresent() .qualified() } + + var simulationOffRouteLocations: [CLLocation] { + let stepCoordiantes = legs[0].steps[0].shape!.coordinates + let stepFirstLocation = stepCoordiantes.first! + let stepLastLocation = stepCoordiantes.last! + let stepDirection = stepFirstLocation.direction(to: stepLastLocation) + + let offRouteCoordiantes = [20, 30, 40].map { stepLastLocation.coordinate(at: $0, facing: stepDirection) } + let coordinates = stepCoordiantes + offRouteCoordiantes + return coordinates + .map { CLLocation(latitude: $0.latitude, longitude: $0.longitude) } + .shiftedToPresent() + .qualified() + } } fileprivate extension NavigationRouteOptions { @@ -105,6 +188,20 @@ fileprivate extension NavigationRouteOptions { profileIdentifier: profile ) } + + static func mockedCustomOptions( + _ profile: ProfileIdentifier + ) -> NavigationRouteOptions { + CustomRouteOptions( + waypoints: [ + .init(coordinate: .origin), + .init(coordinate: .destiantion), + ], + profileIdentifier: profile, + customParameters: [.customItem] + ) + } + } fileprivate extension RouteResponse { @@ -123,6 +220,22 @@ fileprivate extension RouteResponse { routeIndex: 0 ) } + + static func mockedResponse(options: NavigationRouteOptions) -> RouteResponse { + Fixture.routeResponse( + from: "profile-route-original", + options: options + ) + } + + static func mockedIndexRouteResponse( + options: NavigationRouteOptions + ) -> IndexedRouteResponse { + IndexedRouteResponse( + routeResponse: mockedResponse(options: options), + routeIndex: 0 + ) + } } fileprivate extension CLLocationCoordinate2D { @@ -142,3 +255,45 @@ fileprivate extension TimeInterval { fileprivate extension ProfileIdentifier { static let custom: ProfileIdentifier = .init(rawValue: "custom/driving-traffic") } + +fileprivate extension URLQueryItem { + static let customItem: URLQueryItem = .init(name: "foo", value: "bar") +} + +fileprivate final class CustomRouteOptions: NavigationRouteOptions { + var customParameters: [URLQueryItem] + + init( + waypoints: [Waypoint], + profileIdentifier: ProfileIdentifier? = nil, + customParameters: [URLQueryItem] = [] + ) { + self.customParameters = customParameters + + super.init(waypoints: waypoints, profileIdentifier: profileIdentifier) + } + + required init( + waypoints: [Waypoint], + profileIdentifier: ProfileIdentifier? = nil, + queryItems: [URLQueryItem]? = nil + ) { + self.customParameters = [] + super.init( + waypoints: waypoints, + profileIdentifier: profileIdentifier, + queryItems: queryItems + ) + } + + required init(from decoder: any Decoder) throws { + self.customParameters = [] + try super.init(from: decoder) + } + + override var urlQueryItems: [URLQueryItem] { + var combined = super.urlQueryItems + combined.append(contentsOf: customParameters) + return combined + } +} From c6c2d32817e062c898bd7bc53049b8f1274dce24 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 28 Oct 2025 18:23:34 +0300 Subject: [PATCH 08/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index 9236f1efa68..8d98b571048 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -23,6 +23,7 @@ class RouteRefreshIntegrationTests: TestCase { override func tearDown() { HTTPStubs.removeAllStubs() + MapboxRoutingProvider.__testRoutesStub = nil super.tearDown() } @@ -52,7 +53,7 @@ class RouteRefreshIntegrationTests: TestCase { expectationKey: "RerouteCustomParametersDefaultProfile") { options in let customOptions = options as! CustomRouteOptions XCTAssert(customOptions.profileIdentifier == .custom) - XCTAssert(customOptions.urlQueryItems == [.customItem]) + XCTAssert(customOptions.urlQueryItems.contains(.customItem)) } } @@ -62,7 +63,7 @@ class RouteRefreshIntegrationTests: TestCase { expectationKey: "RerouteCustomParametersCustomProfile") { options in let customOptions = options as! CustomRouteOptions XCTAssert(customOptions.profileIdentifier == .automobileAvoidingTraffic) - XCTAssert(customOptions.urlQueryItems == [.customItem]) + XCTAssert(customOptions.urlQueryItems.contains(.customItem)) } } From 4e19e1e359a8c10b554c2a7fefd11f64d7b88d12 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Wed, 29 Oct 2025 15:20:42 +0300 Subject: [PATCH 09/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index 8d98b571048..f12df1e0384 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -279,7 +279,8 @@ fileprivate final class CustomRouteOptions: NavigationRouteOptions { profileIdentifier: ProfileIdentifier? = nil, queryItems: [URLQueryItem]? = nil ) { - self.customParameters = [] + let mappedUrlItem = queryItems!.first(where: { $0 == .customItem })! + self.customParameters = [mappedUrlItem] super.init( waypoints: waypoints, profileIdentifier: profileIdentifier, From cd9c4e2fb43fb94a0067be253644b92f146324ed Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Fri, 31 Oct 2025 14:31:22 +0300 Subject: [PATCH 10/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 44 ++++++------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index f12df1e0384..4caf6e66b0a 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -28,19 +28,19 @@ class RouteRefreshIntegrationTests: TestCase { } func testRouteRefreshWithDefaultDrivingTrafficProfile() { - simulateOnRoute(with: .automobileAvoidingTraffic, shouldRefresh: true) + simulateAndTestOnRoute(with: .automobileAvoidingTraffic, shouldRefresh: true) } func testRouteRefreshWithCustomDrivingTrafficProfile() { - simulateOnRoute(with: .custom, shouldRefresh: true) + simulateAndTestOnRoute(with: .custom, shouldRefresh: true) } func testRouteRefreshWithWalkingProfile() { - simulateOnRoute(with: .walking, shouldRefresh: false) + simulateAndTestOnRoute(with: .walking, shouldRefresh: false) } func testReRouteDefaultParametersDefaultDrivingTrafficProfile() { - simulateOffRoute( + simulateAndTestOffRoute( with: .mockedOptions(.automobileAvoidingTraffic), expectationKey: "RerouteDefaultParametersDefaultProfile") { options in XCTAssert(options.profileIdentifier == .automobileAvoidingTraffic) @@ -48,7 +48,7 @@ class RouteRefreshIntegrationTests: TestCase { } func testReRouteCustomParametersCustomDrivingTrafficProfile() { - simulateOffRoute( + simulateAndTestOffRoute( with: .mockedCustomOptions(.custom), expectationKey: "RerouteCustomParametersDefaultProfile") { options in let customOptions = options as! CustomRouteOptions @@ -58,7 +58,7 @@ class RouteRefreshIntegrationTests: TestCase { } func testReRouteCustomParametersDefaultDrivingTrafficProfile() { - simulateOffRoute( + simulateAndTestOffRoute( with: .mockedCustomOptions(.automobileAvoidingTraffic), expectationKey: "RerouteCustomParametersCustomProfile") { options in let customOptions = options as! CustomRouteOptions @@ -67,8 +67,13 @@ class RouteRefreshIntegrationTests: TestCase { } } - private func simulateOnRoute(with profile: ProfileIdentifier, shouldRefresh: Bool = true) { - let (locationManager, navigation) = navigatorAndLocationManager(with: profile) + private func simulateAndTestOnRoute(with profile: ProfileIdentifier, shouldRefresh: Bool = true) { + let indexedRouteResponse = RouteResponse.mockedIndexRouteResponse(profile: profile) + let simulationLocations = indexedRouteResponse.routeResponse.routes![0].simulationOnRouteLocations + let (locationManager, navigation) = navigatorAndLocationManager( + with: indexedRouteResponse, + simulationLocations: simulationLocations + ) let refreshExpectation = expectation( forNotification: .routeControllerDidRefreshRoute, object: navigation.router @@ -90,7 +95,7 @@ class RouteRefreshIntegrationTests: TestCase { waitForExpectations(timeout: .defaultDelay) { XCTAssertNil($0) } } - private func simulateOffRoute( + private func simulateAndTestOffRoute( with options: NavigationRouteOptions, expectationKey: String, validation: @escaping (NavigationRouteOptions) -> Void @@ -114,27 +119,6 @@ class RouteRefreshIntegrationTests: TestCase { waitForExpectations(timeout: .defaultDelay) { XCTAssertNil($0) } } - private func navigatorAndLocationManager( - with profile: ProfileIdentifier, - ) -> (ReplayLocationManager, MapboxNavigationService) { - RouteControllerProactiveReroutingInterval = 2 - - let indexedRouteResponse = RouteResponse.mockedIndexRouteResponse(profile: profile) - let locationManager = ReplayLocationManager( - locations: indexedRouteResponse.routeResponse.routes![0].simulationOnRouteLocations - ) - - locationManager.speedMultiplier = 1 - let navigation = MapboxNavigationService( - indexedRouteResponse: indexedRouteResponse, - customRoutingProvider: MapboxRoutingProvider(.online), - credentials: Fixture.credentials, - locationSource: locationManager, - simulating: .never - ) - return (locationManager, navigation) - } - private func navigatorAndLocationManager( with indexedRouteResponse: IndexedRouteResponse, simulationLocations: [CLLocation] From e330df0d820696ae607db5e894e90b66f6974a61 Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 4 Nov 2025 13:19:09 +0300 Subject: [PATCH 11/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index 4caf6e66b0a..5540cfb9e25 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -32,7 +32,11 @@ class RouteRefreshIntegrationTests: TestCase { } func testRouteRefreshWithCustomDrivingTrafficProfile() { - simulateAndTestOnRoute(with: .custom, shouldRefresh: true) + simulateAndTestOnRoute(with: .customDrivingTraffic, shouldRefresh: true) + } + + func testRouteRefreshWithDefaultAutomobileProfile() { + simulateAndTestOnRoute(with: .automobile, shouldRefresh: false) } func testRouteRefreshWithWalkingProfile() { @@ -49,10 +53,10 @@ class RouteRefreshIntegrationTests: TestCase { func testReRouteCustomParametersCustomDrivingTrafficProfile() { simulateAndTestOffRoute( - with: .mockedCustomOptions(.custom), + with: .mockedCustomOptions(.customDrivingTraffic), expectationKey: "RerouteCustomParametersDefaultProfile") { options in let customOptions = options as! CustomRouteOptions - XCTAssert(customOptions.profileIdentifier == .custom) + XCTAssert(customOptions.profileIdentifier == .customDrivingTraffic) XCTAssert(customOptions.urlQueryItems.contains(.customItem)) } } @@ -238,7 +242,7 @@ fileprivate extension TimeInterval { } fileprivate extension ProfileIdentifier { - static let custom: ProfileIdentifier = .init(rawValue: "custom/driving-traffic") + static let customDrivingTraffic: ProfileIdentifier = .init(rawValue: "custom/driving-traffic") } fileprivate extension URLQueryItem { @@ -263,8 +267,8 @@ fileprivate final class CustomRouteOptions: NavigationRouteOptions { profileIdentifier: ProfileIdentifier? = nil, queryItems: [URLQueryItem]? = nil ) { - let mappedUrlItem = queryItems!.first(where: { $0 == .customItem })! - self.customParameters = [mappedUrlItem] + let mappedUrlItem = queryItems?.first(where: { $0 == .customItem }) + self.customParameters = mappedUrlItem.map { [$0] } ?? [] super.init( waypoints: waypoints, profileIdentifier: profileIdentifier, From 9cb39f5b54a9bcb31aa01fa895dbf84869f7645c Mon Sep 17 00:00:00 2001 From: Artem Stepuk Date: Tue, 4 Nov 2025 14:16:35 +0300 Subject: [PATCH 12/12] [Core] Integration tests in progress --- .../RouteRefreshIntegrationTests.swift | 54 ++++--------------- 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift index 5540cfb9e25..d38d89c8cd3 100644 --- a/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift +++ b/Tests/MapboxCoreNavigationIntegrationTests/RouteRefreshIntegrationTests.swift @@ -2,7 +2,7 @@ import XCTest import CoreLocation import MapboxDirections import Turf -import TestHelper +@testable import TestHelper @testable import MapboxCoreNavigation import OHHTTPStubs @@ -181,16 +181,16 @@ fileprivate extension NavigationRouteOptions { static func mockedCustomOptions( _ profile: ProfileIdentifier ) -> NavigationRouteOptions { - CustomRouteOptions( + let options = CustomRouteOptions( waypoints: [ .init(coordinate: .origin), .init(coordinate: .destiantion), ], - profileIdentifier: profile, - customParameters: [.customItem] + profileIdentifier: profile ) + options.custom = URLQueryItem.customItem.value + return options } - } fileprivate extension RouteResponse { @@ -246,44 +246,8 @@ fileprivate extension ProfileIdentifier { } fileprivate extension URLQueryItem { - static let customItem: URLQueryItem = .init(name: "foo", value: "bar") -} - -fileprivate final class CustomRouteOptions: NavigationRouteOptions { - var customParameters: [URLQueryItem] - - init( - waypoints: [Waypoint], - profileIdentifier: ProfileIdentifier? = nil, - customParameters: [URLQueryItem] = [] - ) { - self.customParameters = customParameters - - super.init(waypoints: waypoints, profileIdentifier: profileIdentifier) - } - - required init( - waypoints: [Waypoint], - profileIdentifier: ProfileIdentifier? = nil, - queryItems: [URLQueryItem]? = nil - ) { - let mappedUrlItem = queryItems?.first(where: { $0 == .customItem }) - self.customParameters = mappedUrlItem.map { [$0] } ?? [] - super.init( - waypoints: waypoints, - profileIdentifier: profileIdentifier, - queryItems: queryItems - ) - } - - required init(from decoder: any Decoder) throws { - self.customParameters = [] - try super.init(from: decoder) - } - - override var urlQueryItems: [URLQueryItem] { - var combined = super.urlQueryItems - combined.append(contentsOf: customParameters) - return combined - } + static let customItem: URLQueryItem = .init( + name: CustomRouteOptions.CodingKeys.custom.stringValue, + value: "foobar" + ) }