diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 010ce8db6..8d5edd7d9 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -67,6 +67,7 @@ 9AC4FDF12BACE216004479BF /* NearbyTransitPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AC4FDF02BACE216004479BF /* NearbyTransitPageView.swift */; }; 9ACA9DF32BA1EC8B003F0E9E /* HomeMapViewUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ACA9DF22BA1EC8B003F0E9E /* HomeMapViewUITests.swift */; }; 9AD1D1FE2BA4D5C600182060 /* ViewportProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD1D1FD2BA4D5C600182060 /* ViewportProviderTest.swift */; }; + 9ADB849D2BAD05BC006581CE /* Inspection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ADB849C2BAD05BC006581CE /* Inspection.swift */; }; 9AF88E052B48913C00E08C7C /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9AF88E042B48913C00E08C7C /* Localizable.xcstrings */; }; A430D45FE0676C73075AB85B /* Pods_iosAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED1649D982654BA7A4D2F2DC /* Pods_iosAppTests.framework */; }; A55C5596CDC797ED68F79279 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C6BD892027AC258EE8F408D /* Pods_iosApp.framework */; }; @@ -171,6 +172,7 @@ 9AC4FDF02BACE216004479BF /* NearbyTransitPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyTransitPageView.swift; sourceTree = ""; }; 9ACA9DF22BA1EC8B003F0E9E /* HomeMapViewUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMapViewUITests.swift; sourceTree = ""; }; 9AD1D1FD2BA4D5C600182060 /* ViewportProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewportProviderTest.swift; sourceTree = ""; }; + 9ADB849C2BAD05BC006581CE /* Inspection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inspection.swift; sourceTree = ""; }; 9AF88E042B48913C00E08C7C /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; ED1649D982654BA7A4D2F2DC /* Pods_iosAppTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosAppTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F71252D2B68FF131F8E6BDE2 /* Pods-iosAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosAppTests.release.xcconfig"; path = "Target Support Files/Pods-iosAppTests/Pods-iosAppTests.release.xcconfig"; sourceTree = ""; }; @@ -432,6 +434,7 @@ 9A03F3652BA9E68500DA40DC /* Debouncer.swift */, 9A37F3042BACCC40001714FE /* DoubleRoundedExtension.swift */, 9A37F3062BACCCA5001714FE /* CoordinateExtension.swift */, + 9ADB849C2BAD05BC006581CE /* Inspection.swift */, ); path = Utils; sourceTree = ""; @@ -745,6 +748,7 @@ 8CEA10232BA0F3C6001C6EB9 /* ScheduleFetcher.swift in Sources */, 6E99CBB72B9892C80047E78D /* SocketProvider.swift in Sources */, 9A6DDF912B976FDF004D141A /* EmptyWhenModifier.swift in Sources */, + 9ADB849D2BAD05BC006581CE /* Inspection.swift in Sources */, 6EE7457E2B965ADE0052227E /* Socket.swift in Sources */, 9A03F3662BA9E68500DA40DC /* Debouncer.swift in Sources */, 9A8B34AD2B88E5090018412C /* RailRouteShapeFetcher.swift in Sources */, diff --git a/iosApp/iosApp/Fetchers/NearbyFetcher.swift b/iosApp/iosApp/Fetchers/NearbyFetcher.swift index 9fd052daf..b10a860fb 100644 --- a/iosApp/iosApp/Fetchers/NearbyFetcher.swift +++ b/iosApp/iosApp/Fetchers/NearbyFetcher.swift @@ -82,10 +82,9 @@ class NearbyFetcher: ObservableObject { predictions: PredictionsStreamDataResponse?, filterAtTime: Instant ) -> [StopAssociatedRoute]? { - let lat = loadedLocation?.latitude ?? 0.0 - let lon = loadedLocation?.longitude ?? 0.0 + guard let loadedLocation else { return nil } return nearbyByRouteAndStop?.withRealtimeInfo( - sortByDistanceFrom: .init(longitude: lon, latitude: lat), + sortByDistanceFrom: .init(longitude: loadedLocation.longitude, latitude: loadedLocation.latitude), schedules: schedules, predictions: predictions, filterAtTime: filterAtTime diff --git a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitLocationProvider.swift b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitLocationProvider.swift index e5161ed22..4f573c249 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitLocationProvider.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitLocationProvider.swift @@ -8,18 +8,18 @@ import CoreLocation -struct NearbyTransitLocationProvider { +class NearbyTransitLocationProvider: ObservableObject { let currentLocation: CLLocationCoordinate2D? let cameraLocation: CLLocationCoordinate2D let isFollowing: Bool - var location: CLLocationCoordinate2D { - if isFollowing, currentLocation != nil { currentLocation! } else { cameraLocation } - } + @Published var location: CLLocationCoordinate2D init(currentLocation: CLLocationCoordinate2D? = nil, cameraLocation: CLLocationCoordinate2D, isFollowing: Bool) { self.currentLocation = currentLocation self.cameraLocation = cameraLocation self.isFollowing = isFollowing + + location = if isFollowing, currentLocation != nil { currentLocation! } else { cameraLocation } } } diff --git a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitPageView.swift b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitPageView.swift index 976e5d21b..d5c004381 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitPageView.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitPageView.swift @@ -46,7 +46,7 @@ struct NearbyTransitPageView: View { var body: some View { NearbyTransitView( - location: locationProvider.location, + locationProvider: locationProvider, nearbyFetcher: nearbyFetcher, scheduleFetcher: scheduleFetcher, predictionsFetcher: predictionsFetcher @@ -69,7 +69,8 @@ struct NearbyTransitPageView: View { ) } .onChange(of: currentLocation) { newLocation in - let shouldUpdateLocation = viewportProvider.viewport.isFollowing && !locationProvider.location.isRoughlyEqualTo(newLocation) + let shouldUpdateLocation = viewportProvider.viewport.isFollowing + && !locationProvider.location.isRoughlyEqualTo(newLocation) if shouldUpdateLocation { locationProvider = .init( currentLocation: newLocation, diff --git a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift index de766f502..33d49f0a3 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift @@ -15,25 +15,14 @@ import SwiftUI struct NearbyTransitView: View { @Environment(\.scenePhase) private var scenePhase - let location: CLLocationCoordinate2D + @ObservedObject var locationProvider: NearbyTransitLocationProvider @ObservedObject var nearbyFetcher: NearbyFetcher @ObservedObject var scheduleFetcher: ScheduleFetcher @ObservedObject var predictionsFetcher: PredictionsFetcher @State var now = Date.now let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect() - - init( - location: CLLocationCoordinate2D, - nearbyFetcher: NearbyFetcher, - scheduleFetcher: ScheduleFetcher, - predictionsFetcher: PredictionsFetcher - ) { - self.location = location - self.nearbyFetcher = nearbyFetcher - self.scheduleFetcher = scheduleFetcher - self.predictionsFetcher = predictionsFetcher - } + let inspection = Inspection() var body: some View { VStack { @@ -52,11 +41,11 @@ struct NearbyTransitView: View { } } .onAppear { - getNearby(location: location) + getNearby(location: locationProvider.location) joinPredictions() didAppear?(self) } - .onChange(of: location) { newLocation in + .onChange(of: locationProvider.location) { newLocation in getNearby(location: newLocation) } .onChange(of: nearbyFetcher.nearbyByRouteAndStop) { _ in @@ -75,12 +64,13 @@ struct NearbyTransitView: View { .onReceive(timer) { input in now = input } + .onReceive(inspection.notice) { inspection.visit(self, $0) } .onDisappear { leavePredictions() } .replaceWhen(nearbyFetcher.errorText) { errorText in IconCard(iconName: "network.slash", details: errorText) - .refreshable(nearbyFetcher.loading) { getNearby(location: location) } + .refreshable(nearbyFetcher.loading) { getNearby(location: locationProvider.location) } } } diff --git a/iosApp/iosApp/Utils/Inspection.swift b/iosApp/iosApp/Utils/Inspection.swift new file mode 100644 index 000000000..e96805509 --- /dev/null +++ b/iosApp/iosApp/Utils/Inspection.swift @@ -0,0 +1,24 @@ +// +// Inspection.swift +// iosApp +// +// This is used with ViewInspector as recommended in their docs. +// See https://github.com/nalexn/ViewInspector/blob/0.9.11/guide.md#approach-2 +// +// Created by Simon, Emma on 3/21/24. +// Copyright © 2024 MBTA. All rights reserved. +// + +import Combine +import SwiftUI + +final class Inspection { + let notice = PassthroughSubject() + var callbacks = [UInt: (V) -> Void]() + + func visit(_ view: V, _ line: UInt) { + if let callback = callbacks.removeValue(forKey: line) { + callback(view) + } + } +} diff --git a/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift b/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift index 3883041c8..7db35eb4f 100644 --- a/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift +++ b/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift @@ -6,6 +6,7 @@ // Copyright © 2024 MBTA. All rights reserved. // +import Combine import CoreLocation @testable import iosApp import shared @@ -15,6 +16,8 @@ import ViewInspector import XCTest @_spi(Experimental) import MapboxMaps +extension Inspection: InspectionEmissary {} + final class NearbyTransitViewTests: XCTestCase { struct NotUnderTestError: Error {} @@ -22,18 +25,21 @@ final class NearbyTransitViewTests: XCTestCase { executionTimeAllowance = 60 } - @MainActor func testPending() throws { + func testPending() throws { let sut = NearbyTransitView( - currentLocation: nil, + locationProvider: .init( + currentLocation: nil, + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: NearbyFetcher(backend: IdleBackend()), scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: .init(socket: MockSocket()), - viewportProvider: .init() + predictionsFetcher: .init(socket: MockSocket()) ) XCTAssertEqual(try sut.inspect().view(NearbyTransitView.self).vStack()[0].text().string(), "Loading...") } - @MainActor func testLoading() throws { + func testLoading() throws { class FakeNearbyFetcher: NearbyFetcher { let getNearbyExpectation: XCTestExpectation @@ -50,11 +56,14 @@ final class NearbyTransitViewTests: XCTestCase { let getNearbyExpectation = expectation(description: "getNearby") var sut = NearbyTransitView( - currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: FakeNearbyFetcher(getNearbyExpectation: getNearbyExpectation), scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: .init(socket: MockSocket()), - viewportProvider: .init() + predictionsFetcher: .init(socket: MockSocket()) ) let hasAppeared = sut.on(\NearbyTransitView.didAppear) { _ in } @@ -68,6 +77,7 @@ final class NearbyTransitViewTests: XCTestCase { class Route52NearbyFetcher: NearbyFetcher { init() { super.init(backend: IdleBackend()) + let objects = ObjectCollectionBuilder() let route52 = objects.route { route in route.id = "52" @@ -128,29 +138,36 @@ final class NearbyTransitViewTests: XCTestCase { } } } + loadedLocation = CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78) } override func getNearby(location _: CLLocationCoordinate2D) async {} } - @MainActor func testRoutePatternsGroupedByRouteAndStop() throws { + func testRoutePatternsGroupedByRouteAndStop() throws { let sut = NearbyTransitView( - currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: Route52NearbyFetcher(), scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: .init(socket: MockSocket()), - viewportProvider: .init() + predictionsFetcher: .init(socket: MockSocket()) ) let routes = try sut.inspect().findAll(NearbyRouteView.self) - XCTAssertNotNil(try routes[0].find(text: "52")) - XCTAssertNotNil(try routes[0].find(text: "Sawmill Brook Pkwy @ Walsh Rd") + XCTAssert(!routes.isEmpty) + guard let route = routes.first else { return } + + XCTAssertNotNil(try route.find(text: "52")) + XCTAssertNotNil(try route.find(text: "Sawmill Brook Pkwy @ Walsh Rd") .parent().find(text: "Charles River Loop")) - XCTAssertNotNil(try routes[0].find(text: "Sawmill Brook Pkwy @ Walsh Rd") + XCTAssertNotNil(try route.find(text: "Sawmill Brook Pkwy @ Walsh Rd") .parent().find(text: "Dedham Mall")) - XCTAssertNotNil(try routes[0].find(text: "Sawmill Brook Pkwy @ Walsh Rd - opposite side") + XCTAssertNotNil(try route.find(text: "Sawmill Brook Pkwy @ Walsh Rd - opposite side") .parent().find(text: "Watertown Yard")) } @@ -214,11 +231,14 @@ final class NearbyTransitViewTests: XCTestCase { } let sut = NearbyTransitView( - currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: Route52NearbyFetcher(), scheduleFetcher: FakeScheduleFetcher(objects), - predictionsFetcher: FakePredictionsFetcher(objects), - viewportProvider: .init() + predictionsFetcher: FakePredictionsFetcher(objects) ) let patterns = try sut.inspect().findAll(NearbyStopRoutePatternView.self) @@ -288,11 +308,14 @@ final class NearbyTransitViewTests: XCTestCase { let testFormatter = DateFormatter() testFormatter.timeStyle = .short let sut = NearbyTransitView( - currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: Route52NearbyFetcher(), scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: FakePredictionsFetcher(distantInstant: distantInstant), - viewportProvider: .init() + predictionsFetcher: FakePredictionsFetcher(distantInstant: distantInstant) ) let stops = try sut.inspect().findAll(NearbyStopView.self) @@ -344,11 +367,14 @@ final class NearbyTransitViewTests: XCTestCase { let nearbyFetcher = Route52NearbyFetcher() let predictionsFetcher = FakePredictionsFetcher(sawmillAtWalshExpectation: sawmillAtWalshExpectation, lechmereExpectation: lechmereExpectation) let sut = NearbyTransitView( - currentLocation: .init(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: nearbyFetcher, scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: predictionsFetcher, - viewportProvider: .init() + predictionsFetcher: predictionsFetcher ) ViewHosting.host(view: sut) @@ -371,11 +397,14 @@ final class NearbyTransitViewTests: XCTestCase { let predictionsFetcher = PredictionsFetcher(socket: MockSocket()) let sut = NearbyTransitView( - currentLocation: .init(), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: Route52NearbyFetcher(), scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: predictionsFetcher, - viewportProvider: .init() + predictionsFetcher: predictionsFetcher ) func prediction(minutesAway: Double) -> PredictionsStreamDataResponse { @@ -427,11 +456,14 @@ final class NearbyTransitViewTests: XCTestCase { let nearbyFetcher = Route52NearbyFetcher() let predictionsFetcher = FakePredictionsFetcher(joinExpectation: joinExpectation, leaveExpectation: leaveExpectation) let sut = NearbyTransitView( - currentLocation: .init(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: nearbyFetcher, scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: predictionsFetcher, - viewportProvider: .init() + predictionsFetcher: predictionsFetcher ) ViewHosting.host(view: sut) @@ -468,11 +500,14 @@ final class NearbyTransitViewTests: XCTestCase { let nearbyFetcher = Route52NearbyFetcher() let predictionsFetcher = FakePredictionsFetcher(joinExpectation: joinExpectation, leaveExpectation: leaveExpectation) let sut = NearbyTransitView( - currentLocation: .init(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: nearbyFetcher, scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: predictionsFetcher, - viewportProvider: .init() + predictionsFetcher: predictionsFetcher ) ViewHosting.host(view: sut) @@ -512,11 +547,14 @@ final class NearbyTransitViewTests: XCTestCase { let nearbyFetcher = Route52NearbyFetcher() let predictionsFetcher = FakePredictionsFetcher(joinExpectation: joinExpectation, leaveExpectation: leaveExpectation) let sut = NearbyTransitView( - currentLocation: .init(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: nearbyFetcher, scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: predictionsFetcher, - viewportProvider: .init() + predictionsFetcher: predictionsFetcher ) ViewHosting.host(view: sut) @@ -539,11 +577,14 @@ final class NearbyTransitViewTests: XCTestCase { } let sut = NearbyTransitView( - currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: FakeNearbyFetcher(), scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: .init(socket: MockSocket()), - viewportProvider: .init() + predictionsFetcher: .init(socket: MockSocket()) ) XCTAssertNotNil(try sut.inspect().view(NearbyTransitView.self).find(text: "Failed to load nearby transit, test error")) @@ -553,6 +594,7 @@ final class NearbyTransitViewTests: XCTestCase { class FakeNearbyFetcher: NearbyFetcher { init() { super.init(backend: IdleBackend()) + loadedLocation = CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78) nearbyByRouteAndStop = NearbyStaticData(data: []) } } @@ -564,20 +606,22 @@ final class NearbyTransitViewTests: XCTestCase { } let sut = NearbyTransitView( - currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + locationProvider: .init( + currentLocation: CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78), + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ), nearbyFetcher: FakeNearbyFetcher(), scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: FakePredictionsFetcher(), - viewportProvider: .init() + predictionsFetcher: FakePredictionsFetcher() ) XCTAssertNotNil(try sut.inspect().view(NearbyTransitView.self).find(text: "Failed to load predictions, test error")) } - @MainActor func testMapPanReloads() async throws { + @MainActor func testReloadsWhenLocationChanges() throws { class FakeNearbyFetcher: NearbyFetcher { var getNearbyExpectation: XCTestExpectation - var passedLocation: CLLocationCoordinate2D? init(getNearbyExpectation: XCTestExpectation) { self.getNearbyExpectation = getNearbyExpectation @@ -585,7 +629,7 @@ final class NearbyTransitViewTests: XCTestCase { } override func getNearby(location: CLLocationCoordinate2D) async { - passedLocation = location + loadedLocation = location getNearbyExpectation.fulfill() } } @@ -593,63 +637,48 @@ final class NearbyTransitViewTests: XCTestCase { let getNearbyExpectation = expectation(description: "getNearby") getNearbyExpectation.expectedFulfillmentCount = 2 - let viewportProvider = ViewportProvider() let fakeFetcher = FakeNearbyFetcher(getNearbyExpectation: getNearbyExpectation) let currentLocation = CLLocationCoordinate2D(latitude: 12.34, longitude: -56.78) - var sut = NearbyTransitView( + let locationProvider: NearbyTransitLocationProvider = .init( currentLocation: currentLocation, + cameraLocation: ViewportProvider.defaultCenter, + isFollowing: true + ) + + let sut = NearbyTransitView( + locationProvider: locationProvider, nearbyFetcher: fakeFetcher, scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: .init(socket: MockSocket()), - viewportProvider: viewportProvider + predictionsFetcher: .init(socket: MockSocket()) ) - let hasAppeared = sut.on(\NearbyTransitView.didAppear) { _ in } - ViewHosting.host(view: sut) - - await fulfillment(of: [hasAppeared], timeout: 5) - XCTAssertEqual(fakeFetcher.passedLocation, currentLocation) - let newLocation = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0) - viewportProvider.cameraState = .init(center: newLocation, padding: .zero, zoom: 1, bearing: 0.0, pitch: 0.0) - await fulfillment(of: [getNearbyExpectation], timeout: 1) - XCTAssertEqual(fakeFetcher.passedLocation, newLocation) - } - func testLoadsDefaultCenterWithNoLocation() throws { - class FakeNearbyFetcher: NearbyFetcher { - var getNearbyExpectation: XCTestExpectation - var passedLocation: CLLocationCoordinate2D? - - init(getNearbyExpectation: XCTestExpectation) { - self.getNearbyExpectation = getNearbyExpectation - super.init(backend: IdleBackend()) - } + let hasAppeared = sut.inspection.inspect(after: 0.2) { view in + XCTAssertEqual(try view.actualView().nearbyFetcher.loadedLocation, currentLocation) + } - override func getNearby(location: CLLocationCoordinate2D) async { - passedLocation = location - getNearbyExpectation.fulfill() - } + let hasChangedLocation = sut.inspection.inspect(onReceive: locationProvider.$location.dropFirst()) { view in + XCTAssertEqual(try view.actualView().locationProvider.location, newLocation) } - let getNearbyExpectation = expectation(description: "getNearby") + ViewHosting.host(view: sut) + wait(for: [hasAppeared], timeout: 1) + locationProvider.location = newLocation + wait(for: [getNearbyExpectation, hasChangedLocation], timeout: 3) + } - let viewportProvider = ViewportProvider() - let fakeFetcher = FakeNearbyFetcher(getNearbyExpectation: getNearbyExpectation) + func testLocationProviderResolvesProperly() { + let cameraLocation = CLLocationCoordinate2D(latitude: 1.0, longitude: 1.0) + let currentLocation = CLLocationCoordinate2D(latitude: 2.0, longitude: 2.0) - var sut = NearbyTransitView( - currentLocation: nil, - nearbyFetcher: fakeFetcher, - scheduleFetcher: .init(backend: IdleBackend()), - predictionsFetcher: .init(socket: MockSocket()), - viewportProvider: viewportProvider - ) + let nilCurrentProvider: NearbyTransitLocationProvider = .init(currentLocation: nil, cameraLocation: cameraLocation, isFollowing: true) + XCTAssertEqual(nilCurrentProvider.location, cameraLocation) - let hasAppeared = sut.on(\NearbyTransitView.didAppear) { _ in } - ViewHosting.host(view: sut) + let cameraProvider: NearbyTransitLocationProvider = .init(currentLocation: currentLocation, cameraLocation: cameraLocation, isFollowing: false) + XCTAssertEqual(cameraProvider.location, cameraLocation) - wait(for: [hasAppeared], timeout: 1) - wait(for: [getNearbyExpectation], timeout: 1) - XCTAssertEqual(fakeFetcher.passedLocation, ViewportProvider.defaultCenter) + let currentProvider: NearbyTransitLocationProvider = .init(currentLocation: currentLocation, cameraLocation: cameraLocation, isFollowing: true) + XCTAssertEqual(currentProvider.location, currentLocation) } }