102 lines
3.1 KiB
Swift
102 lines
3.1 KiB
Swift
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..<turns {
|
|
if condition() { return false }
|
|
_ = appEnv_g_main_context_iteration(nil, 0)
|
|
}
|
|
return true
|
|
}
|
|
|
|
private enum AppFlagKey: EnvironmentKey {
|
|
static let defaultValue = false
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
fileprivate var appFlag: Bool {
|
|
get { self[AppFlagKey.self] }
|
|
set { self[AppFlagKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
/// Shared reference storage, so every struct copy of the wrapper observes one resolution.
|
|
@MainActor private final class FlagStorage {
|
|
var box: StateBox<Bool>?
|
|
}
|
|
|
|
/// 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")
|
|
}
|
|
}
|