-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUsageExampleSpec.swift
216 lines (208 loc) · 7.45 KB
/
UsageExampleSpec.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright © Rouven Strauss. MIT license.
import ART
import Foundation
import Nimble
import Quick
/// Object solely existing for the purpose of explaining how `ART` works. Typically, applications
/// relying on `ART` maintain a single object similar to the one documented here which is
/// responsible for
/// a) holding the `LogicModule` instance,
/// b) holding a collection of observers which cannot be added to the `LogicModule` already during
/// its creation and must be held since the `LogicModule` does not hold dynamically added
/// observers strongly,
/// c) sending requests and/or side effects to the `LogicModule` due to changes of the system state
/// (e.g., due to the app transitioning into the background) and/or incoming events/requests from
/// external means of interaction, such as an API.
class App {
private let logicModule: LogicModule
private let uiLogicModule: UIEventLogicModule?
init(with logicModule: LogicModule, uiLogicModule: UIEventLogicModule? = nil) {
self.logicModule = logicModule
self.uiLogicModule = uiLogicModule
}
/// Function implementing the state reduction logic. There are several ways of encapsulating the
/// reduction logic and the chosen way of using a static function of `App` is solely an example.
fileprivate static func reduce(
state: inout App.State,
requests: [App.Request],
coeffects: App.Coeffects
) {
requests.forEach { request in
switch request {
case let .completionOfDataDownload(downloadResult):
switch downloadResult {
case let .failure(error):
state.errorMessage = error.localizedDescription
case let .success(data):
state.downloadedData = data
}
case .dismissalOfErrorMessage:
state.errorMessage = nil
}
}
}
/// Function implementing side effect performing.
fileprivate static func sideEffectClosure(
_ handle: @escaping @Sendable (Request, Coeffects) -> Void
) -> SideEffectPerformer.SideEffectClosure {
return { sideEffect, coeffects in
switch sideEffect {
case .downloadOfData:
// For the sake of the example, success in downloading the data is assumed.
handle(.completionOfDataDownload(.success(App.ArbitraryDownloadableResource())), coeffects)
return .success
}
}
}
}
//final class UsageExampleSpec: AsyncSpec {
// override class func spec() {
// context("minimal example application") {
// context("setup") {
// it("sets up application with logic module") {
// let logicModule: App.LogicModule = .newInstance()
// _ = App(with: logicModule)
// }
//
// it("sets up application with logic module and UI") {
// let (logicModule, view, observer) = await App.LogicModule.newInstanceWithUI()
// _ = App(with: logicModule)
//
// connect(view)
//
// // Make sure to hold hold observer strongly to allow for UI updates.
// holdStrongly(observer)
// }
//
// // Dummy functions for example purposes.
//
// func connect(_ view: App.MainView) {}
// func holdStrongly(_ observer: Any) {}
// }
//
// context("state update and observation") {
// it("observes current state when adding observer") {
// let logicModule: App.LogicModule = .newInstance()
// var observedData: App.ArbitraryDownloadableResource? = .init()
// let observer: PropertyPathObserver = .observer(
// for: \App.State.downloadedData,
// initiallyObservedValue: {
// observedData = $0
// }
// ) { _ in
// fatalErrorDueToMissingImplementation()
// }
// await logicModule.add(observer.modelObserver)
//
// expect(observedData).to(beNil())
// }
//
// it("observes state change triggered by request handling") {
// let logicModule: App.LogicModule = .newInstance()
// var observedData: App.ArbitraryDownloadableResource?
// let observer: PropertyPathObserver = .observer(for: \App.State.downloadedData) {
// observedData = $0
// }
// await logicModule.add(observer.modelObserver)
//
// expect(observedData).to(beNil())
//
// logicModule.handle(.completionOfDataDownload(.success(.init())))
//
// expect(observedData).toNot(beNil())
// }
//
// it("observes state change triggered by side effect") {
// let logicModule: App.LogicModule = .newInstance()
// var observedData: App.ArbitraryDownloadableResource?
// let observer: PropertyPathObserver = .observer(for: \App.State.downloadedData) {
// observedData = $0
// }
// await logicModule.add(observer.modelObserver)
//
// expect(observedData).to(beNil())
//
// await logicModule.perform(.downloadOfData(from: URL(string: "fakeURL")!))
//
// await expect(observedData).toEventuallyNot(beNil())
// }
//
// it("observes state change triggered by UI event") {
// let (logicModule, view, _) = await App.LogicModule.newInstanceWithUI()
// var observedData: App.ArbitraryDownloadableResource?
// let dataObserver: PropertyPathObserver = .observer(for: \App.State.downloadedData) {
// observedData = $0
// }
// await logicModule.add(dataObserver.modelObserver)
//
// expect(observedData).to(beNil())
//
// await view.handle(.downloadButtonPress)
//
// await expect(observedData).toEventuallyNot(beNil())
// }
// }
// }
// }
//}
//
//private extension App.LogicModule {
// static func newInstance() -> App.LogicModule {
// let coeffects = App.Coeffects()
// let model = App.Model(state: App.State(), reduce: App.reduce)
// let sideEffectPerformer = App.SideEffectPerformer(
// sideEffectClosure: App.sideEffectClosure(model.handle)
// )
//
// return App.LogicModule(
// model: model,
// sideEffectPerformer: sideEffectPerformer,
// coeffects: coeffects,
// staticObservers: []
// )
// }
//
// static func newInstanceWithUI() async -> (App.LogicModule, App.MainView, Any) {
// let coeffects = App.Coeffects()
// let model = App.Model(state: App.State(), reduce: App.reduce)
// let sideEffectPerformer = App.SideEffectPerformer(
// sideEffectClosure: App.sideEffectClosure(model.handle)
// )
// let logicModule = App.LogicModule(
// model: model,
// sideEffectPerformer: sideEffectPerformer,
// coeffects: coeffects,
// staticObservers: []
// )
// let uiLogicModule = await logicModule.newUILogicModule()
// let (view, observer) = await App.MainView.instance(
// observing: \.self,
// of: model,
// using: coeffects
// ) { event in
// uiLogicModule.handle(event, given: model.state)
// }
//
// return (logicModule, view, observer)
// }
//
// private func newUILogicModule() -> App.UIEventLogicModule {
// return self.viewLogic { event, state, then, _ in
// switch event {
// case .downloadButtonPress:
// Task {
// let result = await then.perform(.downloadOfData(from: URL(string: "fakeURL")!))
//
// switch result {
// case .success:
// break
// case let .failure(error):
// print("Failed downloading data: \(error.localizedDescription)")
// }
// }
// case .errorMessageView(.buttonPress):
// self.handle(.dismissalOfErrorMessage)
// }
// }
// }
//}