Skip to content

Commit a27c2d4

Browse files
pyrtsastephencelis
andauthored
Restore perception checking tests on macOS (#166)
* Fix unit tests on macOS, where tests now build against macosx14.0 by default * Update CI matrix to include Xcode 26.0 * Update CI to use actions/checkout@v5 * Skip perception checking tests if ImageRenderer is not available * Re-enable several perception checking tests on macOS * Fix typo in perception checking test message * Update Tests/PerceptionTests/PerceptionCheckingTests.swift --------- Co-authored-by: Stephen Celis <stephen.celis@gmail.com>
1 parent 4f47eba commit a27c2d4

File tree

3 files changed

+119
-92
lines changed

3 files changed

+119
-92
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,21 @@ jobs:
1818
name: macOS
1919
strategy:
2020
matrix:
21-
xcode: ['16.4']
21+
xcode: ['16.4', '26.0']
2222
config:
2323
- debug
24-
# - release
24+
- release
2525
runs-on: macos-15
2626
steps:
27-
- uses: actions/checkout@v4
27+
- uses: actions/checkout@v5
2828
- name: Select Xcode ${{ matrix.xcode }}
2929
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
3030
- name: List available devices
3131
run: xcrun simctl list devices available
32-
# NB: GitHub CI only contains newer runtimes
33-
# - name: Run ${{ matrix.config }} tests
34-
# run: swift test -c ${{ matrix.config }}
32+
if: ${{ matrix.config == 'debug' }}
33+
# NB: GitHub CI only contains newer runtimes, but we force tests to build against deployment target predating Observation
34+
- name: Run ${{ matrix.config }} tests
35+
run: swift test -c ${{ matrix.config }} -Xswiftc -target -Xswiftc arm64-apple-macosx13.0
3536
- name: Run compatibility tests
3637
run: make test-compatibility
3738
if: ${{ matrix.config == 'debug' }}
@@ -45,15 +46,15 @@ jobs:
4546
runs-on: ubuntu-latest
4647
container: swift:${{ matrix.swift }}
4748
steps:
48-
- uses: actions/checkout@v4
49+
- uses: actions/checkout@v5
4950
- name: Build
5051
run: swift build
5152

5253
wasm:
5354
name: Wasm
5455
runs-on: ubuntu-latest
5556
steps:
56-
- uses: actions/checkout@v4
57+
- uses: actions/checkout@v5
5758
- uses: bytecodealliance/actions/wasmtime/setup@v1
5859
- name: Install Swift and Swift SDK for WebAssembly
5960
run: |

Tests/PerceptionTests/PerceptionCheckingTests.swift

Lines changed: 106 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,25 @@
44
import SwiftUI
55
import XCTest
66

7-
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
7+
@available(iOS, introduced: 16, deprecated: 17, obsoleted: 17)
8+
@available(macOS, introduced: 13, deprecated: 14, obsoleted: 14)
9+
@available(tvOS, introduced: 16, deprecated: 17, obsoleted: 17)
10+
@available(watchOS, introduced: 9, deprecated: 10, obsoleted: 10)
811
final class PerceptionCheckingTests: XCTestCase {
12+
override func setUp() async throws {
13+
guard !deploymentTargetIncludesObservation() else {
14+
throw XCTSkip(
15+
"""
16+
PerceptionTests were built against a deployment target too recent for perception checking.
17+
18+
To force these tests to run on macOS, you can override the target OS version explicitly as:
19+
20+
swift test -Xswiftc -target -Xswiftc arm64-apple-macosx13.0
21+
"""
22+
)
23+
}
24+
}
25+
926
@MainActor
1027
func testNotInPerceptionBody() {
1128
let model = Model()
@@ -65,35 +82,35 @@
6582
try await render(FeatureView())
6683
}
6784

68-
#if !os(macOS)
69-
@MainActor
70-
func testNotInPerceptionBody_SwiftUIBinding() async throws {
71-
struct FeatureView: View {
72-
@Perception.Bindable var model: Model
73-
var body: some View {
74-
Form {
75-
TextField("", text: expectRuntimeWarning { $model.text })
76-
}
85+
@MainActor
86+
func testNotInPerceptionBody_SwiftUIBinding() async throws {
87+
struct FeatureView: View {
88+
@Perception.Bindable var model: Model
89+
var body: some View {
90+
Form {
91+
TextField("", text: expectRuntimeWarning { $model.text })
7792
}
7893
}
79-
try await render(FeatureView(model: Model()))
8094
}
81-
#endif
95+
#if os(macOS)
96+
// NB: This failure is triggered out-of-body by the binding.
97+
XCTExpectFailure { $0.compactDescription.contains("Perceptible state was accessed") }
98+
#endif
99+
try await render(FeatureView(model: Model()))
100+
}
82101

83-
#if !os(macOS)
84-
@MainActor
85-
func testInPerceptionBody_SwiftUIBinding() async throws {
86-
struct FeatureView: View {
87-
@Perception.Bindable var model: Model
88-
var body: some View {
89-
WithPerceptionTracking {
90-
TextField("", text: $model.text)
91-
}
102+
@MainActor
103+
func testInPerceptionBody_SwiftUIBinding() async throws {
104+
struct FeatureView: View {
105+
@Perception.Bindable var model: Model
106+
var body: some View {
107+
WithPerceptionTracking {
108+
TextField("", text: $model.text)
92109
}
93110
}
94-
try await render(FeatureView(model: Model()))
95111
}
96-
#endif
112+
try await render(FeatureView(model: Model()))
113+
}
97114

98115
@MainActor
99116
func testNotInPerceptionBody_ForEach() async throws {
@@ -199,83 +216,75 @@
199216
)
200217
}
201218

202-
#if !os(macOS)
203-
@MainActor
204-
func testNotInPerceptionBody_Sheet() async throws {
205-
struct FeatureView: View {
206-
@Perception.Bindable var model: Model
207-
var body: some View {
219+
@MainActor
220+
func testNotInPerceptionBody_Sheet() async throws {
221+
struct FeatureView: View {
222+
@Perception.Bindable var model: Model
223+
var body: some View {
224+
Text("Parent")
225+
.sheet(item: expectRuntimeWarning { $model.child }) { child in
226+
Text(expectRuntimeWarning { child.count }.description)
227+
}
228+
}
229+
}
230+
// NB: This failure is triggered out-of-body by the binding.
231+
XCTExpectFailure { $0.compactDescription.contains("Perceptible state was accessed") }
232+
try await render(FeatureView(model: Model(child: Model())))
233+
}
234+
235+
@MainActor
236+
func testInnerInPerceptionBody_Sheet() async throws {
237+
struct FeatureView: View {
238+
@Perception.Bindable var model: Model
239+
var body: some View {
240+
Text("Parent")
241+
.sheet(item: expectRuntimeWarning { $model.child }) { child in
242+
WithPerceptionTracking {
243+
Text(child.count.description)
244+
}
245+
}
246+
}
247+
}
248+
// NB: This failure is triggered out-of-body by the binding.
249+
XCTExpectFailure { $0.compactDescription.contains("Perceptible state was accessed") }
250+
try await render(FeatureView(model: Model(child: Model())))
251+
}
252+
253+
@MainActor
254+
func testOuterInPerceptionBody_Sheet() async throws {
255+
struct FeatureView: View {
256+
@Perception.Bindable var model: Model
257+
var body: some View {
258+
WithPerceptionTracking {
208259
Text("Parent")
209-
.sheet(item: expectRuntimeWarning { $model.child }) { child in
260+
.sheet(item: $model.child) { child in
210261
Text(expectRuntimeWarning { child.count }.description)
211262
}
212263
}
213264
}
214-
// NB: This failure is triggered out-of-body by the binding.
215-
XCTExpectFailure { $0.compactDescription.contains("Perceptible state was accessed") }
216-
try await render(FeatureView(model: Model(child: Model())))
217265
}
218-
#endif
219266

220-
#if !os(macOS)
221-
@MainActor
222-
func testInnerInPerceptionBody_Sheet() async throws {
223-
struct FeatureView: View {
224-
@Perception.Bindable var model: Model
225-
var body: some View {
267+
try await render(FeatureView(model: Model(child: Model())))
268+
}
269+
270+
@MainActor
271+
func testOuterAndInnerInPerceptionBody_Sheet() async throws {
272+
struct FeatureView: View {
273+
@Perception.Bindable var model: Model
274+
var body: some View {
275+
WithPerceptionTracking {
226276
Text("Parent")
227-
.sheet(item: expectRuntimeWarning { $model.child }) { child in
277+
.sheet(item: $model.child) { child in
228278
WithPerceptionTracking {
229279
Text(child.count.description)
230280
}
231281
}
232282
}
233283
}
234-
// NB: This failure is triggered out-of-body by the binding.
235-
XCTExpectFailure { $0.compactDescription.contains("Perceptible state was accessed") }
236-
try await render(FeatureView(model: Model(child: Model())))
237-
}
238-
#endif
239-
240-
#if !os(macOS)
241-
@MainActor
242-
func testOuterInPerceptionBody_Sheet() async throws {
243-
struct FeatureView: View {
244-
@Perception.Bindable var model: Model
245-
var body: some View {
246-
WithPerceptionTracking {
247-
Text("Parent")
248-
.sheet(item: $model.child) { child in
249-
Text(expectRuntimeWarning { child.count }.description)
250-
}
251-
}
252-
}
253-
}
254-
255-
try await render(FeatureView(model: Model(child: Model())))
256284
}
257-
#endif
258-
259-
#if !os(macOS)
260-
@MainActor
261-
func testOuterAndInnerInPerceptionBody_Sheet() async throws {
262-
struct FeatureView: View {
263-
@Perception.Bindable var model: Model
264-
var body: some View {
265-
WithPerceptionTracking {
266-
Text("Parent")
267-
.sheet(item: $model.child) { child in
268-
WithPerceptionTracking {
269-
Text(child.count.description)
270-
}
271-
}
272-
}
273-
}
274-
}
275285

276-
try await render(FeatureView(model: Model(child: Model())))
277-
}
278-
#endif
286+
try await render(FeatureView(model: Model(child: Model())))
287+
}
279288

280289
@MainActor
281290
func testActionClosure() async throws {
@@ -597,6 +606,7 @@
597606

598607
@MainActor
599608
private func render(_ view: some View) async throws {
609+
try checkImageRendererAvailable()
600610
let image = ImageRenderer(content: view).cgImage
601611
_ = image
602612
try await Task.sleep(for: .seconds(0.1))
@@ -635,4 +645,16 @@
635645
self.content
636646
}
637647
}
648+
649+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
650+
private func deploymentTargetIncludesObservation() -> Bool { true }
651+
652+
@_disfavoredOverload
653+
private func deploymentTargetIncludesObservation(_: Void = ()) -> Bool { false }
654+
655+
private func checkImageRendererAvailable() throws {
656+
guard #available(iOS 16, macOS 13, tvOS 16, watchOS 9, *) else {
657+
throw XCTSkip("This test requires 'SwiftUI.ImageRenderer' to be available.")
658+
}
659+
}
638660
#endif

Tests/PerceptionTests/PerceptionTrackingTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Perception
22
import XCTest
33

4+
@available(iOS, introduced: 16, deprecated: 17)
5+
@available(macOS, introduced: 13, deprecated: 14)
6+
@available(tvOS, introduced: 16, deprecated: 17)
7+
@available(watchOS, introduced: 9, deprecated: 10)
48
final class PerceptionTrackingTests: XCTestCase {
59
var isComplete = false
610

0 commit comments

Comments
 (0)