Skip to content

Commit f4a7aba

Browse files
committed
wip
1 parent b1c819c commit f4a7aba

File tree

5 files changed

+57
-2
lines changed

5 files changed

+57
-2
lines changed

Examples/Github-App/Github-App/View/RootView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import SwiftUI
55
struct RootReducer {
66
enum ViewAction: Equatable {
77
case textChanged
8+
case onAppear
89
}
910

1011
enum ReducerAction: Equatable {
@@ -26,6 +27,9 @@ struct RootReducer {
2627

2728
func reduce(into state: StateContainer<RootView>, action: Action) -> SideEffect<Self> {
2829
switch action {
30+
case .onAppear:
31+
return fetchRepositories(query: "Swift")
32+
2933
case .textChanged:
3034
if state.searchText.isEmpty {
3135
state.repositories = []
@@ -116,6 +120,9 @@ struct RootView: View {
116120
send(.textChanged)
117121
}
118122
.alert(target: self, unwrapping: $alertState)
123+
.onAppear {
124+
send(.onAppear)
125+
}
119126
}
120127
}
121128
}

Sources/SimplexArchitecture/SideEffect.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ public extension SideEffect {
122122
}
123123

124124
public extension SideEffect {
125+
/// Turns an side effect into one that can be debounced.
126+
///
127+
/// debounce is an operator that plays only the last event in a sequence of SideEffects that have been issued for more than a certain time interval.
128+
/// To turn an effect into a debounce-able one you must provide an identifier, which is used to determine which in-flight effect should be canceled in order to start a new effect.
129+
///
130+
/// - Parameters:
131+
/// - id: The effect's identifier.
132+
/// - duration: The duration you want to debounce for.
133+
/// - clock: A clock conforming to the `Clock` protocol, used to measure the passing of time for the debounce duration.
134+
/// - Returns: A debounced version of the effect.
125135
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
126136
@inlinable
127137
func debounce(

Sources/SimplexArchitecture/Store/Store+send.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ extension Store {
199199
cancellableTasks[id]?.cancel()
200200
let cancellableTask = Task.withEffectContext {
201201
try? await sleep()
202-
guard !Task.isCancelled else { return }
202+
guard !Task.isCancelled else {
203+
return
204+
}
203205
await reduce(tasks: runEffect(base, send: send)).wait()
204206
}
205207
cancellableTasks.updateValue(cancellableTask, forKey: id)

Tests/SimplexArchitectureTests/ReducerTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ final class ReducerTests: XCTestCase {
148148
await testStore.receiveWithoutStateCheck(.decrement)
149149
await testStore.receiveWithoutStateCheck(.increment)
150150
}
151+
152+
func testDebounced() async {
153+
let testStore = TestView().testStore(
154+
viewState: .init()) {
155+
$0.continuousClock = ImmediateClock()
156+
}
157+
158+
await testStore.send(.debounced)
159+
await testStore.receive(.increment) {
160+
$0.count = 1
161+
}
162+
}
151163
}
152164

153165
struct TestDependency: DependencyKey {
@@ -195,6 +207,7 @@ private struct TestReducer {
195207
case testDependencies
196208
case runEffectsSerially
197209
case runEffectsConcurrently
210+
case debounced
198211
}
199212

200213
@Dependency(\.continuousClock) private var clock
@@ -281,6 +294,15 @@ private struct TestReducer {
281294
await send(.increment)
282295
}
283296
)
297+
298+
case .debounced:
299+
enum CancelID { case debounce }
300+
return .send(.increment)
301+
.debounce(
302+
id: CancelID.debounce,
303+
for: .seconds(0.3),
304+
clock: clock
305+
)
284306
}
285307
}
286308
}

Tests/SimplexArchitectureTests/StoreTests.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,27 @@ final class StoreTests: XCTestCase {
6969

7070
let sendTasks7 = store.runEffect(.serial(.c3, .c4), send: send)
7171
XCTAssertEqual(sendTasks7.count, 1)
72-
XCTAssertNotNil(sendTasks5.first?.task)
72+
XCTAssertNotNil(sendTasks7.first?.task)
7373

7474
let sendTasks8 = store.runEffect(.concurrent(.c3, .c4), send: send)
7575
XCTAssertEqual(sendTasks8.count, 2)
7676
for task in sendTasks8.map(\.task) {
7777
XCTAssertNotNil(task)
7878
}
79+
80+
let sendTasks9 = store.runEffect(.serial(.send(.c1), .send(.c2)), send: send)
81+
XCTAssertEqual(sendTasks9.count, 1)
82+
XCTAssertNotNil(sendTasks9.first?.task)
83+
84+
let sendTasks10 = store.runEffect(.concurrent(.send(.c1), .send(.c2)), send: send)
85+
XCTAssertEqual(sendTasks10.count, 2)
86+
for task in sendTasks10.map(\.task) {
87+
XCTAssertNotNil(task)
88+
}
89+
90+
let sendTasks11 = store.runEffect(.send(.c1).debounce(id: "test", for: .seconds(0.3), clock: ImmediateClock()), send: send)
91+
XCTAssertEqual(sendTasks11.count, 1)
92+
XCTAssertNotNil(sendTasks11.first?.task)
7993
}
8094

8195
func testSendAction() async throws {

0 commit comments

Comments
 (0)