Skip to content

Commit 997de56

Browse files
authored
Merge pull request #31 from Ryu0118/feature/pullback-test
Add Pullback Tests
2 parents 8bf3d06 + 857cee6 commit 997de56

File tree

2 files changed

+113
-18
lines changed

2 files changed

+113
-18
lines changed

Sources/SimplexArchitecture/Store/Store+send.swift

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,36 +30,45 @@ extension Store {
3030
_ action: Reducer.Action,
3131
container: StateContainer<Reducer.Target>
3232
) -> SendTask {
33-
defer {
34-
if let _ = Reducer.Action.self as? Pullbackable.Type, let pullbackAction {
35-
pullbackAction(action)
36-
}
37-
}
38-
39-
return sendAction(.action(action), container: container)
33+
sendAction(.action(action), container: container)
4034
}
4135

4236
@usableFromInline
4337
func sendAction(
4438
_ action: Reducer.ReducerAction,
4539
container: StateContainer<Reducer.Target>
4640
) -> SendTask {
47-
defer {
48-
if let _ = Reducer.ReducerAction.self as? Pullbackable.Type,
49-
let pullbackReducerAction
50-
{
51-
pullbackReducerAction(action)
52-
}
53-
}
54-
55-
return sendAction(.action(action), container: container)
41+
sendAction(.action(action), container: container)
5642
}
5743

5844
@inline(__always)
5945
func sendAction(
6046
_ action: CombineAction<Reducer>,
6147
container: StateContainer<Reducer.Target>
6248
) -> SendTask {
49+
defer {
50+
switch action.kind {
51+
case .viewAction(let action):
52+
guard let pullbackAction else {
53+
break
54+
}
55+
if let _ = Reducer.Action.self as? Pullbackable.Type {
56+
pullbackAction(action)
57+
} else {
58+
runtimeWarning("\(Reducer.Action.self) must be conformed to Pullbackable in order to pullback to parent reducer")
59+
}
60+
case .reducerAction(let action):
61+
guard let pullbackReducerAction else {
62+
break
63+
}
64+
if let _ = Reducer.ReducerAction.self as? Pullbackable.Type {
65+
pullbackReducerAction(action)
66+
} else {
67+
runtimeWarning("\(Reducer.ReducerAction.self) must be conformed to Pullbackable in order to pullback to parent reducer")
68+
}
69+
}
70+
}
71+
6372
let sideEffect: SideEffect<Reducer>
6473
// If Unit Testing is in progress and an action is sent from SideEffect
6574
if _XCTIsTesting, let effectContext = EffectContext.id {

Tests/SimplexArchitectureTests/StoreTests.swift

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@testable import SimplexArchitecture
22
import SwiftUI
33
import XCTest
4+
import CasePaths
45

56
final class StoreTests: XCTestCase {
67
fileprivate var store: Store<TestReducer>!
@@ -95,15 +96,100 @@ final class StoreTests: XCTestCase {
9596

9697
XCTAssertNotNil(sendTask2.task)
9798
}
99+
100+
func testPullbackAction() throws {
101+
let parent = ParentView()
102+
parent.store.setContainerIfNeeded(for: parent, states: .init())
103+
store.setContainerIfNeeded(for: TestView())
104+
XCTAssertNil(store.pullbackAction)
105+
store.pullback(to: /ParentReducer.Action.child, parent: parent)
106+
store.sendIfNeeded(.c1)
107+
XCTAssertNotNil(store.pullbackAction)
108+
XCTAssertEqual(parent.store.container?.count, 1)
109+
}
110+
111+
func testPullbackReducerAction() throws {
112+
let parent = ParentView()
113+
parent.store.setContainerIfNeeded(for: parent, states: .init())
114+
store.setContainerIfNeeded(for: TestView())
115+
XCTAssertNil(store.pullbackReducerAction)
116+
store.pullback(to: /ParentReducer.Action.childReducerAction, parent: parent)
117+
store.sendIfNeeded(.c4)
118+
XCTAssertNotNil(store.pullbackReducerAction)
119+
XCTAssertEqual(parent.store.container?.count, 1)
120+
}
121+
122+
func testPullbackActionForId() throws {
123+
let parent = ParentView()
124+
parent.store.setContainerIfNeeded(for: parent, states: .init())
125+
store.setContainerIfNeeded(for: TestView())
126+
XCTAssertNil(store.pullbackAction)
127+
let uuid = UUID()
128+
store.pullback(to: /ParentReducer.Action.childId, parent: parent, id: uuid)
129+
store.sendIfNeeded(.c1)
130+
XCTAssertNotNil(store.pullbackAction)
131+
XCTAssertEqual(parent.store.container?.id, uuid)
132+
}
133+
134+
func testPullbackReducerActionForId() throws {
135+
let parent = ParentView()
136+
parent.store.setContainerIfNeeded(for: parent, states: .init())
137+
store.setContainerIfNeeded(for: TestView())
138+
XCTAssertNil(store.pullbackReducerAction)
139+
let uuid = UUID()
140+
store.pullback(to: /ParentReducer.Action.childIdReducerAction, parent: parent, id: uuid)
141+
store.sendIfNeeded(.c4)
142+
XCTAssertNotNil(store.pullbackReducerAction)
143+
XCTAssertEqual(parent.store.container?.id, uuid)
144+
}
145+
}
146+
147+
@ScopeState
148+
private struct ParentView: View {
149+
@State var count = 0
150+
@State var id: UUID?
151+
let store: Store<ParentReducer> = .init(reducer: ParentReducer())
152+
var body: some View {
153+
EmptyView()
154+
}
155+
}
156+
157+
private struct ParentReducer: ReducerProtocol {
158+
enum Action {
159+
case child(TestReducer.Action)
160+
case childReducerAction(TestReducer.ReducerAction)
161+
case childId(id: UUID, action: TestReducer.Action)
162+
case childIdReducerAction(id: UUID, action: TestReducer.ReducerAction)
163+
}
164+
165+
func reduce(into state: StateContainer<ParentView>, action: Action) -> SideEffect<ParentReducer> {
166+
switch action {
167+
case .child:
168+
state.count += 1
169+
return .none
170+
171+
case .childReducerAction:
172+
state.count += 1
173+
return .none
174+
175+
case .childId(let id, _):
176+
state.id = id
177+
return .none
178+
179+
case .childIdReducerAction(let id, _):
180+
state.id = id
181+
return .none
182+
}
183+
}
98184
}
99185

100186
private struct TestReducer: ReducerProtocol {
101-
enum ReducerAction {
187+
enum ReducerAction: Equatable, Pullbackable {
102188
case c3
103189
case c4
104190
}
105191

106-
enum Action: Equatable {
192+
enum Action: Equatable, Pullbackable {
107193
case c1
108194
case c2
109195
}

0 commit comments

Comments
 (0)