138 lines
5 KiB
Swift
138 lines
5 KiB
Swift
//
|
|
// PreferenceTests.swift
|
|
//
|
|
// Copyright 2026 Brendan Szymanski <hello@bscubed.dev>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
import Foundation
|
|
@_spi(SGTKInternal) import Gtk
|
|
import LuminateCore
|
|
@_spi(Portico) import Portico
|
|
import Testing
|
|
|
|
@testable import LuminateUI
|
|
|
|
/// Test view that renders the current server URL preference.
|
|
private struct PreferenceLabel: View {
|
|
@Preference(.serverURL) private var serverURL: URL?
|
|
|
|
var body: some View {
|
|
Label(str: "").label { serverURL?.absoluteString ?? "No server configured" }
|
|
}
|
|
}
|
|
|
|
/// Exercises preference mounting, observation, and persistence wiring.
|
|
@Suite(.serialized) @MainActor struct PreferenceTests {
|
|
@Test("An unset preference reads nil at mount")
|
|
func unsetAtMount() {
|
|
guard Gtk.initCheck() else { return }
|
|
let preferences = Preferences(store: EphemeralPreferenceStore())
|
|
let context = MountContext()
|
|
let label =
|
|
AnyView(PreferenceLabel().environment(\.preferences, preferences))
|
|
.makeWidget(context) as! Gtk.Label
|
|
defer { context.registry.teardown() }
|
|
|
|
#expect(label.getText() == "No server configured")
|
|
}
|
|
|
|
@Test("An un-injected preference resolves through the shared coordinator")
|
|
func resolvesWithoutInjection() {
|
|
guard Gtk.initCheck() else { return }
|
|
Preferences.shared[.serverURL] = URL(string: "http://shared.test")
|
|
defer { Preferences.shared[.serverURL] = nil }
|
|
|
|
let context = MountContext()
|
|
let label = AnyView(PreferenceLabel()).makeWidget(context) as! Gtk.Label
|
|
defer { context.registry.teardown() }
|
|
|
|
#expect(label.getText() == "http://shared.test")
|
|
}
|
|
|
|
@Test("Writing a preference updates the same mounted label")
|
|
func liveUpdate() {
|
|
guard Gtk.initCheck() else { return }
|
|
let preferences = Preferences(store: EphemeralPreferenceStore())
|
|
let context = MountContext()
|
|
let label =
|
|
AnyView(PreferenceLabel().environment(\.preferences, preferences))
|
|
.makeWidget(context) as! Gtk.Label
|
|
defer { context.registry.teardown() }
|
|
let pointer = label.pointer
|
|
|
|
preferences.slot(for: .serverURL).value = URL(string: "http://jellyfin.test:8096")
|
|
pump {
|
|
label.getText() == "http://jellyfin.test:8096"
|
|
}
|
|
|
|
#expect(label.pointer == pointer)
|
|
#expect(label.getText() == "http://jellyfin.test:8096")
|
|
}
|
|
|
|
@Test("A preference write reaches the in-memory backend after flush")
|
|
func persistsToBackend() async throws {
|
|
let store = EphemeralPreferenceStore()
|
|
let preferences = Preferences(store: store)
|
|
preferences.slot(for: .serverURL).value = URL(string: "http://jellyfin.test:8096")
|
|
try await store.flush()
|
|
|
|
#expect(await store.storedValues["server.url"] == .text("http://jellyfin.test:8096"))
|
|
}
|
|
|
|
@Test("A seeded store presents its value at mount")
|
|
func restoresAtMount() {
|
|
guard Gtk.initCheck() else { return }
|
|
let store = EphemeralPreferenceStore(
|
|
initialSnapshot: ["server.url": .text("http://seeded.test")]
|
|
)
|
|
let preferences = Preferences(store: store)
|
|
let context = MountContext()
|
|
let label =
|
|
AnyView(PreferenceLabel().environment(\.preferences, preferences))
|
|
.makeWidget(context) as! Gtk.Label
|
|
defer { context.registry.teardown() }
|
|
|
|
#expect(label.getText() == "http://seeded.test")
|
|
}
|
|
|
|
@Test("Setting a preference to nil enqueues a deletion")
|
|
func deletesFromBackend() async throws {
|
|
let store = EphemeralPreferenceStore(
|
|
initialSnapshot: ["server.url": .text("http://seeded.test")]
|
|
)
|
|
let preferences = Preferences(store: store)
|
|
preferences.slot(for: .serverURL).value = nil
|
|
try await store.flush()
|
|
|
|
#expect(await store.storedValues["server.url"] == nil)
|
|
}
|
|
|
|
private func pump(until condition: () -> Bool = { false }, turns: Int = 200) {
|
|
for _ in 0..<turns {
|
|
if condition() { return }
|
|
_ = preference_g_main_context_iteration(nil, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
// UPSTREAM: Portico should expose a main-loop pump for tests so suites do not need @_silgen_name.
|
|
@_silgen_name("g_main_context_iteration")
|
|
private nonisolated func preference_g_main_context_iteration(
|
|
_ context: UnsafeMutableRawPointer?,
|
|
_ mayBlock: Int32
|
|
) -> Int32
|