Skip to content

Commit 96bbd04

Browse files
authored
Markj/private subject rendering (#374)
## Checklist - [ ] Unit Tests - [ ] UI Tests - [ ] Snapshot Tests (iOS only) - [ ] I have made corresponding changes to the documentation
1 parent 59bcc8b commit 96bbd04

File tree

15 files changed

+146
-162
lines changed

15 files changed

+146
-162
lines changed

Samples/Tutorial/Frameworks/Tutorial5Complete/Tests/RootWorkflowTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class RootWorkflowTests: XCTestCase {
8787

8888
// First rendering is just the welcome screen. Update the name.
8989
do {
90-
let backStack = workflowHost.rendering.value
90+
let backStack = workflowHost.rendering
9191
XCTAssertEqual(1, backStack.items.count)
9292

9393
guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
@@ -100,7 +100,7 @@ class RootWorkflowTests: XCTestCase {
100100

101101
// Log in and go to the todo list.
102102
do {
103-
let backStack = workflowHost.rendering.value
103+
let backStack = workflowHost.rendering
104104
XCTAssertEqual(1, backStack.items.count)
105105

106106
guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
@@ -113,7 +113,7 @@ class RootWorkflowTests: XCTestCase {
113113

114114
// Expect the todo list to be rendered. Edit the first todo.
115115
do {
116-
let backStack = workflowHost.rendering.value
116+
let backStack = workflowHost.rendering
117117
XCTAssertEqual(2, backStack.items.count)
118118

119119
guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
@@ -134,7 +134,7 @@ class RootWorkflowTests: XCTestCase {
134134

135135
// Selected a todo to edit. Expect the todo edit screen.
136136
do {
137-
let backStack = workflowHost.rendering.value
137+
let backStack = workflowHost.rendering
138138
XCTAssertEqual(3, backStack.items.count)
139139

140140
guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
@@ -158,7 +158,7 @@ class RootWorkflowTests: XCTestCase {
158158

159159
// Save the selected todo.
160160
do {
161-
let backStack = workflowHost.rendering.value
161+
let backStack = workflowHost.rendering
162162
XCTAssertEqual(3, backStack.items.count)
163163

164164
guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
@@ -204,7 +204,7 @@ class RootWorkflowTests: XCTestCase {
204204

205205
// Expect the todo list. Validate the title was updated.
206206
do {
207-
let backStack = workflowHost.rendering.value
207+
let backStack = workflowHost.rendering
208208
XCTAssertEqual(2, backStack.items.count)
209209

210210
guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {

Workflow/Sources/ReadOnlyCurrentValueSubject.swift

Lines changed: 0 additions & 22 deletions
This file was deleted.

Workflow/Sources/WorkflowHost.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,19 @@ public final class WorkflowHost<WorkflowType: Workflow>: WorkflowOutputPublisher
4141
// @testable
4242
let rootNode: WorkflowNode<WorkflowType>
4343

44-
private let mutableRendering: CurrentValueSubject<WorkflowType.Rendering, Never>
44+
private let renderingSubject: CurrentValueSubject<WorkflowType.Rendering, Never>
4545
private let outputSubject = PassthroughSubject<WorkflowType.Output, Never>()
4646

4747
/// Represents the `Rendering` produced by the root workflow in the hierarchy. New `Rendering` values are produced
4848
/// as state transitions occur within the hierarchy.
49-
public let rendering: ReadOnlyCurrentValueSubject<WorkflowType.Rendering, Never>
49+
public var rendering: WorkflowType.Rendering {
50+
renderingSubject.value
51+
}
52+
53+
/// A Publisher containing rendering events produced by the root workflow in the hierarchy.
54+
public var renderingPublisher: AnyPublisher<WorkflowType.Rendering, Never> {
55+
renderingSubject.eraseToAnyPublisher()
56+
}
5057

5158
/// Context object to pass down to descendant nodes in the tree.
5259
let context: HostContext
@@ -83,7 +90,7 @@ public final class WorkflowHost<WorkflowType: Workflow>: WorkflowOutputPublisher
8390
parentSession: nil
8491
)
8592

86-
(self.rendering, self.mutableRendering) = ReadOnlyCurrentValueSubject.publisher(value: rootNode.render())
93+
self.renderingSubject = CurrentValueSubject(rootNode.render())
8794

8895
rootNode.enableEvents()
8996

@@ -115,7 +122,7 @@ public final class WorkflowHost<WorkflowType: Workflow>: WorkflowOutputPublisher
115122
private func handle(output: WorkflowNode<WorkflowType>.Output) {
116123
let shouldRender = !shouldSkipRenderForOutput(output)
117124
if shouldRender {
118-
mutableRendering.send(rootNode.render())
125+
renderingSubject.send(rootNode.render())
119126
}
120127

121128
// Always emit an output, regardless of whether a render occurs

Workflow/Tests/AnyWorkflowTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class AnyWorkflowTests: XCTestCase {
4040
let host = WorkflowHost(workflow: OnOutputWorkflow())
4141

4242
let renderingExpectation = expectation(description: "Waiting for rendering")
43-
let cancellable = host.rendering.sink { rendering in
43+
let cancellable = host.renderingPublisher.sink { rendering in
4444
if rendering {
4545
renderingExpectation.fulfill()
4646
}

Workflow/Tests/ConcurrencyTests.swift

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,20 @@ final class ConcurrencyTests: XCTestCase {
2929
var first = true
3030
var observedScreen: TestScreen?
3131

32-
let cancellable = host.rendering.sink { rendering in
32+
let cancellable = host.renderingPublisher.sink { rendering in
3333
if first {
3434
expectation.fulfill()
3535
first = false
3636
}
3737
observedScreen = rendering
3838
}
3939

40-
let initialScreen = host.rendering.value
40+
let initialScreen = host.rendering
4141
XCTAssertEqual(0, initialScreen.count)
4242
initialScreen.update()
4343

4444
// This update happens immediately as a new rendering is generated synchronously.
45-
XCTAssertEqual(1, host.rendering.value.count)
45+
XCTAssertEqual(1, host.rendering.count)
4646

4747
wait(for: [expectation], timeout: 1.0)
4848
guard let screen = observedScreen else {
@@ -60,7 +60,7 @@ final class ConcurrencyTests: XCTestCase {
6060
let renderingExpectation = expectation(description: "Waiting on rendering values.")
6161
var first = true
6262

63-
let cancellable = host.rendering.dropFirst().sink { rendering in
63+
let cancellable = host.renderingPublisher.dropFirst().sink { rendering in
6464
if first {
6565
first = false
6666
// Emit an event when the rendering is first received.
@@ -70,15 +70,15 @@ final class ConcurrencyTests: XCTestCase {
7070
}
7171
}
7272

73-
let initialScreen = host.rendering.value
73+
let initialScreen = host.rendering
7474
XCTAssertEqual(0, initialScreen.count)
7575

7676
// Updating the screen will cause two events - the `update` here, and the update caused by the first time the rendering changes.
7777
initialScreen.update()
7878

7979
waitForExpectations(timeout: 1)
8080

81-
XCTAssertEqual(2, host.rendering.value.count)
81+
XCTAssertEqual(2, host.rendering.count)
8282
cancellable.cancel()
8383
}
8484

@@ -88,7 +88,7 @@ final class ConcurrencyTests: XCTestCase {
8888
let renderingExpectation = expectation(description: "Waiting on rendering values.")
8989
var renderingValuesCount = 0
9090

91-
let cancellable = host.rendering.dropFirst().sink { rendering in
91+
let cancellable = host.renderingPublisher.dropFirst().sink { rendering in
9292
if renderingValuesCount == 0 {
9393
// Emit two events.
9494
rendering.update()
@@ -104,15 +104,15 @@ final class ConcurrencyTests: XCTestCase {
104104
renderingValuesCount += 1
105105
}
106106

107-
let initialScreen = host.rendering.value
107+
let initialScreen = host.rendering
108108
XCTAssertEqual(0, initialScreen.count)
109109

110110
// Updating the screen will cause three events.
111111
initialScreen.update()
112112

113113
waitForExpectations(timeout: 1)
114114

115-
XCTAssertEqual(3, host.rendering.value.count)
115+
XCTAssertEqual(3, host.rendering.count)
116116
cancellable.cancel()
117117
}
118118

@@ -123,20 +123,20 @@ final class ConcurrencyTests: XCTestCase {
123123
let host = WorkflowHost(workflow: TestWorkflow())
124124

125125
// Capture the initial screen and corresponding closure that uses the original sink.
126-
let initialScreen = host.rendering.value
126+
let initialScreen = host.rendering
127127
XCTAssertEqual(0, initialScreen.count)
128128

129129
// Send an action to the workflow. This invalidates this sink, but the next render pass declares a
130130
// sink of the same type.
131131
initialScreen.update()
132132

133-
let secondScreen = host.rendering.value
133+
let secondScreen = host.rendering
134134
XCTAssertEqual(1, secondScreen.count)
135135

136136
// Send an action from the original screen and sink. It should be proxied through the most recent sink.
137137
initialScreen.update()
138138

139-
let thirdScreen = host.rendering.value
139+
let thirdScreen = host.rendering
140140
XCTAssertEqual(2, thirdScreen.count)
141141
}
142142

@@ -146,14 +146,14 @@ final class ConcurrencyTests: XCTestCase {
146146
let host = WorkflowHost(workflow: OneShotWorkflow())
147147

148148
// Capture the initial screen and corresponding closure that uses the original sink.
149-
let initialScreen = host.rendering.value
149+
let initialScreen = host.rendering
150150
XCTAssertEqual(0, initialScreen.count)
151151

152152
// Send an action to the workflow. This invalidates this sink, but the next render pass declares a
153153
// sink of the same type.
154154
initialScreen.update()
155155

156-
let secondScreen = host.rendering.value
156+
let secondScreen = host.rendering
157157
XCTAssertEqual(1, secondScreen.count)
158158

159159
// Calling `update` uses the original sink. Historically this would be expected
@@ -165,7 +165,7 @@ final class ConcurrencyTests: XCTestCase {
165165
// If the sink *was* still valid, this would be correct. However, it should just fail and be `1` still.
166166
// XCTAssertEqual(2, secondScreen.count)
167167
// Actual expected result, if we had not fatal errored.
168-
XCTAssertEqual(1, host.rendering.value.count)
168+
XCTAssertEqual(1, host.rendering.count)
169169

170170
struct OneShotWorkflow: Workflow {
171171
typealias Output = Never
@@ -228,7 +228,7 @@ final class ConcurrencyTests: XCTestCase {
228228
var first = true
229229

230230
let renderingsComplete = expectation(description: "Waiting for renderings")
231-
let cancellable = host.rendering.dropFirst().sink { rendering in
231+
let cancellable = host.renderingPublisher.dropFirst().sink { rendering in
232232
if first {
233233
first = false
234234
rendering.update()
@@ -237,7 +237,7 @@ final class ConcurrencyTests: XCTestCase {
237237
}
238238
}
239239

240-
let initialScreen = host.rendering.value
240+
let initialScreen = host.rendering
241241
initialScreen.update()
242242

243243
waitForExpectations(timeout: 1)
@@ -251,14 +251,14 @@ final class ConcurrencyTests: XCTestCase {
251251
func test_childWorkflowsAreSynchronous() {
252252
let host = WorkflowHost(workflow: ParentWorkflow())
253253

254-
let initialScreen = host.rendering.value
254+
let initialScreen = host.rendering
255255
XCTAssertEqual(0, initialScreen.count)
256256
initialScreen.update()
257257

258258
// This update happens immediately as a new rendering is generated synchronously.
259259
// Both the child updates from the action (incrementing state by 1) as well as the
260260
// parent from the output (incrementing its state by 10)
261-
XCTAssertEqual(11, host.rendering.value.count)
261+
XCTAssertEqual(11, host.rendering.count)
262262

263263
struct ParentWorkflow: Workflow {
264264
struct State {
@@ -317,11 +317,11 @@ final class ConcurrencyTests: XCTestCase {
317317
outputExpectation.fulfill()
318318
}
319319

320-
let cancellable = host.rendering.sink { rendering in
320+
let cancellable = host.renderingPublisher.sink { rendering in
321321
renderingExpectation.fulfill()
322322
}
323323

324-
let screen = host.rendering.value
324+
let screen = host.rendering
325325

326326
XCTAssertEqual(0, screen.count)
327327

@@ -330,7 +330,7 @@ final class ConcurrencyTests: XCTestCase {
330330

331331
wait(for: [renderingExpectation, outputExpectation], timeout: 1.0)
332332

333-
XCTAssertEqual(101, host.rendering.value.count)
333+
XCTAssertEqual(101, host.rendering.count)
334334

335335
cancellable.cancel()
336336
outputCancellable.cancel()
@@ -343,18 +343,18 @@ final class ConcurrencyTests: XCTestCase {
343343
func test_multipleAnyWorkflowAction_sinksDontOverrideEachOther() {
344344
let host = WorkflowHost(workflow: AnyActionWorkflow())
345345

346-
let initialScreen = host.rendering.value
346+
let initialScreen = host.rendering
347347
XCTAssertEqual(0, initialScreen.count)
348348

349349
// Update using the first action.
350350
initialScreen.updateFirst()
351351

352-
let secondScreen = host.rendering.value
352+
let secondScreen = host.rendering
353353
XCTAssertEqual(1, secondScreen.count)
354354

355355
// Update using the second action.
356356
secondScreen.updateSecond()
357-
XCTAssertEqual(11, host.rendering.value.count)
357+
XCTAssertEqual(11, host.rendering.count)
358358

359359
struct AnyActionWorkflow: Workflow {
360360
enum Output {

0 commit comments

Comments
 (0)