import Testing @_spi(Portico) import Portico @_spi(SGTKInternal) import Gtk import Adw @_silgen_name("g_main_context_iteration") private nonisolated func appEnv_g_main_context_iteration( _ context: UnsafeMutableRawPointer?, _ mayBlock: Int32 ) -> Int32 /// Drives the GLib main context until `condition` holds or `turns` is exhausted. /// Returns `true` if the loop exhausted turns without the condition becoming true. @MainActor private func pump(until condition: () -> Bool, turns: Int = 200) -> Bool { for _ in 0..? } /// A wrapper with no lazy fallback, standing in for a third-party `DynamicProperty` /// that has nothing to read before `_resolve` runs. @propertyWrapper @MainActor private struct AppFlag: DynamicProperty { private let storage = FlagStorage() init() {} func _resolve(in environment: EnvironmentValues) { storage.box = environment._box(for: \.appFlag) } /// `nil` until resolution; a tracking read afterwards. var wrappedValue: Bool? { storage.box?.get() } var isResolved: Bool { storage.box != nil } } private struct ProbeApp: App { @AppFlag var flag: Bool? /// Surfaces the wrapper's resolution state; `_flag` is not reachable from the suite type. var isResolved: Bool { _flag.isResolved } var body: some Scene { if flag == true { ApplicationWindow { _ in Gtk.Label(str: "on") }.title("On") } else { ApplicationWindow { _ in Gtk.Label(str: "off") }.title("Off") } } } @MainActor @Suite(.serialized) struct AppEnvironmentTests { @Test func rootResolutionBindsAppDynamicProperties() { let box = EnvironmentValues()._box(for: \.appFlag)! box.set(false) let app = ProbeApp() #expect(!app.isResolved) PorticoRuntime._resolveRootProperties(app) #expect(app.isResolved) #expect(app.flag == false) } @Test func sceneConditionalTracksAppDynamicProperty() { guard Gtk.initCheck() else { return } let box = EnvironmentValues()._box(for: \.appFlag)! box.set(false) let app = ProbeApp() PorticoRuntime._resolveRootProperties(app) let adwApp = Adw.Application(applicationId: nil, flags: .defaultFlags) let host = SceneHost(app: adwApp, makeScene: { app.body }) host.start() #expect(host.liveIdentity == .second(.leaf)) #expect(host.live?.window.getTitle() == "Off") box.set(true) let timedOut = pump(until: { host.liveIdentity == .first(.leaf) }) #expect(!timedOut, "pump timed out waiting for branch swap") #expect(host.live?.window.getTitle() == "On") } }