Skip to content

Commit 73cde08

Browse files
committed
wip
1 parent 921d78d commit 73cde08

File tree

5 files changed

+133
-5
lines changed

5 files changed

+133
-5
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Dependencies
2+
3+
public struct _DependenciesOverrideModifier<Base: ReducerProtocol>: _ReducerModifier {
4+
public let base: Base
5+
public let override: (inout DependencyValues) -> Void
6+
7+
public func reduce(into state: StateContainer<Base.Target>, action: Base.Action) -> SideEffect<Base> {
8+
withDependencies(override) {
9+
base.reduce(into: state, action: action)
10+
}
11+
}
12+
13+
public func reduce(into state: StateContainer<Base.Target>, action: Base.ReducerAction) -> SideEffect<Base> {
14+
withDependencies(override) {
15+
base.reduce(into: state, action: action)
16+
}
17+
}
18+
19+
public func dependency<Value>(
20+
_ keyPath: WritableKeyPath<DependencyValues, Value>,
21+
value: Value
22+
) -> Self {
23+
Self(base: self.base) { values in
24+
values[keyPath: keyPath] = value
25+
override(&values)
26+
}
27+
}
28+
}
29+
30+
public extension ReducerProtocol {
31+
func dependency<Value>(
32+
_ keyPath: WritableKeyPath<DependencyValues, Value>,
33+
value: Value
34+
) -> _DependenciesOverrideModifier<Self> {
35+
.init(base: self) { values in
36+
values[keyPath: keyPath] = value
37+
}
38+
}
39+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Foundation
2+
import Dependencies
3+
4+
public protocol _ReducerModifier<Base> {
5+
associatedtype Base: ReducerProtocol
6+
var base: Base { get }
7+
func reduce(into state: StateContainer<Base.Target>, action: Base.Action) -> SideEffect<Base>
8+
/// Evolve the current state of ActionSendable to the next state.
9+
///
10+
/// - Parameters:
11+
/// - state: Current state of ActionSendable and ReducerState. ReducerState can be accessed from the `reducerState` property of State..
12+
/// - action: A ReducerAction that can change the state of View and ReducerState.
13+
/// - Returns: An `SideEffect` representing the side effects generated by the reducer.
14+
func reduce(into state: StateContainer<Base.Target>, action: Base.ReducerAction) -> SideEffect<Base>
15+
}
16+
17+
public extension _ReducerModifier {
18+
@inlinable
19+
func reduce(
20+
into state: StateContainer<Base.Target>,
21+
action: CombineAction<Base>
22+
) -> SideEffect<Base> {
23+
switch action.kind {
24+
case let .viewAction(action):
25+
reduce(into: state, action: action)
26+
27+
case let .reducerAction(action):
28+
reduce(into: state, action: action)
29+
}
30+
}
31+
}

Sources/SimplexArchitecture/Store/Store+send.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ extension Store {
7474
if _XCTIsTesting, let effectContext = EffectContext.id {
7575
let before = container.copy()
7676
sideEffect = withLock {
77-
reducer.reduce(into: container, action: action)
77+
reduce(container, action)
7878
}
7979
sentFromEffectActions.append(
8080
ActionTransition(
@@ -86,7 +86,7 @@ extension Store {
8686
)
8787
)
8888
} else {
89-
sideEffect = withLock { reducer.reduce(into: container, action: action) }
89+
sideEffect = withLock { reduce(container, action) }
9090
}
9191

9292
if case .none = sideEffect.kind {

Sources/SimplexArchitecture/Store/Store.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,35 @@ public final class Store<Reducer: ReducerProtocol> {
2020
@usableFromInline var pullbackAction: ((Reducer.Action) -> Void)?
2121
@usableFromInline var pullbackReducerAction: ((Reducer.ReducerAction) -> Void)?
2222

23-
let reducer: Reducer
23+
let reduce: (StateContainer<Reducer.Target>, CombineAction<Reducer>) -> SideEffect<Reducer>
2424
var initialReducerState: (() -> Reducer.ReducerState)?
2525

2626
/// Initialize `Store` with the given reducer when the `ReducerState` is `Never`.
2727
public init(reducer: Reducer) where Reducer.ReducerState == Never {
28-
self.reducer = reducer
28+
self.reduce = reducer.reduce
2929
}
3030

3131
/// Initialize `Store` with the given `Reducer` and initial `ReducerState`.
3232
public init(
3333
reducer: Reducer,
3434
initialReducerState: @autoclosure @escaping () -> Reducer.ReducerState
3535
) {
36-
self.reducer = reducer
36+
self.reduce = reducer.reduce
37+
self.initialReducerState = initialReducerState
38+
}
39+
40+
public init<R: _ReducerModifier<Reducer>>(
41+
reducer: R
42+
) where Reducer.ReducerState == Never {
43+
self.reduce = reducer.reduce
44+
}
45+
46+
/// Initialize `Store` with the given `Reducer` and initial `ReducerState`.
47+
public init<R: _ReducerModifier<Reducer>>(
48+
reducer: R,
49+
initialReducerState: @autoclosure @escaping () -> Reducer.ReducerState
50+
) {
51+
self.reduce = reducer.reduce
3752
self.initialReducerState = initialReducerState
3853
}
3954

Tests/SimplexArchitectureTests/ReducerTests.swift

Lines changed: 43 additions & 0 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 Dependencies
45

56
@MainActor
67
final class ReducerTests: XCTestCase {
@@ -94,6 +95,39 @@ final class ReducerTests: XCTestCase {
9495
$0.count = 0
9596
}
9697
}
98+
99+
func testDependencies() async {
100+
let testStore = TestView(
101+
store: .init(
102+
reducer: TestReducer()
103+
.dependency(\.test, value: .init {})
104+
,
105+
initialReducerState: .init()
106+
)
107+
).testStore(states: .init())
108+
await testStore.send(.testDependencies)
109+
await testStore.receive(.increment) {
110+
$0.count = 1
111+
}
112+
}
113+
}
114+
115+
struct TestDependency: DependencyKey {
116+
public var asyncThrows: @Sendable () async throws -> Void
117+
118+
public init(asyncThrows: @Sendable @escaping () async throws -> Void) {
119+
self.asyncThrows = asyncThrows
120+
}
121+
122+
public static let liveValue: TestDependency = .init(asyncThrows: { throw CancellationError() })
123+
public static let testValue: TestDependency = .init(asyncThrows: {})
124+
}
125+
126+
extension DependencyValues {
127+
var test: TestDependency {
128+
get { self[TestDependency.self] }
129+
set { self[TestDependency.self] = newValue }
130+
}
97131
}
98132

99133
private struct TestReducer: ReducerProtocol {
@@ -118,8 +152,11 @@ private struct TestReducer: ReducerProtocol {
118152
case invokeIncrement
119153
case invokeDecrement
120154
case send
155+
case testDependencies
121156
}
122157

158+
@Dependency(\.test) var test
159+
123160
func reduce(into state: StateContainer<TestView>, action: ReducerAction) -> SideEffect<Self> {
124161
switch action {
125162
case .incrementFromReducerAction:
@@ -171,6 +208,12 @@ private struct TestReducer: ReducerProtocol {
171208

172209
case .send:
173210
return .send(.increment)
211+
212+
case .testDependencies:
213+
return .run { send in
214+
try await test.asyncThrows()
215+
await send(.increment)
216+
}
174217
}
175218
}
176219
}

0 commit comments

Comments
 (0)