From c22d05d148a6dec5932825fe8b45ee713d9d4484 Mon Sep 17 00:00:00 2001 From: Ian Wagner Date: Tue, 12 Mar 2024 12:11:48 +0900 Subject: [PATCH] Minor refactoring; introduce more snapshot tests to make things cleaner --- Sources/MapLibreSwiftUI/Examples/Camera.swift | 14 ++++--- .../MapLibreSwiftUI/MapViewCoordinator.swift | 39 ++++++++++-------- .../Models/MapCamera/CameraState.swift | 4 +- .../Models/MapCamera/CameraStateTests.swift | 24 +++++------ .../Models/MapCamera/MapViewCameraTests.swift | 40 +++++++------------ .../testCenterCameraState.1.txt | 1 + .../CameraStateTests/testRect.1.txt | 1 + .../testTrackingUserLocation.1.txt | 1 + .../testTrackingUserLocationWithCourse.1.txt | 1 + .../testTrackingUserLocationWithHeading.1.txt | 1 + .../MapViewCameraTests/testCenterCamera.1.txt | 13 ++++++ .../testTrackUserLocationWithCourse.1.txt | 10 +++++ .../testTrackUserLocationWithHeading.1.txt | 7 ++++ .../testTrackingUserLocation.1.txt | 10 +++++ 14 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testRect.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt create mode 100644 Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt diff --git a/Sources/MapLibreSwiftUI/Examples/Camera.swift b/Sources/MapLibreSwiftUI/Examples/Camera.swift index 3d7d025..8cf49c0 100644 --- a/Sources/MapLibreSwiftUI/Examples/Camera.swift +++ b/Sources/MapLibreSwiftUI/Examples/Camera.swift @@ -8,6 +8,7 @@ struct CameraDirectManipulationPreview: View { let styleURL: URL var onStyleLoaded: (() -> Void)? = nil + var targetCameraAfterDelay: MapViewCamera? = nil var body: some View { MapView(styleURL: styleURL, camera: $camera) @@ -16,7 +17,7 @@ struct CameraDirectManipulationPreview: View { onStyleLoaded?() } .overlay(alignment: .bottom, content: { - Text("\(String(describing: camera.state)) z \(camera.zoom)") + Text("\(String(describing: camera.state))") .padding() .foregroundColor(.white) .background( @@ -27,16 +28,19 @@ struct CameraDirectManipulationPreview: View { .padding(.bottom, 42) }) .task { - try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC) + if let targetCameraAfterDelay { + try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC) - camera = MapViewCamera.center(switzerland, zoom: 6) + camera = targetCameraAfterDelay + } } } } -#Preview("Camera Preview") { +#Preview("Camera Zoom after delay") { CameraDirectManipulationPreview( - styleURL: URL(string: "https://demotiles.maplibre.org/style.json")! + styleURL: URL(string: "https://demotiles.maplibre.org/style.json")!, + targetCameraAfterDelay: .center(switzerland, zoom: 6) ) .ignoresSafeArea(.all) } diff --git a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift index 531b1fd..91d33d5 100644 --- a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift +++ b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift @@ -54,7 +54,7 @@ public class MapViewCoordinator: NSObject { zoomLevel: camera.zoom, direction: camera.direction, animated: animated) - case .trackingUserLocation: + case.trackingUserLocation: mapView.userTrackingMode = .follow mapView.setZoomLevel(camera.zoom, animated: false) case .trackingUserLocationWithHeading: @@ -176,32 +176,39 @@ public class MapViewCoordinator: NSObject { // MARK: - MLNMapViewDelegate extension MapViewCoordinator: MLNMapViewDelegate { - public func mapView(_: MLNMapView, didFinishLoading mglStyle: MLNStyle) { + public func mapView(_ mapView: MLNMapView, didFinishLoading mglStyle: MLNStyle) { addLayers(to: mglStyle) onStyleLoaded?(mglStyle) } /// The MapView's region has changed with a specific reason. public func mapView(_ mapView: MLNMapView, regionDidChangeWith reason: MLNCameraChangeReason, animated _: Bool) { - // Validate that the mapView.userTrackingMode still matches our desired camera state for each tracking type. - let isFollowing = parent.camera.state == .trackingUserLocation && mapView.userTrackingMode == .follow - let isFollowingHeading = parent.camera.state == .trackingUserLocationWithHeading && mapView - .userTrackingMode == .followWithHeading - let isFollowingCourse = parent.camera.state == .trackingUserLocationWithCourse && mapView - .userTrackingMode == .followWithCourse - // If any of these are a mismatch, we know the camera is no longer following a desired method, so we should - // detach and revert to a .centered camera. If any one of these is true, the desired camera state still matches - // the mapView's userTrackingMode - if isFollowing || isFollowingHeading || isFollowingCourse { - // User tracking is still active, we can ignore camera updates until we unset/fail this boolean check + // detach and revert to a .centered camera. If any one of these is true, the desired camera state still + // matches the mapView's userTrackingMode + let isProgrammaticallyTracking: Bool = switch parent.camera.state { + case .centered(onCoordinate: _): + false + case .trackingUserLocation: + mapView.userTrackingMode == .follow + case .trackingUserLocationWithHeading: + mapView.userTrackingMode == .followWithHeading + case .trackingUserLocationWithCourse: + mapView.userTrackingMode == .followWithCourse + case .rect(northeast: _, southwest: _): + false + case .showcase(shapeCollection: _): + false + } + + if isProgrammaticallyTracking { + // Programmatic tracking is still active, we can ignore camera updates until we unset/fail this boolean + // check return } DispatchQueue.main.async { - // The user's desired camera is not a user tracking method, now we need to publish the MLNMapView's camera - // state - // to the MapView camera binding. + // Publish the MLNMapView's "raw" camera state to the MapView camera binding. self.parent.camera = .center(mapView.centerCoordinate, zoom: mapView.zoomLevel, reason: CameraChangeReason(reason)) diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift index f6fdd8e..e07fdd6 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift @@ -34,8 +34,8 @@ public enum CameraState: Hashable { extension CameraState: CustomDebugStringConvertible { public var debugDescription: String { switch self { - case let .centered(onCoordinate: onCoordinate): - "CameraState.centered(onCoordinate: \(onCoordinate)" + case let .centered(onCoordinate: coordinate): + "CameraState.centered(onCoordinate: \(coordinate))" case .trackingUserLocation: "CameraState.trackingUserLocation" case .trackingUserLocationWithHeading: diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift index 5365a8d..ec3b090 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/CameraStateTests.swift @@ -1,34 +1,33 @@ import CoreLocation +import SnapshotTesting import XCTest @testable import MapLibreSwiftUI final class CameraStateTests: XCTestCase { + let coordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4) + func testCenterCameraState() { - let expectedCoordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4) - let state: CameraState = .centered(onCoordinate: expectedCoordinate) - XCTAssertEqual(state, .centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4))) - XCTAssertEqual( - String(describing: state), - "CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)" - ) + let state: CameraState = .centered(onCoordinate: coordinate) + XCTAssertEqual(state, .centered(onCoordinate: coordinate)) + assertSnapshot(of: state, as: .description) } func testTrackingUserLocation() { let state: CameraState = .trackingUserLocation XCTAssertEqual(state, .trackingUserLocation) - XCTAssertEqual(String(describing: state), "CameraState.trackingUserLocation") + assertSnapshot(of: state, as: .description) } func testTrackingUserLocationWithHeading() { let state: CameraState = .trackingUserLocationWithHeading XCTAssertEqual(state, .trackingUserLocationWithHeading) - XCTAssertEqual(String(describing: state), "CameraState.trackingUserLocationWithHeading") + assertSnapshot(of: state, as: .description) } func testTrackingUserLocationWithCourse() { let state: CameraState = .trackingUserLocationWithCourse XCTAssertEqual(state, .trackingUserLocationWithCourse) - XCTAssertEqual(String(describing: state), "CameraState.trackingUserLocationWithCourse") + assertSnapshot(of: state, as: .description) } func testRect() { @@ -37,9 +36,6 @@ final class CameraStateTests: XCTestCase { let state: CameraState = .rect(northeast: northeast, southwest: southwest) XCTAssertEqual(state, .rect(northeast: northeast, southwest: southwest)) - XCTAssertEqual( - String(describing: state), - "CameraState.rect(northeast: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), southwest: CLLocationCoordinate2D(latitude: 34.5, longitude: 45.6))" - ) + assertSnapshot(of: state, as: .description) } } diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift b/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift index 1031264..15b091d 100644 --- a/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/MapViewCameraTests.swift @@ -1,49 +1,37 @@ import CoreLocation +import SnapshotTesting import XCTest @testable import MapLibreSwiftUI final class MapViewCameraTests: XCTestCase { func testCenterCamera() { - let expectedCoordinate = CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4) - let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34) - let direction: CLLocationDirection = 23 - - let camera = MapViewCamera.center(expectedCoordinate, zoom: 12, pitch: pitch, direction: direction) - - XCTAssertEqual(camera.state, .centered(onCoordinate: expectedCoordinate)) - XCTAssertEqual(camera.zoom, 12) - XCTAssertEqual(camera.pitch, pitch) - XCTAssertEqual(camera.direction, direction) + let camera = MapViewCamera.center( + CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), + zoom: 5, + pitch: .freeWithinRange(minimum: 12, maximum: 34), + direction: 23 + ) + + assertSnapshot(of: camera, as: .dump) } func testTrackingUserLocation() { let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34) - let camera = MapViewCamera.trackUserLocation(pitch: pitch) + let camera = MapViewCamera.trackUserLocation(zoom: 10, pitch: pitch) - XCTAssertEqual(camera.state, .trackingUserLocation) - XCTAssertEqual(camera.zoom, 10) - XCTAssertEqual(camera.pitch, pitch) - XCTAssertEqual(camera.direction, 0) + assertSnapshot(of: camera, as: .dump) } func testTrackUserLocationWithCourse() { let pitch: CameraPitch = .freeWithinRange(minimum: 12, maximum: 34) let camera = MapViewCamera.trackUserLocationWithCourse(zoom: 18, pitch: pitch) - XCTAssertEqual(camera.state, .trackingUserLocationWithCourse) - XCTAssertEqual(camera.zoom, 18) - XCTAssertEqual(camera.pitch, pitch) - XCTAssertEqual(camera.direction, 0) + assertSnapshot(of: camera, as: .dump) } func testTrackUserLocationWithHeading() { - let camera = MapViewCamera.trackUserLocationWithHeading() + let camera = MapViewCamera.trackUserLocationWithHeading(zoom: 10, pitch: .free) - XCTAssertEqual(camera.state, .trackingUserLocationWithHeading) - XCTAssertEqual(camera.zoom, 10) - XCTAssertEqual(camera.pitch, .free) - XCTAssertEqual(camera.direction, 0) + assertSnapshot(of: camera, as: .dump) } - - // TODO: Add additional camera tests once behaviors are added (e.g. rect) } diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt new file mode 100644 index 0000000..d154bc2 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testCenterCameraState.1.txt @@ -0,0 +1 @@ +CameraState.centered(onCoordinate: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4)) \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testRect.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testRect.1.txt new file mode 100644 index 0000000..c1c7645 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testRect.1.txt @@ -0,0 +1 @@ +CameraState.rect(northeast: CLLocationCoordinate2D(latitude: 12.3, longitude: 23.4), southwest: CLLocationCoordinate2D(latitude: 34.5, longitude: 45.6)) \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt new file mode 100644 index 0000000..40324e8 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocation.1.txt @@ -0,0 +1 @@ +CameraState.trackingUserLocation \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt new file mode 100644 index 0000000..4523e80 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithCourse.1.txt @@ -0,0 +1 @@ +CameraState.trackingUserLocationWithCourse \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt new file mode 100644 index 0000000..b75f646 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/CameraStateTests/testTrackingUserLocationWithHeading.1.txt @@ -0,0 +1 @@ +CameraState.trackingUserLocationWithHeading \ No newline at end of file diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt new file mode 100644 index 0000000..5e42855 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testCenterCamera.1.txt @@ -0,0 +1,13 @@ +▿ MapViewCamera + - direction: 23.0 + - lastReasonForChange: Optional.none + ▿ pitch: CameraPitch + ▿ freeWithinRange: (2 elements) + - minimum: 12.0 + - maximum: 34.0 + ▿ state: CameraState + ▿ centered: (1 element) + ▿ onCoordinate: CLLocationCoordinate2D + - latitude: 12.3 + - longitude: 23.4 + - zoom: 5.0 diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt new file mode 100644 index 0000000..5e18633 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithCourse.1.txt @@ -0,0 +1,10 @@ +▿ MapViewCamera + - direction: 0.0 + ▿ lastReasonForChange: Optional + - some: CameraChangeReason.programmatic + ▿ pitch: CameraPitch + ▿ freeWithinRange: (2 elements) + - minimum: 12.0 + - maximum: 34.0 + - state: CameraState.CameraState.trackingUserLocationWithCourse + - zoom: 18.0 diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt new file mode 100644 index 0000000..1164911 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackUserLocationWithHeading.1.txt @@ -0,0 +1,7 @@ +▿ MapViewCamera + - direction: 0.0 + ▿ lastReasonForChange: Optional + - some: CameraChangeReason.programmatic + - pitch: CameraPitch.free + - state: CameraState.CameraState.trackingUserLocationWithHeading + - zoom: 10.0 diff --git a/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt new file mode 100644 index 0000000..156d26a --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Models/MapCamera/__Snapshots__/MapViewCameraTests/testTrackingUserLocation.1.txt @@ -0,0 +1,10 @@ +▿ MapViewCamera + - direction: 0.0 + ▿ lastReasonForChange: Optional + - some: CameraChangeReason.programmatic + ▿ pitch: CameraPitch + ▿ freeWithinRange: (2 elements) + - minimum: 12.0 + - maximum: 34.0 + - state: CameraState.CameraState.trackingUserLocation + - zoom: 10.0