From 72a53ec9b71cda5bc9e5940f8e3b8572bd90e1be Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 15 Nov 2024 09:00:57 -0500 Subject: [PATCH 1/2] Add a test to document how 'task tree' cancellation happens. --- .../StoreTests.swift | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Tests/ComposableArchitectureTests/StoreTests.swift b/Tests/ComposableArchitectureTests/StoreTests.swift index 50037cd87877..602f1a2c2c4f 100644 --- a/Tests/ComposableArchitectureTests/StoreTests.swift +++ b/Tests/ComposableArchitectureTests/StoreTests.swift @@ -2,6 +2,10 @@ @_spi(Internals) import ComposableArchitecture import XCTest +#if canImport(Testing) + import Testing +#endif + final class StoreTests: BaseTCATestCase { var cancellables: Set = [] @@ -1172,6 +1176,51 @@ final class StoreTests: BaseTCATestCase { } } +@Suite +struct ModernStoreTests { + @Reducer + fileprivate struct TaskTreeFeature { + let clock: TestClock + @ObservableState + struct State { var count = 0 } + enum Action { case tap, response1, response2 } + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .tap: + return Effect.run { send in + await send(.response1) + } + case .response1: + state.count = 42 + return Effect.run { send in + try await clock.sleep(for: .seconds(1)) + await send(.response2) + } + case .response2: + state.count = 1729 + return .none + } + } + } + } + + @MainActor + @Test + func cancellation() async throws { + let clock = TestClock() + let store = Store(initialState: TaskTreeFeature.State()) { TaskTreeFeature(clock: clock) } + let task = store.send(.tap) + try await Task.sleep(for: .seconds(0.1)) + #expect(store.count == 42) + task.cancel() + await clock.run() + withKnownIssue("Cancelling the root effect should not cancel the child effects.") { + #expect(store.count == 1729) + } + } +} + private struct Count: TestDependencyKey { var value: Int static let liveValue = Count(value: 0) From 155efa2f8f446f0f59249403897f7ad70e984c3b Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 15 Nov 2024 09:12:16 -0500 Subject: [PATCH 2/2] fix --- .../StoreTests.swift | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/Tests/ComposableArchitectureTests/StoreTests.swift b/Tests/ComposableArchitectureTests/StoreTests.swift index 602f1a2c2c4f..7ef9bccebecd 100644 --- a/Tests/ComposableArchitectureTests/StoreTests.swift +++ b/Tests/ComposableArchitectureTests/StoreTests.swift @@ -1176,50 +1176,52 @@ final class StoreTests: BaseTCATestCase { } } -@Suite -struct ModernStoreTests { - @Reducer - fileprivate struct TaskTreeFeature { - let clock: TestClock - @ObservableState - struct State { var count = 0 } - enum Action { case tap, response1, response2 } - var body: some ReducerOf { - Reduce { state, action in - switch action { - case .tap: - return Effect.run { send in - await send(.response1) - } - case .response1: - state.count = 42 - return Effect.run { send in - try await clock.sleep(for: .seconds(1)) - await send(.response2) +#if canImport(Testing) + @Suite + struct ModernStoreTests { + @Reducer + fileprivate struct TaskTreeFeature { + let clock: TestClock + @ObservableState + struct State { var count = 0 } + enum Action { case tap, response1, response2 } + var body: some ReducerOf { + Reduce { state, action in + switch action { + case .tap: + return Effect.run { send in + await send(.response1) + } + case .response1: + state.count = 42 + return Effect.run { send in + try await clock.sleep(for: .seconds(1)) + await send(.response2) + } + case .response2: + state.count = 1729 + return .none } - case .response2: - state.count = 1729 - return .none } } } - } - @MainActor - @Test - func cancellation() async throws { - let clock = TestClock() - let store = Store(initialState: TaskTreeFeature.State()) { TaskTreeFeature(clock: clock) } - let task = store.send(.tap) - try await Task.sleep(for: .seconds(0.1)) - #expect(store.count == 42) - task.cancel() - await clock.run() - withKnownIssue("Cancelling the root effect should not cancel the child effects.") { - #expect(store.count == 1729) + @MainActor + @Test + func cancellation() async throws { + let clock = TestClock() + let store = Store(initialState: TaskTreeFeature.State()) { TaskTreeFeature(clock: clock) } + let task = store.send(.tap) + try await Task.sleep(for: .seconds(0.1)) + #expect(store.count == 42) + task.cancel() + await clock.run() + withKnownIssue("Cancelling the root effect should not cancel the child effects.") { + #expect(store.count == 1729) + } } } -} +#endif private struct Count: TestDependencyKey { var value: Int