From 8b4e8ecbde11e7f9f96b7405c98736b93416d393 Mon Sep 17 00:00:00 2001 From: Alexander Ignatov Date: Sat, 27 May 2023 00:54:00 +0300 Subject: [PATCH] Add station domain tests --- .../StationDomain/StationReducer.swift | 8 +- .../StationDomainTests.swift | 140 ++++++++++++++++++ 2 files changed, 146 insertions(+), 2 deletions(-) diff --git a/BDZDelays/bdz-delays/Sources/StationDomain/StationReducer.swift b/BDZDelays/bdz-delays/Sources/StationDomain/StationReducer.swift index f7be62d..46356a6 100644 --- a/BDZDelays/bdz-delays/Sources/StationDomain/StationReducer.swift +++ b/BDZDelays/bdz-delays/Sources/StationDomain/StationReducer.swift @@ -67,7 +67,11 @@ public struct StationReducer: ReducerProtocol { } case .tick: - // Refresh on every new calendar minute. + // Auto-refresh only on every new calendar minute and if not errored. + guard state.loadingState != .failed else { + return .none + } + let isInTheSameMinute = state.lastUpdateTime .map { let components: Set = [.day, .hour, .minute] @@ -92,7 +96,7 @@ public struct StationReducer: ReducerProtocol { await .receive(TaskResult { try await stationRepository.fetchTrainsAtStation(station) }) - } + }.cancellable(id: TrainsTaskCancelID.self) case .receive(.success(let trains)): state.lastUpdateTime = now diff --git a/BDZDelays/bdz-delays/Tests/StationDomainTests/StationDomainTests.swift b/BDZDelays/bdz-delays/Tests/StationDomainTests/StationDomainTests.swift index 79b8c9e..c76aa0c 100644 --- a/BDZDelays/bdz-delays/Tests/StationDomainTests/StationDomainTests.swift +++ b/BDZDelays/bdz-delays/Tests/StationDomainTests/StationDomainTests.swift @@ -131,6 +131,146 @@ final class StationDomainTests: XCTestCase { $0.lastUpdateTime = Date(timeIntervalSinceReferenceDate: 1) } } + + func test_task_sendsTickEverySecond() async throws { + let clock = TestClock() + let store = TestStore( + initialState: .init( + station: .sofia + ), + reducer: StationReducer() + ) { + $0.continuousClock = clock + } + + store.exhaustivity = .off + + let task = await store.send(.task) + + for _ in 1...10 { + await clock.advance(by: .seconds(1)) + await store.receive(.tick) + } + + await task.cancel() // done by SwiftUI + } + + func test_tick_refreshesWhenInDifferrentMinute() async throws { + let dateInNextMinute = Date(timeIntervalSinceReferenceDate: 61) + + let store = TestStore( + initialState: .init( + station: .sofia, + lastUpdateTime: Date(timeIntervalSinceReferenceDate: 51) + ), + reducer: StationReducer() + ) { + $0.date.now = dateInNextMinute + $0.calendar = Calendar(identifier: .gregorian) + $0.stationRepository.fetchTrainsAtStation = { _ in [] } + } + + await store.send(.tick) + + await store.receive(.refresh) { + $0.loadingState = .loading + } + + await store.receive(.receive(.success([]))) { + $0.trains = [] + $0.lastUpdateTime = dateInNextMinute + $0.loadingState = .loaded + } + } + + func test_tick_doesNotRefreshWhenInSameMinute() async throws { + let store = TestStore( + initialState: .init( + station: .sofia, + lastUpdateTime: Date(timeIntervalSinceReferenceDate: 51) + ), + reducer: StationReducer() + ) { + $0.date.now = Date(timeIntervalSinceReferenceDate: 52) + $0.calendar = Calendar(identifier: .gregorian) + $0.stationRepository.fetchTrainsAtStation = { _ in [] } + } + + await store.send(.tick) + } + + func test_tick_refreshesWhenNoPreviousUpdateTime() async throws { + let dateInNextMinute = Date(timeIntervalSinceReferenceDate: 61) + + let store = TestStore( + initialState: .init( + station: .sofia, + lastUpdateTime: nil + ), + reducer: StationReducer() + ) { + $0.date.now = dateInNextMinute + $0.calendar = Calendar(identifier: .gregorian) + $0.stationRepository.fetchTrainsAtStation = { _ in [] } + } + + await store.send(.tick) + + await store.receive(.refresh) { + $0.loadingState = .loading + } + + await store.receive(.receive(.success([]))) { + $0.trains = [] + $0.lastUpdateTime = dateInNextMinute + $0.loadingState = .loaded + } + } + + func test_tick_doesNotRefreshWhenErrored() async throws { + let dateInNextMinute = Date(timeIntervalSinceReferenceDate: 61) + + let store = TestStore( + initialState: .init( + station: .sofia, + loadingState: .failed, + lastUpdateTime: Date(timeIntervalSinceReferenceDate: 51) + ), + reducer: StationReducer() + ) { + $0.date.now = dateInNextMinute + $0.calendar = Calendar(identifier: .gregorian) + $0.stationRepository.fetchTrainsAtStation = { _ in [] } + } + + await store.send(.tick) + } + + func test_finalize_cancelsEffects() async throws { + let clock = TestClock() + + let store = TestStore( + initialState: .init(station: .sofia), + reducer: StationReducer() + ) { + $0.stationRepository = StationRepository( + fetchTrainsAtStation: { _ in + for await _ in clock.timer(interval: .seconds(1)) { + return [] + } + return [] + } + ) + } + + await store.send(.refresh) { + $0.loadingState = .loading + } + + // do not advance clock so that the task is still in flight + + await store.send(.finalize) + } } // MARK: - Helpers