1
- import XCTest
1
+ import Testing
2
+ import Foundation
2
3
3
4
@testable import SwiftCrossUI
4
5
5
6
#if canImport(AppKitBackend)
7
+ import AppKit
8
+ import CoreGraphics
6
9
@testable import AppKitBackend
7
10
#endif
8
11
@@ -18,15 +21,17 @@ struct CounterView: View {
18
21
}
19
22
}
20
23
21
- struct XCTError : LocalizedError {
24
+ struct TestError : LocalizedError {
22
25
var message : String
23
26
24
27
var errorDescription : String ? {
25
28
message
26
29
}
27
30
}
28
31
29
- final class SwiftCrossUITests : XCTestCase {
32
+ @Suite ( " Testing for SwiftCrossUI " )
33
+ struct SwiftCrossUITests {
34
+ @Test ( " Ensures that a NavigationPath can be round tripped to and from JSON " )
30
35
func testCodableNavigationPath( ) throws {
31
36
var path = NavigationPath ( )
32
37
path. append ( " a " )
@@ -45,12 +50,13 @@ final class SwiftCrossUITests: XCTestCase {
45
50
String . self, Int . self, [ Int ] . self, Double . self,
46
51
] )
47
52
48
- XCTAssert ( Self . compareComponents ( ofType: String . self, components [ 0 ] , decodedComponents [ 0 ] ) )
49
- XCTAssert ( Self . compareComponents ( ofType: Int . self, components [ 1 ] , decodedComponents [ 1 ] ) )
50
- XCTAssert ( Self . compareComponents ( ofType: [ Int ] . self, components [ 2 ] , decodedComponents [ 2 ] ) )
51
- XCTAssert ( Self . compareComponents ( ofType: Double . self, components [ 3 ] , decodedComponents [ 3 ] ) )
53
+ #expect ( Self . compareComponents ( ofType: String . self, components [ 0 ] , decodedComponents [ 0 ] ) )
54
+ #expect ( Self . compareComponents ( ofType: Int . self, components [ 1 ] , decodedComponents [ 1 ] ) )
55
+ #expect ( Self . compareComponents ( ofType: [ Int ] . self, components [ 2 ] , decodedComponents [ 2 ] ) )
56
+ #expect ( Self . compareComponents ( ofType: Double . self, components [ 3 ] , decodedComponents [ 3 ] ) )
52
57
}
53
58
59
+ /// Helper function for `testCodableNavigationPath`.
54
60
static func compareComponents< T: Equatable > (
55
61
ofType type: T . Type , _ original: Any , _ decoded: Any
56
62
) -> Bool {
@@ -64,114 +70,8 @@ final class SwiftCrossUITests: XCTestCase {
64
70
return original == decoded
65
71
}
66
72
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
-
119
73
#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
- ratio <= 0.85 ,
168
- """
169
- Expected throttled updates to take under 85% of the main \
170
- thread's time. Took \( Int ( ratio * 100 ) ) %
171
- """
172
- )
173
- }
174
-
74
+ @Test ( " Ensure that a basic view has the expected dimensions under AppKitBackend " )
175
75
@MainActor
176
76
func testBasicLayout( ) async throws {
177
77
let backend = AppKitBackend ( )
@@ -200,31 +100,31 @@ final class SwiftCrossUITests: XCTestCase {
200
100
backend. setSize ( of: view, to: result. size. size)
201
101
backend. setSize ( ofWindow: window, to: result. size. size)
202
102
203
- XCTAssertEqual (
204
- result. size,
205
- ViewSize ( fixedSize: SIMD2 ( 92 , 96 ) ) ,
103
+ #expect(
104
+ result. size == ViewSize ( fixedSize: SIMD2 ( 92 , 96 ) ) ,
206
105
" View update result mismatch "
207
106
)
208
107
209
- XCTAssert (
108
+ #expect (
210
109
result. preferences. onOpenURL == nil ,
211
110
" onOpenURL not nil "
212
111
)
213
112
}
214
113
114
+ /// Snapshots an AppKit view to a TIFF image.
215
115
@MainActor
216
116
static func snapshotView( _ view: NSView ) throws -> Data {
217
117
view. wantsLayer = true
218
118
view. layer? . backgroundColor = CGColor . white
219
119
220
120
guard let bitmap = view. bitmapImageRepForCachingDisplay ( in: view. bounds) else {
221
- throw XCTError ( message: " Failed to create bitmap backing " )
121
+ throw TestError ( message: " Failed to create bitmap backing " )
222
122
}
223
123
224
124
view. cacheDisplay ( in: view. bounds, to: bitmap)
225
125
226
126
guard let data = bitmap. tiffRepresentation else {
227
- throw XCTError ( message: " Failed to create tiff representation " )
127
+ throw TestError ( message: " Failed to create tiff representation " )
228
128
}
229
129
230
130
return data
0 commit comments