diff --git a/Tests/ComposableArchitectureTests/EffectTests.swift b/Tests/ComposableArchitectureTests/EffectTests.swift index c44af5a4d..a88dbc699 100644 --- a/Tests/ComposableArchitectureTests/EffectTests.swift +++ b/Tests/ComposableArchitectureTests/EffectTests.swift @@ -4,12 +4,12 @@ import XCTest // `@MainActor` introduces issues gathering tests on Linux #if !os(Linux) -@MainActor -final class EffectTests: XCTestCase { + @MainActor + final class EffectTests: XCTestCase { let mainQueue = TestScheduler() func testEraseToEffectWithError() { - struct Error: Swift.Error, Equatable {} + struct Error: Swift.Error, Equatable {} SignalProducer(result: .success(42)) .startWithResult { XCTAssertNoDifference($0, .success(42)) } @@ -21,112 +21,112 @@ final class EffectTests: XCTestCase { .startWithResult { XCTAssertNoDifference($0, .success(42)) } SignalProducer(result: .success(42)) - .catchToEffect { - switch $0 { - case let .success(val): - return val - case .failure: - return -1 + .catchToEffect { + switch $0 { + case let .success(val): + return val + case .failure: + return -1 + } } - } .producer .startWithValues { XCTAssertNoDifference($0, 42) } SignalProducer(result: .failure(Error())) - .catchToEffect { - switch $0 { - case let .success(val): - return val - case .failure: - return -1 + .catchToEffect { + switch $0 { + case let .success(val): + return val + case .failure: + return -1 + } } - } .producer .startWithValues { XCTAssertNoDifference($0, -1) } - } + } - #if swift(>=5.7) && (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) - func testConcatenate() async { - if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) { - let clock = TestClock() - var values: [Int] = [] - - let effect = Effect.concatenate( - (1...3).map { count in - .task { - try await clock.sleep(for: .seconds(count)) - return count + #if swift(>=5.7) && (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) + func testConcatenate() async { + if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) { + let clock = TestClock() + var values: [Int] = [] + + let effect = Effect.concatenate( + (1...3).map { count in + .task { + try await clock.sleep(for: .seconds(count)) + return count + } } - } - ) + ) effect.producer.startWithValues { values.append($0) } - XCTAssertEqual(values, []) + XCTAssertEqual(values, []) - await clock.advance(by: .seconds(1)) - XCTAssertEqual(values, [1]) + await clock.advance(by: .seconds(1)) + XCTAssertEqual(values, [1]) - await clock.advance(by: .seconds(2)) - XCTAssertEqual(values, [1, 2]) + await clock.advance(by: .seconds(2)) + XCTAssertEqual(values, [1, 2]) - await clock.advance(by: .seconds(3)) - XCTAssertEqual(values, [1, 2, 3]) + await clock.advance(by: .seconds(3)) + XCTAssertEqual(values, [1, 2, 3]) - await clock.run() - XCTAssertEqual(values, [1, 2, 3]) + await clock.run() + XCTAssertEqual(values, [1, 2, 3]) + } } - } - #endif + #endif - func testConcatenateOneEffect() { - var values: [Int] = [] + func testConcatenateOneEffect() { + var values: [Int] = [] - let effect = EffectTask.concatenate( + let effect = EffectTask.concatenate( EffectTask(value: 1).deferred(for: 1, scheduler: mainQueue) - ) + ) effect.producer.startWithValues { values.append($0) } - XCTAssertEqual(values, []) - - self.mainQueue.advance(by: 1) - XCTAssertEqual(values, [1]) + XCTAssertEqual(values, []) - self.mainQueue.run() - XCTAssertEqual(values, [1]) - } + self.mainQueue.advance(by: 1) + XCTAssertEqual(values, [1]) - #if swift(>=5.7) && (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) - func testMerge() async { - if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) { - let clock = TestClock() + self.mainQueue.run() + XCTAssertEqual(values, [1]) + } - let effect = Effect.merge( - (1...3).map { count in - .task { - try await clock.sleep(for: .seconds(count)) - return count + #if swift(>=5.7) && (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) + func testMerge() async { + if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) { + let clock = TestClock() + + let effect = Effect.merge( + (1...3).map { count in + .task { + try await clock.sleep(for: .seconds(count)) + return count + } } - } - ) + ) - var values: [Int] = [] + var values: [Int] = [] effect.producer.startWithValues { values.append($0) } - XCTAssertEqual(values, []) + XCTAssertEqual(values, []) - await clock.advance(by: .seconds(1)) - XCTAssertEqual(values, [1]) + await clock.advance(by: .seconds(1)) + XCTAssertEqual(values, [1]) - await clock.advance(by: .seconds(1)) - XCTAssertEqual(values, [1, 2]) + await clock.advance(by: .seconds(1)) + XCTAssertEqual(values, [1, 2]) - await clock.advance(by: .seconds(1)) - XCTAssertEqual(values, [1, 2, 3]) + await clock.advance(by: .seconds(1)) + XCTAssertEqual(values, [1, 2, 3]) + } } - } - #endif + #endif func testEffectRunInitializer() { let effect = EffectTask.run { observer in @@ -134,226 +134,226 @@ final class EffectTests: XCTestCase { observer.send(value: 2) self.mainQueue.schedule(after: self.mainQueue.currentDate.addingTimeInterval(1)) { observer.send(value: 3) - } + } self.mainQueue.schedule(after: self.mainQueue.currentDate.addingTimeInterval(2)) { observer.send(value: 4) observer.sendCompleted() - } + } return AnyDisposable() - } + } - var values: [Int] = [] - var isComplete = false - effect + var values: [Int] = [] + var isComplete = false + effect .producer .on(completed: { isComplete = true }, value: { values.append($0) }) .start() - XCTAssertEqual(values, [1, 2]) - XCTAssertEqual(isComplete, false) + XCTAssertEqual(values, [1, 2]) + XCTAssertEqual(isComplete, false) - self.mainQueue.advance(by: 1) + self.mainQueue.advance(by: 1) - XCTAssertEqual(values, [1, 2, 3]) - XCTAssertEqual(isComplete, false) + XCTAssertEqual(values, [1, 2, 3]) + XCTAssertEqual(isComplete, false) - self.mainQueue.advance(by: 1) + self.mainQueue.advance(by: 1) - XCTAssertEqual(values, [1, 2, 3, 4]) - XCTAssertEqual(isComplete, true) - } + XCTAssertEqual(values, [1, 2, 3, 4]) + XCTAssertEqual(isComplete, true) + } func testEffectRunInitializer_WithCancellation() { - enum CancelID {} + enum CancelID {} - let effect = EffectTask.run { subscriber in + let effect = EffectTask.run { subscriber in subscriber.send(value: 1) self.mainQueue.schedule(after: self.mainQueue.currentDate.addingTimeInterval(1)) { subscriber.send(value: 2) - } + } return AnyDisposable() - } - .cancellable(id: CancelID.self) + } + .cancellable(id: CancelID.self) - var values: [Int] = [] - var isComplete = false - effect + var values: [Int] = [] + var isComplete = false + effect .producer .on(completed: { isComplete = true }) .startWithValues { values.append($0) } - XCTAssertEqual(values, [1]) - XCTAssertEqual(isComplete, false) + XCTAssertEqual(values, [1]) + XCTAssertEqual(isComplete, false) - EffectTask.cancel(id: CancelID.self) + EffectTask.cancel(id: CancelID.self) .producer .startWithValues { _ in } - self.mainQueue.advance(by: 1) + self.mainQueue.advance(by: 1) - XCTAssertEqual(values, [1]) - XCTAssertEqual(isComplete, true) - } + XCTAssertEqual(values, [1]) + XCTAssertEqual(isComplete, true) + } - func testDoubleCancelInFlight() { - var result: Int? + func testDoubleCancelInFlight() { + var result: Int? _ = Effect(value: 42) - .cancellable(id: "id", cancelInFlight: true) - .cancellable(id: "id", cancelInFlight: true) + .cancellable(id: "id", cancelInFlight: true) + .cancellable(id: "id", cancelInFlight: true) .producer .startWithValues { result = $0 } - XCTAssertEqual(result, 42) - } + XCTAssertEqual(result, 42) + } #if DEBUG && !os(Linux) - func testUnimplemented() { + func testUnimplemented() { let effect = EffectTask.failing("unimplemented") _ = XCTExpectFailure { - effect + effect .producer .start() - } issueMatcher: { issue in - issue.compactDescription == "unimplemented - An unimplemented effect ran." + } issueMatcher: { issue in + issue.compactDescription == "unimplemented - An unimplemented effect ran." + } } - } - #endif + #endif #if canImport(_Concurrency) && compiler(>=5.5.2) - func testTask() async { - guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else { return } - let effect = EffectTask.task { 42 } + func testTask() async { + guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else { return } + let effect = EffectTask.task { 42 } for await result in effect.producer.values { - XCTAssertEqual(result, 42) - } - } - - func testCancellingTask_Infallible() { - @Sendable func work() async -> Int { - do { - try await Task.sleep(nanoseconds: NSEC_PER_MSEC) - XCTFail() - } catch { + XCTAssertEqual(result, 42) + } } - return 42 - } + + func testCancellingTask_Infallible() { + @Sendable func work() async -> Int { + do { + try await Task.sleep(nanoseconds: NSEC_PER_MSEC) + XCTFail() + } catch { + } + return 42 + } let disposable = EffectTask.task { await work() } .producer .on( completed: { XCTFail() }, value: { _ in XCTFail() } - ) + ) .start(on: QueueScheduler.main) .start() disposable.dispose() - _ = XCTWaiter.wait(for: [.init()], timeout: 1.1) - } + _ = XCTWaiter.wait(for: [.init()], timeout: 1.1) + } #endif - func testDependenciesTransferredToEffects_Task() async { - struct Feature: ReducerProtocol { - enum Action: Equatable { - case tap - case response(Int) - } - @Dependency(\.date) var date - func reduce(into state: inout Int, action: Action) -> EffectTask { - switch action { - case .tap: - return .task { - .response(Int(self.date.now.timeIntervalSinceReferenceDate)) + func testDependenciesTransferredToEffects_Task() async { + struct Feature: ReducerProtocol { + enum Action: Equatable { + case tap + case response(Int) + } + @Dependency(\.date) var date + func reduce(into state: inout Int, action: Action) -> EffectTask { + switch action { + case .tap: + return .task { + .response(Int(self.date.now.timeIntervalSinceReferenceDate)) + } + case let .response(value): + state = value + return .none } - case let .response(value): - state = value - return .none } } - } - let store = TestStore( - initialState: 0, - reducer: Feature() - .dependency(\.date, .constant(.init(timeIntervalSinceReferenceDate: 1_234_567_890))) - ) - - await store.send(.tap).finish(timeout: NSEC_PER_SEC) - await store.receive(.response(1_234_567_890)) { - $0 = 1_234_567_890 - } - } + let store = TestStore( + initialState: 0, + reducer: Feature() + .dependency(\.date, .constant(.init(timeIntervalSinceReferenceDate: 1_234_567_890))) + ) - func testDependenciesTransferredToEffects_Run() async { - struct Feature: ReducerProtocol { - enum Action: Equatable { - case tap - case response(Int) + await store.send(.tap).finish(timeout: NSEC_PER_SEC) + await store.receive(.response(1_234_567_890)) { + $0 = 1_234_567_890 } - @Dependency(\.date) var date - func reduce(into state: inout Int, action: Action) -> Effect { - switch action { - case .tap: - return .run { send in - await send(.response(Int(self.date.now.timeIntervalSinceReferenceDate))) + } + + func testDependenciesTransferredToEffects_Run() async { + struct Feature: ReducerProtocol { + enum Action: Equatable { + case tap + case response(Int) + } + @Dependency(\.date) var date + func reduce(into state: inout Int, action: Action) -> Effect { + switch action { + case .tap: + return .run { send in + await send(.response(Int(self.date.now.timeIntervalSinceReferenceDate))) + } + case let .response(value): + state = value + return .none } - case let .response(value): - state = value - return .none } } - } - let store = TestStore( - initialState: 0, - reducer: Feature() - .dependency(\.date, .constant(.init(timeIntervalSinceReferenceDate: 1_234_567_890))) - ) - - await store.send(.tap).finish(timeout: NSEC_PER_SEC) - await store.receive(.response(1_234_567_890)) { - $0 = 1_234_567_890 - } - } + let store = TestStore( + initialState: 0, + reducer: Feature() + .dependency(\.date, .constant(.init(timeIntervalSinceReferenceDate: 1_234_567_890))) + ) - func testMap() async { - @Dependency(\.date) var date - let effect = - DependencyValues - .withValue(\.date, .init { Date(timeIntervalSince1970: 1_234_567_890) }) { - EffectTask(value: ()) - .map { date() } + await store.send(.tap).finish(timeout: NSEC_PER_SEC) + await store.receive(.response(1_234_567_890)) { + $0 = 1_234_567_890 } - var output: Date? - effect - .producer - .startWithValues { output = $0 } - XCTAssertEqual(output, Date(timeIntervalSince1970: 1_234_567_890)) + } - if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { + func testMap() async { + @Dependency(\.date) var date let effect = DependencyValues .withValue(\.date, .init { Date(timeIntervalSince1970: 1_234_567_890) }) { - EffectTask.task {} + EffectTask(value: ()) .map { date() } } - output = await effect.values.first(where: { _ in true }) + var output: Date? + effect + .producer + .startWithValues { output = $0 } XCTAssertEqual(output, Date(timeIntervalSince1970: 1_234_567_890)) + + if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) { + let effect = + DependencyValues + .withValue(\.date, .init { Date(timeIntervalSince1970: 1_234_567_890) }) { + EffectTask.task {} + .map { date() } + } + output = await effect.values.first(where: { _ in true }) + XCTAssertEqual(output, Date(timeIntervalSince1970: 1_234_567_890)) + } } - } - func testCanary1() async { - for _ in 1...100 { - let task = TestStoreTask(rawValue: Task {}, timeout: NSEC_PER_SEC) - await task.finish() + func testCanary1() async { + for _ in 1...100 { + let task = TestStoreTask(rawValue: Task {}, timeout: NSEC_PER_SEC) + await task.finish() + } } - } - func testCanary2() async { - for _ in 1...100 { - let task = TestStoreTask(rawValue: nil, timeout: NSEC_PER_SEC) - await task.finish() + func testCanary2() async { + for _ in 1...100 { + let task = TestStoreTask(rawValue: nil, timeout: NSEC_PER_SEC) + await task.finish() + } } } -} #endif diff --git a/Tests/ComposableArchitectureTests/ReducerTests.swift b/Tests/ComposableArchitectureTests/ReducerTests.swift index 0127756ea..17da5087e 100644 --- a/Tests/ComposableArchitectureTests/ReducerTests.swift +++ b/Tests/ComposableArchitectureTests/ReducerTests.swift @@ -5,254 +5,254 @@ import XCTest // `@MainActor` introduces issues gathering tests on Linux #if !os(Linux) -@MainActor -final class ReducerTests: XCTestCase { - func testCallableAsFunction() { - let reducer = Reduce { state, _ in - state += 1 - return .none - } + @MainActor + final class ReducerTests: XCTestCase { + func testCallableAsFunction() { + let reducer = Reduce { state, _ in + state += 1 + return .none + } - var state = 0 - _ = reducer.reduce(into: &state, action: ()) - XCTAssertEqual(state, 1) - } + var state = 0 + _ = reducer.reduce(into: &state, action: ()) + XCTAssertEqual(state, 1) + } - #if swift(>=5.7) && (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) - func testCombine_EffectsAreMerged() async throws { - if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) { - enum Action: Equatable { - case increment - } + #if swift(>=5.7) && (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst)) + func testCombine_EffectsAreMerged() async throws { + if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) { + enum Action: Equatable { + case increment + } - struct Delayed: ReducerProtocol { - typealias State = Int + struct Delayed: ReducerProtocol { + typealias State = Int - @Dependency(\.continuousClock) var clock + @Dependency(\.continuousClock) var clock - let delay: Duration - let setValue: @Sendable () async -> Void + let delay: Duration + let setValue: @Sendable () async -> Void - func reduce(into state: inout State, action: Action) -> EffectTask { - state += 1 - return .fireAndForget { - try await self.clock.sleep(for: self.delay) - await self.setValue() + func reduce(into state: inout State, action: Action) -> EffectTask { + state += 1 + return .fireAndForget { + try await self.clock.sleep(for: self.delay) + await self.setValue() + } } } - } - var fastValue: Int? = nil - var slowValue: Int? = nil + var fastValue: Int? = nil + var slowValue: Int? = nil - let store = TestStore( - initialState: 0, - reducer: CombineReducers { - Delayed(delay: .seconds(1), setValue: { @MainActor in fastValue = 42 }) - Delayed(delay: .seconds(2), setValue: { @MainActor in slowValue = 1729 }) - } - ) + let store = TestStore( + initialState: 0, + reducer: CombineReducers { + Delayed(delay: .seconds(1), setValue: { @MainActor in fastValue = 42 }) + Delayed(delay: .seconds(2), setValue: { @MainActor in slowValue = 1729 }) + } + ) - let clock = TestClock() - store.dependencies.continuousClock = clock + let clock = TestClock() + store.dependencies.continuousClock = clock - await store.send(.increment) { - $0 = 2 + await store.send(.increment) { + $0 = 2 + } + // Waiting a second causes the fast effect to fire. + await clock.advance(by: .seconds(1)) + try await Task.sleep(nanoseconds: NSEC_PER_SEC / 3) + XCTAssertEqual(fastValue, 42) + XCTAssertEqual(slowValue, nil) + // Waiting one more second causes the slow effect to fire. This proves that the effects + // are merged together, as opposed to concatenated. + await clock.advance(by: .seconds(1)) + await store.finish() + XCTAssertEqual(fastValue, 42) + XCTAssertEqual(slowValue, 1729) } - // Waiting a second causes the fast effect to fire. - await clock.advance(by: .seconds(1)) - try await Task.sleep(nanoseconds: NSEC_PER_SEC / 3) - XCTAssertEqual(fastValue, 42) - XCTAssertEqual(slowValue, nil) - // Waiting one more second causes the slow effect to fire. This proves that the effects - // are merged together, as opposed to concatenated. - await clock.advance(by: .seconds(1)) - await store.finish() - XCTAssertEqual(fastValue, 42) - XCTAssertEqual(slowValue, 1729) } - } - #endif + #endif - func testCombine() async { - enum Action: Equatable { - case increment - } + func testCombine() async { + enum Action: Equatable { + case increment + } - struct One: ReducerProtocol { - typealias State = Int - let effect: @Sendable () async -> Void - func reduce(into state: inout State, action: Action) -> EffectTask { - state += 1 - return .fireAndForget { - await self.effect() + struct One: ReducerProtocol { + typealias State = Int + let effect: @Sendable () async -> Void + func reduce(into state: inout State, action: Action) -> EffectTask { + state += 1 + return .fireAndForget { + await self.effect() + } } } - } - var first = false - var second = false + var first = false + var second = false - let store = TestStore( - initialState: 0, - reducer: CombineReducers { - One(effect: { @MainActor in first = true }) - One(effect: { @MainActor in second = true }) - } - ) + let store = TestStore( + initialState: 0, + reducer: CombineReducers { + One(effect: { @MainActor in first = true }) + One(effect: { @MainActor in second = true }) + } + ) - await store - .send(.increment) { $0 = 2 } - .finish() + await store + .send(.increment) { $0 = 2 } + .finish() - XCTAssertTrue(first) - XCTAssertTrue(second) - } + XCTAssertTrue(first) + XCTAssertTrue(second) + } - #if DEBUG - func testDebug() async { - enum DebugAction: Equatable { - case incrWithBool(Bool) - case incr, noop - } - struct DebugState: Equatable { var count = 0 } - - var logs: [String] = [] - let logsExpectation = self.expectation(description: "logs") - logsExpectation.expectedFulfillmentCount = 2 - - let reducer = AnyReducer { state, action, _ in - switch action { - case .incrWithBool: - return .none - case .incr: - state.count += 1 - return .none - case .noop: - return .none + #if DEBUG + func testDebug() async { + enum DebugAction: Equatable { + case incrWithBool(Bool) + case incr, noop } - } - .debug("[prefix]") { _ in - DebugEnvironment( - printer: { - logs.append($0) - logsExpectation.fulfill() + struct DebugState: Equatable { var count = 0 } + + var logs: [String] = [] + let logsExpectation = self.expectation(description: "logs") + logsExpectation.expectedFulfillmentCount = 2 + + let reducer = AnyReducer { state, action, _ in + switch action { + case .incrWithBool: + return .none + case .incr: + state.count += 1 + return .none + case .noop: + return .none } + } + .debug("[prefix]") { _ in + DebugEnvironment( + printer: { + logs.append($0) + logsExpectation.fulfill() + } + ) + } + + let store = TestStore( + initialState: .init(), + reducer: reducer, + environment: () + ) + await store.send(.incr) { $0.count = 1 } + await store.send(.noop) + + self.wait(for: [logsExpectation], timeout: 5) + + XCTAssertEqual( + logs, + [ + #""" + [prefix]: received action: + ReducerTests.DebugAction.incr + - ReducerTests.DebugState(count: 0) + + ReducerTests.DebugState(count: 1) + + """#, + #""" + [prefix]: received action: + ReducerTests.DebugAction.noop + (No state changes) + + """#, + ] ) } - let store = TestStore( - initialState: .init(), - reducer: reducer, - environment: () - ) - await store.send(.incr) { $0.count = 1 } - await store.send(.noop) - - self.wait(for: [logsExpectation], timeout: 5) - - XCTAssertEqual( - logs, - [ - #""" - [prefix]: received action: - ReducerTests.DebugAction.incr - - ReducerTests.DebugState(count: 0) - + ReducerTests.DebugState(count: 1) - - """#, - #""" - [prefix]: received action: - ReducerTests.DebugAction.noop - (No state changes) - - """#, - ] - ) - } - - func testDebug_ActionFormat_OnlyLabels() { - enum DebugAction: Equatable { - case incrWithBool(Bool) - case incr, noop - } - struct DebugState: Equatable { var count = 0 } - - var logs: [String] = [] - let logsExpectation = self.expectation(description: "logs") - - let reducer = AnyReducer { state, action, _ in - switch action { - case let .incrWithBool(bool): - state.count += bool ? 1 : 0 - return .none - default: - return .none + func testDebug_ActionFormat_OnlyLabels() { + enum DebugAction: Equatable { + case incrWithBool(Bool) + case incr, noop } - } - .debug("[prefix]", actionFormat: .labelsOnly) { _ in - DebugEnvironment( - printer: { - logs.append($0) - logsExpectation.fulfill() + struct DebugState: Equatable { var count = 0 } + + var logs: [String] = [] + let logsExpectation = self.expectation(description: "logs") + + let reducer = AnyReducer { state, action, _ in + switch action { + case let .incrWithBool(bool): + state.count += bool ? 1 : 0 + return .none + default: + return .none } - ) - } + } + .debug("[prefix]", actionFormat: .labelsOnly) { _ in + DebugEnvironment( + printer: { + logs.append($0) + logsExpectation.fulfill() + } + ) + } - let viewStore = ViewStore( - Store( - initialState: .init(), - reducer: reducer, - environment: () + let viewStore = ViewStore( + Store( + initialState: .init(), + reducer: reducer, + environment: () + ) ) - ) - viewStore.send(.incrWithBool(true)) + viewStore.send(.incrWithBool(true)) - self.wait(for: [logsExpectation], timeout: 5) + self.wait(for: [logsExpectation], timeout: 5) - XCTAssertEqual( - logs, - [ - #""" - [prefix]: received action: - ReducerTests.DebugAction.incrWithBool - - ReducerTests.DebugState(count: 0) - + ReducerTests.DebugState(count: 1) + XCTAssertEqual( + logs, + [ + #""" + [prefix]: received action: + ReducerTests.DebugAction.incrWithBool + - ReducerTests.DebugState(count: 0) + + ReducerTests.DebugState(count: 1) - """# - ] - ) - } - #endif + """# + ] + ) + } + #endif #if canImport(os) @available(iOS 12.0, *) - func testDefaultSignpost() { - let reducer = EmptyReducer().signpost(log: .default) - var n = 0 - let effect = reducer.reduce(into: &n, action: ()) - let expectation = self.expectation(description: "effect") - effect + func testDefaultSignpost() { + let reducer = EmptyReducer().signpost(log: .default) + var n = 0 + let effect = reducer.reduce(into: &n, action: ()) + let expectation = self.expectation(description: "effect") + effect .producer .startWithCompleted { expectation.fulfill() } - self.wait(for: [expectation], timeout: 0.1) - } + self.wait(for: [expectation], timeout: 0.1) + } @available(iOS 12.0, *) - func testDisabledSignpost() { - let reducer = EmptyReducer().signpost(log: .disabled) - var n = 0 - let effect = reducer.reduce(into: &n, action: ()) - let expectation = self.expectation(description: "effect") - effect + func testDisabledSignpost() { + let reducer = EmptyReducer().signpost(log: .disabled) + var n = 0 + let effect = reducer.reduce(into: &n, action: ()) + let expectation = self.expectation(description: "effect") + effect .producer .startWithCompleted { expectation.fulfill() } - self.wait(for: [expectation], timeout: 0.1) - } + self.wait(for: [expectation], timeout: 0.1) + } #endif -} + } #endif