diff --git a/.circleci/config.yml b/.circleci/config.yml index c8fe50468dd..41344dd0fdd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -115,7 +115,7 @@ jobs: condition: << parameters.lint >> steps: - run: find . -path '*.podspec' -exec perl -pi -e 's/.+\.social_media_url.+//' {} \; - - run: pod lib lint MapboxCoreNavigation.podspec + - run: pod lib lint MapboxCoreNavigation.podspec --skip-import-validation - *save-cache-podmaster - *save-cache-cocoapods diff --git a/Cartfile b/Cartfile index 9c8d5c5585a..03cb911149e 100644 --- a/Cartfile +++ b/Cartfile @@ -1,6 +1,6 @@ -binary "https://www.mapbox.com/ios-sdk/MapboxAccounts.json" ~> 2.3 +binary "https://www.mapbox.com/ios-sdk/MapboxAccounts.json" ~> 2.3.0 binary "https://mapbox-gl-native-ios.s3.amazonaws.com/public/internal/Mapbox-iOS-SDK.json" == 5.9.1000 -binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" ~> 9.2.1 +binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" ~> 12.0.3 github "mapbox/mapbox-directions-swift" ~> 0.32 github "mapbox/turf-swift" ~> 0.5 github "mapbox/mapbox-events-ios" ~> 0.10 diff --git a/Cartfile.resolved b/Cartfile.resolved index e8a417afb5d..4d892b387d4 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,6 +1,6 @@ binary "https://mapbox-gl-native-ios.s3.amazonaws.com/public/internal/Mapbox-iOS-SDK.json" "5.9.1000" binary "https://www.mapbox.com/ios-sdk/MapboxAccounts.json" "2.3.0" -binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" "9.2.1" +binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" "12.0.3" github "CedarBDD/Cedar" "v1.0" github "Quick/Nimble" "v8.1.1" github "Quick/Quick" "v2.2.1" diff --git a/MapboxCoreNavigation.podspec b/MapboxCoreNavigation.podspec index 5b785054cd1..110e75377ef 100644 --- a/MapboxCoreNavigation.podspec +++ b/MapboxCoreNavigation.podspec @@ -40,7 +40,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.module_name = "MapboxCoreNavigation" - s.dependency "MapboxNavigationNative", "~> 9.2.1" + s.dependency "MapboxNavigationNative", "~> 12.0.3" s.dependency "MapboxAccounts", "~> 2.3.0" s.dependency "MapboxDirections", "~> 0.32.0" s.dependency "MapboxMobileEvents", "~> 0.10.2" # Always pin to a patch release if pre-1.0 @@ -48,4 +48,9 @@ Pod::Spec.new do |s| s.swift_version = "5.0" + # MapboxNavigationNative is built for arm64 and x86_64 architectures (i386 and armv7 are not present), so only 64-bit architecture is checked. + s.pod_target_xcconfig = { + 'ARCHS[sdk=iphonesimulator*]' => '$(ARCHS_STANDARD_64_BIT)' + } + end diff --git a/MapboxCoreNavigation/OfflineDirections.swift b/MapboxCoreNavigation/OfflineDirections.swift index d45b06d5101..50bcecdf62f 100644 --- a/MapboxCoreNavigation/OfflineDirections.swift +++ b/MapboxCoreNavigation/OfflineDirections.swift @@ -151,8 +151,14 @@ public class NavigationDirections: Directions { let tilePath = filePathURL.path let outputPath = outputDirectoryURL.path - - let numberOfTiles = Navigator().unpackTiles(forPackedTilesPath: tilePath, outputDirectory: outputPath) + + let navigator: Navigator = { + let settingsProfile = SettingsProfile(application: ProfileApplication.kMobile, + platform: ProfilePlatform.KIOS) + return Navigator(profile: settingsProfile, customConfig: "") + }() + + let numberOfTiles = navigator.unpackTiles(forPackedTilesPath: tilePath, outputDirectory: outputPath) // Report 100% progress progressHandler?(totalPackedBytes, totalPackedBytes) @@ -235,7 +241,9 @@ public class NavigationDirections: Directions { "The offline navigator must be accessed from the dedicated serial queue") if _navigator == nil { - self._navigator = Navigator() + let settingsProfile = SettingsProfile(application: ProfileApplication.kMobile, + platform: ProfilePlatform.KIOS) + self._navigator = Navigator(profile: settingsProfile, customConfig: "") } return _navigator diff --git a/MapboxCoreNavigation/RouteController.swift b/MapboxCoreNavigation/RouteController.swift index 7101098518b..defeba9942b 100644 --- a/MapboxCoreNavigation/RouteController.swift +++ b/MapboxCoreNavigation/RouteController.swift @@ -20,8 +20,12 @@ open class RouteController: NSObject { public static let shouldPreventReroutesWhenArrivingAtWaypoint: Bool = true public static let shouldDisableBatteryMonitoring: Bool = true } - - let navigator = Navigator() + + lazy var navigator: Navigator = { + let settingsProfile = SettingsProfile(application: ProfileApplication.kMobile, + platform: ProfilePlatform.KIOS) + return Navigator(profile: settingsProfile, customConfig: "") + }() public var route: Route { get { @@ -316,7 +320,7 @@ open class RouteController: NSObject { NotificationUserInfoKey.routeProgressKey: progress, NotificationUserInfoKey.locationKey: location, //guaranteed value NotificationUserInfoKey.rawLocationKey: rawLocation, //raw - ]) + ]) } } diff --git a/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj b/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj index a0e04a41633..04fdd70f7e7 100644 --- a/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj +++ b/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/PodInstall.xcodeproj/project.pbxproj @@ -244,7 +244,7 @@ "${BUILT_PRODUCTS_DIR}/MapboxNavigation/MapboxNavigation.framework", "${PODS_ROOT}/MapboxNavigationNative/MapboxNavigationNative.framework", "${PODS_ROOT}/MapboxNavigationNative/MapboxNavigationNative.framework.dSYM", - "${PODS_ROOT}/MapboxNavigationNative/9E1DC337-5279-3C31-9D5D-D8BA2ECDEB2C.bcsymbolmap", + "${PODS_ROOT}/MapboxNavigationNative/6F62B195-8538-3DD3-BDCB-E7AAD8FD1595.bcsymbolmap", "${BUILT_PRODUCTS_DIR}/MapboxSpeech/MapboxSpeech.framework", "${BUILT_PRODUCTS_DIR}/Polyline/Polyline.framework", "${BUILT_PRODUCTS_DIR}/Solar/Solar.framework", @@ -265,7 +265,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxNavigation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxNavigationNative.framework", "${DWARF_DSYM_FOLDER_PATH}/MapboxNavigationNative.framework.dSYM", - "${BUILT_PRODUCTS_DIR}/9E1DC337-5279-3C31-9D5D-D8BA2ECDEB2C.bcsymbolmap", + "${BUILT_PRODUCTS_DIR}/6F62B195-8538-3DD3-BDCB-E7AAD8FD1595.bcsymbolmap", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxSpeech.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Polyline.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Solar.framework", diff --git a/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/Podfile.lock b/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/Podfile.lock index 6aa1d9cba09..2387340d3fc 100644 --- a/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/Podfile.lock +++ b/MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/Podfile.lock @@ -6,7 +6,7 @@ PODS: - MapboxAccounts (~> 2.3.0) - MapboxDirections (~> 0.32.0) - MapboxMobileEvents (~> 0.10.2) - - MapboxNavigationNative (= 9.2.1) + - MapboxNavigationNative (~> 12.0.3) - Turf (~> 0.5.0) - MapboxDirections (0.32.0): - Polyline (~> 4.2) @@ -18,7 +18,7 @@ PODS: - MapboxMobileEvents (~> 0.10.2) - MapboxSpeech (~> 0.3.0) - Solar (~> 2.1) - - MapboxNavigationNative (9.2.1) + - MapboxNavigationNative (12.0.3) - MapboxSpeech (0.3.0) - Polyline (4.2.1) - Solar (2.1.0) @@ -49,11 +49,11 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Mapbox-iOS-SDK: a5915700ec84bc1a7f8b3e746d474789e35b7956 MapboxAccounts: 84abfdde95d9dc483f604c1b0fe1861edf691ce7 - MapboxCoreNavigation: 3aab7ff9c1d56f0da6c2f8656162e90e918ba281 + MapboxCoreNavigation: 677802d7786c5795b960269618756e27f07d4217 MapboxDirections: 7f36b3e9ef6a53fc997c114a341ab4da721756bd MapboxMobileEvents: 2bc0ca2eedb627b73cf403258dce2b2fa98074a6 MapboxNavigation: 42bae50b0381dce317c85884ba0de38fc68a4814 - MapboxNavigationNative: 97104806edeb30c77f96f81a255999a4d52f5451 + MapboxNavigationNative: b09849eda5d4bc842ba677f75d50a7f913ce9e1b MapboxSpeech: 403415e932e084cf290b9d55c49ab7ea210b9595 Polyline: 0e9890790292741c8186201a536b6bb6a78d02dd Solar: 2dc6e7cc39186cb0c8228fa08df76fb50c7d8f24 diff --git a/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift b/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift index 2273327e602..4b2be9c81b6 100644 --- a/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift +++ b/MapboxCoreNavigationTests/MapboxCoreNavigationTests.swift @@ -4,16 +4,16 @@ import Turf import TestHelper @testable import MapboxCoreNavigation -let jsonFileName = "routeWithInstructions" +let routeInstructionsJSONFileName = "routeWithInstructions" var routeOptions: NavigationRouteOptions { let from = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.795042, longitude: -122.413165)) let to = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 37.7727, longitude: -122.433378)) return NavigationRouteOptions(waypoints: [from, to]) } -let response = Fixture.routeResponse(from: jsonFileName, options: routeOptions) +let response = Fixture.routeResponse(from: routeInstructionsJSONFileName, options: routeOptions) let directions = DirectionsSpy() let route: Route = { - return Fixture.route(from: jsonFileName, options: routeOptions) + return Fixture.route(from: routeInstructionsJSONFileName, options: routeOptions) }() let waitForInterval: TimeInterval = 5 @@ -22,20 +22,25 @@ class MapboxCoreNavigationTests: XCTestCase { var navigation: MapboxNavigationService! func testNavigationNotificationsInfoDict() { - navigation = MapboxNavigationService(route: route, routeOptions: routeOptions, directions: directions, simulating: .never) + // Given + navigation = MapboxNavigationService(route: route, + routeOptions: routeOptions, + directions: directions, + simulating: .never) let now = Date() - let steps = route.legs.first!.steps - let coordinates = steps[2].shape!.coordinates + steps[3].shape!.coordinates + let routeSteps = route.legs.first!.steps + let coordinates = routeSteps[2].shape!.coordinates + routeSteps[3].shape!.coordinates let locations = coordinates.enumerated().map { CLLocation(coordinate: $0.element, altitude: -1, horizontalAccuracy: 10, verticalAccuracy: -1, course: -1, speed: 10, timestamp: now + $0.offset) } - let spokenTest = expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, object: navigation.router) { (note) -> Bool in + let didPassSpokenTestExpectation = expectation(forNotification: .routeControllerDidPassSpokenInstructionPoint, + object: navigation.router) { (note) -> Bool in return note.userInfo!.count == 2 } - spokenTest.expectationDescription = "Spoken Instruction notification expected to have user info dictionary with two values" + didPassSpokenTestExpectation.expectationDescription = "Spoken Instruction notification expected to have user info dictionary with two values" navigation.start() @@ -47,7 +52,7 @@ class MapboxCoreNavigationTests: XCTestCase { navigation.locationManager(navigation.locationManager, didUpdateLocations: [location]) - wait(for: [spokenTest], timeout: waitForInterval) + wait(for: [didPassSpokenTestExpectation], timeout: waitForInterval) } func testDepart() { @@ -127,39 +132,70 @@ class MapboxCoreNavigationTests: XCTestCase { } func testShouldReroute() { - let coordinates = route.legs[0].steps[1].shape!.coordinates + // Given let now = Date() - let locations = coordinates.enumerated().map { CLLocation(coordinate: $0.element, - altitude: -1, horizontalAccuracy: 10, verticalAccuracy: -1, course: -1, speed: 10, timestamp: now + $0.offset) } - - let offRouteCoordinates = [[-122.41765, 37.79095],[-122.41830,37.79087],[-122.41907,37.79079],[-122.41960,37.79073]] - .map { CLLocationCoordinate2D(latitude: $0[1], longitude: $0[0]) } - + let coordinates = route.legs[0].steps[1].shape!.coordinates + let locations = coordinates.enumerated().map { + CLLocation(coordinate: $0.element, + altitude: -1, + horizontalAccuracy: 10, + verticalAccuracy: -1, + course: -1, + speed: 10, + timestamp: now + $0.offset) + } + + let offRouteCoordinates = [ + [-122.41765, 37.79095], + [-122.41830,37.79087], + [-122.41907,37.79079], + [-122.41960,37.79073], + ].map { CLLocationCoordinate2D(latitude: $0[1], longitude: $0[0]) } + let offRouteLocations = offRouteCoordinates.enumerated().map { - CLLocation(coordinate: $0.element, altitude: -1, horizontalAccuracy: 10, - verticalAccuracy: -1, course: -1, speed: 10, + CLLocation(coordinate: $0.element, + altitude: -1, + horizontalAccuracy: 10, + verticalAccuracy: -1, + course: -1, + speed: 10, timestamp: now + locations.count + $0.offset) } - - let locationManager = ReplayLocationManager(locations: locations + offRouteLocations) - navigation = MapboxNavigationService(route: route, routeOptions: routeOptions, directions: directions, locationSource: locationManager, simulating: .never) - expectation(forNotification: .routeControllerWillReroute, object: navigation.router) { (notification) -> Bool in + + let allLocations = locations + offRouteLocations + + let locationManager = ReplayLocationManager(locations: allLocations) + navigation = MapboxNavigationService(route: route, + routeOptions: routeOptions, + directions: directions, + locationSource: locationManager, + simulating: .never) + + let _ = expectation(forNotification: .routeControllerWillReroute, + object: navigation.router) { (notification) -> Bool in XCTAssertEqual(notification.userInfo?.count, 1) - - let location = notification.userInfo![RouteController.NotificationUserInfoKey.locationKey] as? CLLocation - return location?.coordinate == offRouteLocations[1].coordinate + + let locationKey = RouteController.NotificationUserInfoKey.locationKey + let locationFromNotification = notification.userInfo![locationKey] as? CLLocation + XCTAssertEqual(locationFromNotification?.coordinate, + offRouteLocations[2].coordinate) + + return true } - + + // When navigation.start() - - (locations + offRouteLocations).forEach { - navigation.router!.locationManager!(navigation.locationManager, didUpdateLocations: [$0]) + allLocations.forEach { + navigation.router!.locationManager!(navigation.locationManager, + didUpdateLocations: [$0]) } - + + // Then waitForExpectations(timeout: waitForInterval) { (error) in XCTAssertNil(error) } } + func testArrive() { let now = Date() diff --git a/MapboxCoreNavigationTests/NavigationServiceTests.swift b/MapboxCoreNavigationTests/NavigationServiceTests.swift index b133a363e3a..c2e782175a1 100644 --- a/MapboxCoreNavigationTests/NavigationServiceTests.swift +++ b/MapboxCoreNavigationTests/NavigationServiceTests.swift @@ -8,37 +8,66 @@ import os.log fileprivate let mbTestHeading: CLLocationDirection = 50 +typealias RouteLocations = (firstLocation: CLLocation, penultimateLocation: CLLocation, lastLocation: CLLocation) + class NavigationServiceTests: XCTestCase { var eventsManagerSpy: NavigationEventsManagerSpy! let directionsClientSpy = DirectionsSpy() let delegate = NavigationServiceDelegateSpy() - typealias RouteLocations = (firstLocation: CLLocation, penultimateLocation: CLLocation, lastLocation: CLLocation) + let initialRoute = Fixture.route(from: routeInstructionsJSONFileName, options: routeOptions) + let alternateRoute = Fixture.route(from: routeInstructionsJSONFileName, options: routeOptions) lazy var dependencies: (navigationService: NavigationService, routeLocations: RouteLocations) = { - let navigationService = MapboxNavigationService(route: initialRoute, routeOptions: routeOptions, directions: directionsClientSpy, eventsManagerType: NavigationEventsManagerSpy.self, simulating: .never) + let navigationService = MapboxNavigationService(route: initialRoute, + routeOptions: routeOptions, + directions: directionsClientSpy, + eventsManagerType: NavigationEventsManagerSpy.self, + simulating: .never) navigationService.delegate = delegate let legProgress: RouteLegProgress = navigationService.router.routeProgress.currentLegProgress - let firstCoord = navigationService.router.routeProgress.nearbyShape.coordinates.first! - let firstLocation = CLLocation(coordinate: firstCoord, altitude: 5, horizontalAccuracy: 10, verticalAccuracy: 5, course: 20, speed: 4, timestamp: Date()) + let firstCoordinate = navigationService.router.routeProgress.nearbyShape.coordinates.first! + let firstLocation = CLLocation(coordinate: firstCoordinate, + altitude: 5, + horizontalAccuracy: 10, + verticalAccuracy: 5, + course: 20, + speed: 4, + timestamp: Date()) let remainingSteps = legProgress.remainingSteps - let penultimateCoord = legProgress.remainingSteps[4].shape!.coordinates.first! - let penultimateLocation = CLLocation(coordinate: penultimateCoord, altitude: 5, horizontalAccuracy: 10, verticalAccuracy: 5, course: 20, speed: 4, timestamp: Date()) - - let lastCoord = legProgress.remainingSteps.last!.shape!.coordinates.first! - let lastLocation = CLLocation(coordinate: lastCoord, altitude: 5, horizontalAccuracy: 10, verticalAccuracy: 5, course: 20, speed: 4, timestamp: Date()) + let penultimateCoordinate = remainingSteps[4].shape!.coordinates.first! + let penultimateLocation = CLLocation(coordinate: penultimateCoordinate, + altitude: 5, + horizontalAccuracy: 10, + verticalAccuracy: 5, + course: 20, + speed: 4, + timestamp: Date()) + + let lastCoordinate = remainingSteps.last!.shape!.coordinates.first! + let lastLocation = CLLocation(coordinate: lastCoordinate, + altitude: 5, + horizontalAccuracy: 10, + verticalAccuracy: 5, + course: 20, + speed: 4, + timestamp: Date()) let routeLocations = RouteLocations(firstLocation, penultimateLocation, lastLocation) return (navigationService: navigationService, routeLocations: routeLocations) }() - let initialRoute = Fixture.route(from: jsonFileName, options: routeOptions) - - let alternateRoute = Fixture.route(from: jsonFileName, options: routeOptions) + let offRouteLocation = CLLocation(coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0), + altitude: 5, + horizontalAccuracy: 10, + verticalAccuracy: 5, + course: 20, + speed: 4, + timestamp: Date()) override func setUp() { super.setUp() @@ -48,15 +77,36 @@ class NavigationServiceTests: XCTestCase { } func testDefaultUserInterfaceUsage() { - XCTAssertTrue(dependencies.navigationService.eventsManager.usesDefaultUserInterface, "MapboxCoreNavigationTests should have an implicit dependency on MapboxNavigation due to running inside the Example application target.") + // Given the state from setUp + + // When // Then + XCTAssertTrue(dependencies.navigationService.eventsManager.usesDefaultUserInterface, + "MapboxCoreNavigationTests should have an implicit dependency on MapboxNavigation due to running inside the Example application target.") } - func testUserIsOnRoute() { - let navigation = dependencies.navigationService - let firstLocation = dependencies.routeLocations.firstLocation + func testUserIsOnRouteReturnsTrueForTheRouteLocation() { + // Given + let navigationService = dependencies.navigationService + let firstRouteLocation = dependencies.routeLocations.firstLocation - navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocation]) - XCTAssertTrue(navigation.router.userIsOnRoute(firstLocation), "User should be on route") + // When + navigationService.locationManager!(navigationService.locationManager, didUpdateLocations: [firstRouteLocation]) + + // Then + XCTAssertTrue(navigationService.router.userIsOnRoute(firstRouteLocation), + "User should be on route") + } + + func testUserIsOnRouteReturnsFalseForTheNonRouteLocation() { + // Given + let navigationService = dependencies.navigationService + + // When + navigationService.locationManager!(navigationService.locationManager, didUpdateLocations: [offRouteLocation]) + + // Then + XCTAssertFalse(navigationService.router.userIsOnRoute(offRouteLocation), + "User should be off route") } func testUserIsOffRoute() { @@ -65,111 +115,221 @@ class NavigationServiceTests: XCTestCase { let coordinates = route.shape!.coordinates.prefix(3) let now = Date() - let locations = coordinates.enumerated().map { CLLocation(coordinate: $0.element, - altitude: -1, horizontalAccuracy: 10, verticalAccuracy: -1, course: -1, speed: 10, timestamp: now + $0.offset) } + let locations = coordinates.enumerated().map { + CLLocation(coordinate: $0.element, + altitude: -1, + horizontalAccuracy: 10, + verticalAccuracy: -1, + course: -1, + speed: 10, + timestamp: now + $0.offset) + } - locations.forEach { navigation.router!.locationManager!(navigation.locationManager, didUpdateLocations: [$0]) } + locations.forEach { navigation.router!.locationManager!(navigation.locationManager, + didUpdateLocations: [$0]) } XCTAssertTrue(navigation.router.userIsOnRoute(locations.last!), "User should be on route") - let coordinatesOffRoute: [CLLocationCoordinate2D] = (0...3).map { _ in locations.first!.coordinate.coordinate(at: 100, facing: 90) } + let coordinatesOffRoute: [CLLocationCoordinate2D] = (0...3).map { _ in + locations.first!.coordinate.coordinate(at: 100, facing: 90) + } + let locationsOffRoute = coordinatesOffRoute.enumerated().map { - CLLocation(coordinate: $0.element, altitude: -1, horizontalAccuracy: 10, - verticalAccuracy: -1, course: -1, speed: 10, + CLLocation(coordinate: $0.element, + altitude: -1, + horizontalAccuracy: 10, + verticalAccuracy: -1, + course: -1, + speed: 10, timestamp: now + locations.count + $0.offset) } - locationsOffRoute.forEach { navigation.router!.locationManager!(navigation.locationManager, didUpdateLocations: [$0]) } + locationsOffRoute.forEach { navigation.router!.locationManager!(navigation.locationManager, + didUpdateLocations: [$0]) } XCTAssertFalse(navigation.router.userIsOnRoute(locationsOffRoute.last!), "User should be off route") } func testAdvancingToFutureStepAndNotRerouting() { + // Given let navigation = dependencies.navigationService let route = navigation.route let firstStepCoordinates = route.legs[0].steps[0].shape!.coordinates let now = Date() let firstStepLocations = firstStepCoordinates.enumerated().map { - CLLocation(coordinate: $0.element, altitude: -1, horizontalAccuracy: 10, verticalAccuracy: -1, course: -1, speed: 10, timestamp: now + $0.offset) + CLLocation(coordinate: $0.element, + altitude: -1, + horizontalAccuracy: 10, + verticalAccuracy: -1, + course: -1, speed: 10, + timestamp: now + $0.offset) } - firstStepLocations.forEach { navigation.router!.locationManager!(navigation.locationManager, didUpdateLocations: [$0]) } + // When + firstStepLocations.forEach { navigation.router!.locationManager!(navigation.locationManager, + didUpdateLocations: [$0]) } + + // Then XCTAssertTrue(navigation.router.userIsOnRoute(firstStepLocations.last!), "User should be on route") - XCTAssertEqual(navigation.router.routeProgress.currentLegProgress.stepIndex, 1, "User is on first step") + XCTAssertEqual(navigation.router.routeProgress.currentLegProgress.stepIndex, 0, "User is on first step") + // Given let thirdStepCoordinates = route.legs[0].steps[2].shape!.coordinates let thirdStepLocations = thirdStepCoordinates.enumerated().map { - CLLocation(coordinate: $0.element, altitude: -1, horizontalAccuracy: 10, verticalAccuracy: -1, course: -1, speed: 10, timestamp: now + firstStepCoordinates.count + $0.offset) + CLLocation(coordinate: $0.element, + altitude: -1, + horizontalAccuracy: 10, + verticalAccuracy: -1, + course: -1, + speed: 10, + timestamp: now + firstStepCoordinates.count + $0.offset) } - thirdStepLocations.forEach { navigation.router!.locationManager!(navigation.locationManager, didUpdateLocations: [$0]) } + // When + thirdStepLocations.forEach { navigation.router!.locationManager!(navigation.locationManager, + didUpdateLocations: [$0]) } + // Then XCTAssertTrue(navigation.router.userIsOnRoute(thirdStepLocations.last!), "User should be on route") XCTAssertEqual(navigation.router.routeProgress.currentLegProgress.stepIndex, 3, "User should be on route and we should increment all the way to the 4th step") } func testSnappedLocation() { + // Given let navigation = dependencies.navigationService let firstLocation = dependencies.routeLocations.firstLocation + + // When navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocation]) + + // Then XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstLocation.coordinate.latitude, accuracy: 0.0005, "Check snapped location is working") XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstLocation.coordinate.longitude, accuracy: 0.0005, "Check snapped location is working") } func testSnappedAtEndOfStepLocationWhenMovingSlowly() { + // Given let navigation = dependencies.navigationService let firstLocation = dependencies.routeLocations.firstLocation + let coordinateThreshold = 0.0005 + // When navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocation]) - XCTAssertEqual(navigation.router.location!.coordinate, firstLocation.coordinate, "Check snapped location is working") - let firstCoordinateOnUpcomingStep = navigation.router.routeProgress.currentLegProgress.upcomingStep!.shape!.coordinates.first! - let firstLocationOnNextStepWithNoSpeed = CLLocation(coordinate: firstCoordinateOnUpcomingStep, altitude: 0, horizontalAccuracy: 10, verticalAccuracy: 10, course: 10, speed: 0, timestamp: Date()) + // Then + XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstLocation.coordinate.latitude, accuracy: coordinateThreshold, "User is snapped to upcoming step when moving") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstLocation.coordinate.longitude, accuracy: coordinateThreshold, "User is snapped to upcoming step when moving") + // Given + let firstCoordinateOnUpcomingStep = navigation.router.routeProgress.currentLegProgress.upcomingStep!.shape!.coordinates.first! + let firstLocationOnNextStepWithNoSpeed = CLLocation(coordinate: firstCoordinateOnUpcomingStep, + altitude: 0, + horizontalAccuracy: 10, + verticalAccuracy: 10, + course: 10, + speed: 0, + timestamp: Date()) + + // When navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocationOnNextStepWithNoSpeed]) - XCTAssertEqual(navigation.router.location!.coordinate, navigation.router.routeProgress.currentLegProgress.currentStep.shape!.coordinates.last!, "When user is not moving, snap to current leg only") - let firstLocationOnNextStepWithSpeed = CLLocation(coordinate: firstCoordinateOnUpcomingStep, altitude: 0, horizontalAccuracy: 10, verticalAccuracy: 10, course: 10, speed: 5, timestamp: Date()) + // Then + XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstLocationOnNextStepWithNoSpeed.coordinate.latitude, accuracy: coordinateThreshold, "User is snapped to upcoming step when moving") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstLocationOnNextStepWithNoSpeed.coordinate.longitude, accuracy: coordinateThreshold, "User is snapped to upcoming step when moving") + + // Given + let firstLocationOnNextStepWithSpeed = CLLocation(coordinate: firstCoordinateOnUpcomingStep, + altitude: 0, + horizontalAccuracy: 10, + verticalAccuracy: 10, + course: 10, + speed: 5, + timestamp: Date()) + + // When navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocationOnNextStepWithSpeed]) - XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstCoordinateOnUpcomingStep.latitude, accuracy: 0.0005, "User is snapped to upcoming step when moving") - XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstCoordinateOnUpcomingStep.longitude, accuracy: 0.0005, "User is snapped to upcoming step when moving") + // Then + XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstCoordinateOnUpcomingStep.latitude, accuracy: coordinateThreshold, "User is snapped to upcoming step when moving") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstCoordinateOnUpcomingStep.longitude, accuracy: coordinateThreshold, "User is snapped to upcoming step when moving") } func testSnappedAtEndOfStepLocationWhenCourseIsSimilar() { + // Given let navigation = dependencies.navigationService let firstLocation = dependencies.routeLocations.firstLocation + let coordinateThreshold = 0.005 + // When navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocation]) - XCTAssertEqual(navigation.router.location!.coordinate, firstLocation.coordinate, "Check snapped location is working") + // Then + XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstLocation.coordinate.latitude, accuracy: coordinateThreshold, "Check snapped location is working") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstLocation.coordinate.longitude, accuracy: coordinateThreshold, "Check snapped location is working") + + // Given let firstCoordinateOnUpcomingStep = navigation.router.routeProgress.currentLegProgress.upcomingStep!.shape!.coordinates.first! let finalHeading = navigation.router.routeProgress.currentLegProgress.upcomingStep!.finalHeading! - let firstLocationOnNextStepWithDifferentCourse = CLLocation(coordinate: firstCoordinateOnUpcomingStep, altitude: 0, horizontalAccuracy: 30, verticalAccuracy: 10, course: -finalHeading, speed: 5, timestamp: Date()) - - navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocationOnNextStepWithDifferentCourse]) - XCTAssertEqual(navigation.router.location!.coordinate, navigation.router.routeProgress.currentLegProgress.currentStep.shape!.coordinates.last!, "When user's course is dissimilar from the finalHeading, they should not snap to upcoming step") - - let firstLocationOnNextStepWithCorrectCourse = CLLocation(coordinate: firstCoordinateOnUpcomingStep, altitude: 0, horizontalAccuracy: 30, verticalAccuracy: 10, course: finalHeading, speed: 0, timestamp: Date()) + let firstLocationOnNextStepWithDifferentCourse = CLLocation(coordinate: firstCoordinateOnUpcomingStep, + altitude: 0, + horizontalAccuracy: 30, + verticalAccuracy: 10, + course: -finalHeading, + speed: 5, + timestamp: Date()) + + // When + navigation.locationManager!(navigation.locationManager, + didUpdateLocations: [firstLocationOnNextStepWithDifferentCourse]) + + // Then + XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstLocation.coordinate.latitude, accuracy: coordinateThreshold, "Latitudes should be almost equal") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstLocation.coordinate.longitude, accuracy: coordinateThreshold, "Longitudes should be almost equal") + + let firstLocationOnNextStepWithCorrectCourse = CLLocation(coordinate: firstCoordinateOnUpcomingStep, + altitude: 0, + horizontalAccuracy: 30, + verticalAccuracy: 10, + course: finalHeading, + speed: 0, timestamp: + Date()) navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocationOnNextStepWithCorrectCourse]) - XCTAssertEqual(navigation.router.location!.coordinate, firstCoordinateOnUpcomingStep, "User is snapped to upcoming step when their course is similar to the final heading") + XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstCoordinateOnUpcomingStep.latitude, accuracy: coordinateThreshold, "Inaccurate location is still snapped") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstCoordinateOnUpcomingStep.longitude, accuracy: coordinateThreshold, "Inaccurate location is still snapped") } func testSnappedLocationForUnqualifiedLocation() { + // Given let navigation = dependencies.navigationService let firstLocation = dependencies.routeLocations.firstLocation + let coordinateThreshold = 0.005 + + // When navigation.locationManager!(navigation.locationManager, didUpdateLocations: [firstLocation]) - XCTAssertEqual(navigation.router.location!.coordinate, firstLocation.coordinate, "Check snapped location is working") - let futureCoord = navigation.router.routeProgress.nearbyShape.coordinateFromStart(distance: 100)! - let futureInaccurateLocation = CLLocation(coordinate: futureCoord, altitude: 0, horizontalAccuracy: 1, verticalAccuracy: 200, course: 0, speed: 5, timestamp: Date()) + // Then + XCTAssertEqual(navigation.router.location!.coordinate.latitude, firstLocation.coordinate.latitude, accuracy: coordinateThreshold, "Inaccurate location is still snapped") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, firstLocation.coordinate.longitude, accuracy: coordinateThreshold, "Inaccurate location is still snapped") + + // Given + let futureCoord = navigation.router.routeProgress.nearbyShape.coordinateFromStart(distance: 100)! + let futureInaccurateLocation = CLLocation(coordinate: futureCoord, + altitude: 0, + horizontalAccuracy: 1, + verticalAccuracy: 200, + course: 0, + speed: 5, + timestamp: Date()) + + // When navigation.locationManager!(navigation.locationManager, didUpdateLocations: [futureInaccurateLocation]) - XCTAssertEqual(navigation.router.location!.coordinate.latitude, futureInaccurateLocation.coordinate.latitude, accuracy: 0.0005, "Inaccurate location is still snapped") - XCTAssertEqual(navigation.router.location!.coordinate.longitude, futureInaccurateLocation.coordinate.longitude, accuracy: 0.0005, "Inaccurate location is still snapped") + // Then + XCTAssertEqual(navigation.router.location!.coordinate.latitude, futureInaccurateLocation.coordinate.latitude, accuracy: coordinateThreshold, "Inaccurate location is still snapped") + XCTAssertEqual(navigation.router.location!.coordinate.longitude, futureInaccurateLocation.coordinate.longitude, accuracy: coordinateThreshold, "Inaccurate location is still snapped") } func testUserPuckShouldFaceBackwards() { @@ -248,47 +408,62 @@ class NavigationServiceTests: XCTestCase { } func testReroutingFromALocationSendsEvents() { + // Given let navigationService = dependencies.navigationService + navigationService.eventsManager.delaysEventFlushing = false + let router = navigationService.router! let testLocation = dependencies.routeLocations.firstLocation - navigationService.eventsManager.delaysEventFlushing = false + let distanceThreshold: CLLocationDistance = 0.5 + let notificationReceivingTimeout: TimeInterval = 0.1 + + let willRerouteNotificationExpectation = expectation(forNotification: .routeControllerWillReroute, + object: router) { (notification) -> Bool in + let locationKey = RouteController.NotificationUserInfoKey.locationKey + let fromLocation = notification.userInfo![locationKey] as? CLLocation - let willRerouteNotificationExpectation = expectation(forNotification: .routeControllerWillReroute, object: router) { (notification) -> Bool in - let fromLocation = notification.userInfo![RouteController.NotificationUserInfoKey.locationKey] as? CLLocation return fromLocation == testLocation } - let didRerouteNotificationExpectation = expectation(forNotification: .routeControllerDidReroute, object: router, handler: nil) + let didRerouteNotificationExpectation = expectation(forNotification: .routeControllerDidReroute, + object: router, handler: nil) + + let routeProgressDidChangeNotificationExpectation = expectation(forNotification: .routeControllerProgressDidChange, + object: router) { (notification) -> Bool in + let locationKey = RouteController.NotificationUserInfoKey.locationKey + let rawLocationKey = RouteController.NotificationUserInfoKey.rawLocationKey + let routeProgressKey = RouteController.NotificationUserInfoKey.routeProgressKey - let routeProgressDidChangeNotificationExpectation = expectation(forNotification: .routeControllerProgressDidChange, object: router) { (notification) -> Bool in - let location = notification.userInfo![RouteController.NotificationUserInfoKey.locationKey] as? CLLocation - let rawLocation = notification.userInfo![RouteController.NotificationUserInfoKey.rawLocationKey] as? CLLocation - let _ = notification.userInfo![RouteController.NotificationUserInfoKey.routeProgressKey] as! RouteProgress + let location = notification.userInfo![locationKey] as? CLLocation + let rawLocation = notification.userInfo![rawLocationKey] as? CLLocation + let _ = notification.userInfo![routeProgressKey] as! RouteProgress - return location!.distance(from: rawLocation!) <= 0.0005 + return location!.distance(from: rawLocation!) <= distanceThreshold } + // When // MARK: When told to re-route from location -- `reroute(from:)` router.reroute(from: testLocation, along: router.routeProgress) + // Then // MARK: it tells the delegate & posts a willReroute notification XCTAssertTrue(delegate.recentMessages.contains("navigationService(_:willRerouteFrom:)")) - wait(for: [willRerouteNotificationExpectation], timeout: 0.1) + wait(for: [willRerouteNotificationExpectation], timeout: notificationReceivingTimeout) // MARK: Upon rerouting successfully... directionsClientSpy.fireLastCalculateCompletion(with: nil, routes: [alternateRoute], error: nil) // MARK: It tells the delegate & posts a didReroute notification XCTAssertTrue(delegate.recentMessages.contains("navigationService(_:didRerouteAlong:at:proactive:)")) - wait(for: [didRerouteNotificationExpectation], timeout: 0.1) + wait(for: [didRerouteNotificationExpectation], timeout: notificationReceivingTimeout) // MARK: On the next call to `locationManager(_, didUpdateLocations:)` navigationService.locationManager!(navigationService.locationManager, didUpdateLocations: [testLocation]) // MARK: It tells the delegate & posts a routeProgressDidChange notification XCTAssertTrue(delegate.recentMessages.contains("navigationService(_:didUpdate:with:rawLocation:)")) - wait(for: [routeProgressDidChangeNotificationExpectation], timeout: 0.1) + wait(for: [routeProgressDidChangeNotificationExpectation], timeout: notificationReceivingTimeout) // MARK: It enqueues and flushes a NavigationRerouteEvent let expectedEventName = MMEEventTypeNavigationReroute diff --git a/MapboxNavigationTests/CarPlayManagerTests.swift b/MapboxNavigationTests/CarPlayManagerTests.swift index 4592622cda2..59dae5a66b4 100644 --- a/MapboxNavigationTests/CarPlayManagerTests.swift +++ b/MapboxNavigationTests/CarPlayManagerTests.swift @@ -497,7 +497,7 @@ class TestCarPlayManagerDelegate: CarPlayManagerDelegate { public var mapButtons: [CPMapButton]? func carPlayManager(_ carPlayManager: CarPlayManager, navigationServiceAlong route: Route, routeOptions: RouteOptions, desiredSimulationMode: SimulationMode) -> NavigationService { - let response = Fixture.routeResponse(from: jsonFileName, options: routeOptions) + let response = Fixture.routeResponse(from: routeInstructionsJSONFileName, options: routeOptions) let initialRoute = response.routes!.first! let directionsClientSpy = DirectionsSpy() let service = MapboxNavigationService(route: initialRoute, routeOptions: routeOptions, directions: directionsClientSpy, locationSource: NavigationLocationManager(), eventsManagerType: NavigationEventsManagerSpy.self, simulating: desiredSimulationMode)