1- import XCTest
1+ import Testing
2+ import Foundation
23
34@testable import SwiftCrossUI
45
56#if canImport(AppKitBackend)
7+ import AppKit
8+ import CoreGraphics
69 @testable import AppKitBackend
710#endif
811
@@ -18,15 +21,17 @@ struct CounterView: View {
1821 }
1922}
2023
21- struct XCTError : LocalizedError {
24+ struct TestError : LocalizedError {
2225 var message : String
2326
2427 var errorDescription : String ? {
2528 message
2629 }
2730}
2831
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 " )
3035 func testCodableNavigationPath( ) throws {
3136 var path = NavigationPath ( )
3237 path. append ( " a " )
@@ -45,12 +50,13 @@ final class SwiftCrossUITests: XCTestCase {
4550 String . self, Int . self, [ Int ] . self, Double . self,
4651 ] )
4752
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 ] ) )
5257 }
5358
59+ /// Helper function for `testCodableNavigationPath`.
5460 static func compareComponents< T: Equatable > (
5561 ofType type: T . Type , _ original: Any , _ decoded: Any
5662 ) -> Bool {
@@ -64,114 +70,8 @@ final class SwiftCrossUITests: XCTestCase {
6470 return original == decoded
6571 }
6672
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-
11973 #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 " )
17575 @MainActor
17676 func testBasicLayout( ) async throws {
17777 let backend = AppKitBackend ( )
@@ -200,31 +100,31 @@ final class SwiftCrossUITests: XCTestCase {
200100 backend. setSize ( of: view, to: result. size. size)
201101 backend. setSize ( ofWindow: window, to: result. size. size)
202102
203- XCTAssertEqual (
204- result. size,
205- ViewSize ( fixedSize: SIMD2 ( 92 , 96 ) ) ,
103+ #expect(
104+ result. size == ViewSize ( fixedSize: SIMD2 ( 92 , 96 ) ) ,
206105 " View update result mismatch "
207106 )
208107
209- XCTAssert (
108+ #expect (
210109 result. preferences. onOpenURL == nil ,
211110 " onOpenURL not nil "
212111 )
213112 }
214113
114+ /// Snapshots an AppKit view to a TIFF image.
215115 @MainActor
216116 static func snapshotView( _ view: NSView ) throws -> Data {
217117 view. wantsLayer = true
218118 view. layer? . backgroundColor = CGColor . white
219119
220120 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 " )
222122 }
223123
224124 view. cacheDisplay ( in: view. bounds, to: bitmap)
225125
226126 guard let data = bitmap. tiffRepresentation else {
227- throw XCTError ( message: " Failed to create tiff representation " )
127+ throw TestError ( message: " Failed to create tiff representation " )
228128 }
229129
230130 return data
0 commit comments