Skip to content

Instantly share code, notes, and snippets.

@Kyle-Ye
Created September 16, 2025 05:52
Show Gist options
  • Save Kyle-Ye/f2787d470e31e76006a51b4bcae4c8dd to your computer and use it in GitHub Desktop.
Save Kyle-Ye/f2787d470e31e76006a51b4bcae4c8dd to your computer and use it in GitHub Desktop.
Unit Test SwiftUI redraws demo from OpenSwiftUI code
import Testing
import SwiftUI
@testable import Demo
typealias PlatformViewController = UIViewController
typealias PlatformWindow = UIWindow
typealias PlatformHostingController = UIHostingController
extension PlatformViewController {
// NOTE: Remember to withExtendedLifetime for window to ensure it is not deallocated duration animation or update.
func triggerLayout() {
#if os(iOS)
let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
window.rootViewController = self
window.makeKeyAndVisible()
view.layoutIfNeeded()
#else
let window = NSWindow(
contentRect: CGRect(x: 0, y: 0, width: 100, height: 100),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
window.contentViewController = self
window.makeKeyAndOrderFront(nil)
view.layoutSubtreeIfNeeded()
#endif
}
}
@MainActor
func triggerLayoutWithWindow(
expectedCount: Int = 1,
_ body: @escaping @MainActor (Confirmation, UnsafeContinuation<Void, Never>) -> PlatformViewController
) async throws {
var window: PlatformWindow!
await confirmation(expectedCount: expectedCount) { @MainActor confirmation in
await withUnsafeContinuation { (continuation: UnsafeContinuation<Void, Never>) in
let vc = body(confirmation, continuation)
vc.triggerLayout()
window = vc.view.window
}
}
#if os(macOS)
window.isReleasedWhenClosed = false
window.close()
#endif
withExtendedLifetime(window) {}
}
struct DemoTests {
class Model: ObservableObject {
@Published var name = ""
@Published var count = 0
}
struct ContentView: View {
@StateObject private var m = Model()
var confirmation: Confirmation
var continuation: UnsafeContinuation<Void, Never>
var body: some View {
let _ = confirmation()
VStack {
Text(m.name)
Text(m.count.description)
}
.onAppear {
DispatchQueue.main.async {
m.name = "A"
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
m.count = 1
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
continuation.resume()
}
}
}
}
}
}
@Test
func observeCount() async throws {
try await triggerLayoutWithWindow(expectedCount: 3) { confirmation, continuation in
PlatformHostingController(
rootView: ContentView(
confirmation: confirmation,
continuation: continuation
)
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment