Skip to content

Commit 4b95957

Browse files
committed
Create ObservableObject observation tests
1 parent c439d12 commit 4b95957

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

Tests/SwiftCrossUITests/SwiftCrossUITests.swift

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,114 @@ final class SwiftCrossUITests: XCTestCase {
6464
return original == decoded
6565
}
6666

67+
func testStateObservation() {
68+
class NestedState: SwiftCrossUI.ObservableObject {
69+
@SwiftCrossUI.Published
70+
var count = 0
71+
}
72+
73+
class MyState: SwiftCrossUI.ObservableObject {
74+
@SwiftCrossUI.Published
75+
var count = 0
76+
@SwiftCrossUI.Published
77+
var publishedNestedState = NestedState()
78+
var unpublishedNestedState = NestedState()
79+
}
80+
81+
let state = MyState()
82+
var observedChange = false
83+
let cancellable = state.didChange.observe {
84+
observedChange = true
85+
}
86+
87+
// Ensures that published value type mutation triggers observation
88+
observedChange = false
89+
state.count += 1
90+
XCTAssert(observedChange, "Expected value type mutation to trigger observation")
91+
92+
// Ensure that published nested ObservableObject triggers observation
93+
observedChange = false
94+
state.publishedNestedState.count += 1
95+
XCTAssert(observedChange, "Expected nested published observable object mutation to trigger observation")
96+
97+
// Ensure that replacing published nested ObservableObject triggers observation
98+
observedChange = false
99+
state.publishedNestedState = NestedState()
100+
XCTAssert(observedChange, "Expected replacing nested published observable object to trigger observation")
101+
102+
// Ensure that replaced published nested ObservableObject triggers observation
103+
observedChange = false
104+
state.publishedNestedState.count += 1
105+
XCTAssert(observedChange, "Expected replaced nested published observable object mutation to trigger observation")
106+
107+
// Ensure that non-published nested ObservableObject doesn't trigger observation
108+
observedChange = false
109+
state.unpublishedNestedState.count += 1
110+
XCTAssert(!observedChange, "Expected nested unpublished observable object mutation to not trigger observation")
111+
112+
// Ensure that cancelling the observation prevents future observations
113+
cancellable.cancel()
114+
observedChange = false
115+
state.count += 1
116+
XCTAssert(!observedChange, "Expected mutation not to trigger cancelled observation")
117+
}
118+
67119
#if canImport(AppKitBackend)
120+
// TODO: Create mock backend so that this can be tested on all platforms. There's
121+
// nothing AppKit-specific about it.
122+
func testThrottledStateObservation() async {
123+
class MyState: SwiftCrossUI.ObservableObject {
124+
@SwiftCrossUI.Published
125+
var count = 0
126+
}
127+
128+
/// A thread-safe count.
129+
actor Count {
130+
var count = 0
131+
132+
func update(_ action: (Int) -> Int) {
133+
count = action(count)
134+
}
135+
}
136+
137+
// Number of mutations to perform
138+
let mutationCount = 20
139+
// Length of each fake state update
140+
let updateDuration = 0.02
141+
// Delay between observation-causing state mutations
142+
let mutationGap = 0.01
143+
144+
let state = MyState()
145+
let updateCount = Count()
146+
147+
let backend = await AppKitBackend()
148+
let cancellable = state.didChange.observeAsUIUpdater(backend: backend) {
149+
Task {
150+
await updateCount.update { $0 + 1 }
151+
}
152+
// Simulate an update of duration `updateDuration` seconds
153+
Thread.sleep(forTimeInterval: updateDuration)
154+
}
155+
_ = cancellable // Silence warning about cancellable being unused
156+
157+
let start = ProcessInfo.processInfo.systemUptime
158+
for _ in 0..<mutationCount {
159+
state.count += 1
160+
try? await Task.sleep(for: .seconds(mutationGap))
161+
}
162+
let elapsed = ProcessInfo.processInfo.systemUptime - start
163+
164+
// Compute percentage of main thread's time taken up by updates.
165+
let ratio = Double(await updateCount.count) * updateDuration / elapsed
166+
XCTAssert(
167+
0.5 <= ratio && ratio <= 0.85,
168+
"""
169+
Expected throttled updates to take between 50% and 80% of the main \
170+
thread's time. Took \(Int(ratio * 100))%
171+
"""
172+
)
173+
}
174+
68175
@MainActor
69176
func testBasicLayout() async throws {
70177
let backend = AppKitBackend()

0 commit comments

Comments
 (0)