From 857cee6da1d66d87891637a811ae49175c47f698 Mon Sep 17 00:00:00 2001 From: Ryu0118 Date: Tue, 19 Sep 2023 23:44:05 +0900 Subject: [PATCH] Add Pullback Tests --- .../Store/Store+send.swift | 41 +++++---- .../SimplexArchitectureTests/StoreTests.swift | 90 ++++++++++++++++++- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/Sources/SimplexArchitecture/Store/Store+send.swift b/Sources/SimplexArchitecture/Store/Store+send.swift index 8e2d05b..4159938 100644 --- a/Sources/SimplexArchitecture/Store/Store+send.swift +++ b/Sources/SimplexArchitecture/Store/Store+send.swift @@ -30,13 +30,7 @@ extension Store { _ action: Reducer.Action, container: StateContainer ) -> SendTask { - defer { - if let _ = Reducer.Action.self as? Pullbackable.Type, let pullbackAction { - pullbackAction(action) - } - } - - return sendAction(.action(action), container: container) + sendAction(.action(action), container: container) } @usableFromInline @@ -44,15 +38,7 @@ extension Store { _ action: Reducer.ReducerAction, container: StateContainer ) -> SendTask { - defer { - if let _ = Reducer.ReducerAction.self as? Pullbackable.Type, - let pullbackReducerAction - { - pullbackReducerAction(action) - } - } - - return sendAction(.action(action), container: container) + sendAction(.action(action), container: container) } @inline(__always) @@ -60,6 +46,29 @@ extension Store { _ action: CombineAction, container: StateContainer ) -> SendTask { + defer { + switch action.kind { + case .viewAction(let action): + guard let pullbackAction else { + break + } + if let _ = Reducer.Action.self as? Pullbackable.Type { + pullbackAction(action) + } else { + runtimeWarning("\(Reducer.Action.self) must be conformed to Pullbackable in order to pullback to parent reducer") + } + case .reducerAction(let action): + guard let pullbackReducerAction else { + break + } + if let _ = Reducer.ReducerAction.self as? Pullbackable.Type { + pullbackReducerAction(action) + } else { + runtimeWarning("\(Reducer.ReducerAction.self) must be conformed to Pullbackable in order to pullback to parent reducer") + } + } + } + let sideEffect: SideEffect // If Unit Testing is in progress and an action is sent from SideEffect if _XCTIsTesting, let effectContext = EffectContext.id { diff --git a/Tests/SimplexArchitectureTests/StoreTests.swift b/Tests/SimplexArchitectureTests/StoreTests.swift index 702d982..f545a59 100644 --- a/Tests/SimplexArchitectureTests/StoreTests.swift +++ b/Tests/SimplexArchitectureTests/StoreTests.swift @@ -1,6 +1,7 @@ @testable import SimplexArchitecture import SwiftUI import XCTest +import CasePaths final class StoreTests: XCTestCase { fileprivate var store: Store! @@ -95,15 +96,100 @@ final class StoreTests: XCTestCase { XCTAssertNotNil(sendTask2.task) } + + func testPullbackAction() throws { + let parent = ParentView() + parent.store.setContainerIfNeeded(for: parent, states: .init()) + store.setContainerIfNeeded(for: TestView()) + XCTAssertNil(store.pullbackAction) + store.pullback(to: /ParentReducer.Action.child, parent: parent) + store.sendIfNeeded(.c1) + XCTAssertNotNil(store.pullbackAction) + XCTAssertEqual(parent.store.container?.count, 1) + } + + func testPullbackReducerAction() throws { + let parent = ParentView() + parent.store.setContainerIfNeeded(for: parent, states: .init()) + store.setContainerIfNeeded(for: TestView()) + XCTAssertNil(store.pullbackReducerAction) + store.pullback(to: /ParentReducer.Action.childReducerAction, parent: parent) + store.sendIfNeeded(.c4) + XCTAssertNotNil(store.pullbackReducerAction) + XCTAssertEqual(parent.store.container?.count, 1) + } + + func testPullbackActionForId() throws { + let parent = ParentView() + parent.store.setContainerIfNeeded(for: parent, states: .init()) + store.setContainerIfNeeded(for: TestView()) + XCTAssertNil(store.pullbackAction) + let uuid = UUID() + store.pullback(to: /ParentReducer.Action.childId, parent: parent, id: uuid) + store.sendIfNeeded(.c1) + XCTAssertNotNil(store.pullbackAction) + XCTAssertEqual(parent.store.container?.id, uuid) + } + + func testPullbackReducerActionForId() throws { + let parent = ParentView() + parent.store.setContainerIfNeeded(for: parent, states: .init()) + store.setContainerIfNeeded(for: TestView()) + XCTAssertNil(store.pullbackReducerAction) + let uuid = UUID() + store.pullback(to: /ParentReducer.Action.childIdReducerAction, parent: parent, id: uuid) + store.sendIfNeeded(.c4) + XCTAssertNotNil(store.pullbackReducerAction) + XCTAssertEqual(parent.store.container?.id, uuid) + } +} + +@ScopeState +private struct ParentView: View { + @State var count = 0 + @State var id: UUID? + let store: Store = .init(reducer: ParentReducer()) + var body: some View { + EmptyView() + } +} + +private struct ParentReducer: ReducerProtocol { + enum Action { + case child(TestReducer.Action) + case childReducerAction(TestReducer.ReducerAction) + case childId(id: UUID, action: TestReducer.Action) + case childIdReducerAction(id: UUID, action: TestReducer.ReducerAction) + } + + func reduce(into state: StateContainer, action: Action) -> SideEffect { + switch action { + case .child: + state.count += 1 + return .none + + case .childReducerAction: + state.count += 1 + return .none + + case .childId(let id, _): + state.id = id + return .none + + case .childIdReducerAction(let id, _): + state.id = id + return .none + } + } } private struct TestReducer: ReducerProtocol { - enum ReducerAction { + enum ReducerAction: Equatable, Pullbackable { case c3 case c4 } - enum Action: Equatable { + enum Action: Equatable, Pullbackable { case c1 case c2 }