Conditionals: EitherView and OptionalView with reactive branch visibility
This commit is contained in:
parent
c309fc3d1a
commit
d177a85236
6 changed files with 308 additions and 5 deletions
168
Tests/PorticoTests/ConditionalTests.swift
Normal file
168
Tests/PorticoTests/ConditionalTests.swift
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import Testing
|
||||
@_spi(SGTKInternal) import Gtk
|
||||
@_spi(Portico) import Portico
|
||||
|
||||
@MainActor @Suite(.serialized) struct ConditionalTests {
|
||||
|
||||
// MARK: - EitherView
|
||||
|
||||
@Test func eitherViewInitiallyTrueBranch() {
|
||||
guard Gtk.initCheck() else { return }
|
||||
ConditionProbe.reset()
|
||||
|
||||
let box = StateBox(true)
|
||||
let binding = Binding(box)
|
||||
let view = EitherView(binding,
|
||||
first: { FirstProbeLabel() },
|
||||
second: { SecondProbeLabel() }
|
||||
)
|
||||
let widget = AnyView(view).makeWidget(MountContext())
|
||||
let stack = widget as! Gtk.Stack
|
||||
|
||||
// Initially, only the "true" branch is mounted and visible.
|
||||
#expect(stack.getVisibleChildName() == "true")
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
#expect(ConditionProbe.secondCount == 0)
|
||||
|
||||
// Toggle to false — lazy-mounts the "false" branch.
|
||||
box.set(false)
|
||||
#expect(stack.getVisibleChildName() == "false")
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
#expect(ConditionProbe.secondCount == 1)
|
||||
|
||||
// Toggle back to true — no new mounts.
|
||||
box.set(true)
|
||||
#expect(stack.getVisibleChildName() == "true")
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
#expect(ConditionProbe.secondCount == 1)
|
||||
}
|
||||
|
||||
@Test func eitherViewInitiallyFalseBranch() {
|
||||
guard Gtk.initCheck() else { return }
|
||||
ConditionProbe.reset()
|
||||
|
||||
let box = StateBox(false)
|
||||
let binding = Binding(box)
|
||||
let view = EitherView(binding,
|
||||
first: { FirstProbeLabel() },
|
||||
second: { SecondProbeLabel() }
|
||||
)
|
||||
let widget = AnyView(view).makeWidget(MountContext())
|
||||
let stack = widget as! Gtk.Stack
|
||||
|
||||
// Initially, only the "false" branch is mounted and visible.
|
||||
#expect(stack.getVisibleChildName() == "false")
|
||||
#expect(ConditionProbe.firstCount == 0)
|
||||
#expect(ConditionProbe.secondCount == 1)
|
||||
|
||||
// Toggle to true — lazy-mounts the "true" branch.
|
||||
box.set(true)
|
||||
#expect(stack.getVisibleChildName() == "true")
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
#expect(ConditionProbe.secondCount == 1)
|
||||
|
||||
// Toggle back to false — no new mounts.
|
||||
box.set(false)
|
||||
#expect(stack.getVisibleChildName() == "false")
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
#expect(ConditionProbe.secondCount == 1)
|
||||
}
|
||||
|
||||
@Test func eitherViewTracksConditionChanges() {
|
||||
guard Gtk.initCheck() else { return }
|
||||
ConditionProbe.reset()
|
||||
|
||||
let box = StateBox(true)
|
||||
let binding = Binding(box)
|
||||
let view = EitherView(binding,
|
||||
first: { FirstProbeLabel() },
|
||||
second: { SecondProbeLabel() }
|
||||
)
|
||||
let widget = AnyView(view).makeWidget(MountContext())
|
||||
let stack = widget as! Gtk.Stack
|
||||
|
||||
// Three toggles: true→false→true→false
|
||||
box.set(false)
|
||||
box.set(true)
|
||||
box.set(false)
|
||||
|
||||
#expect(stack.getVisibleChildName() == "false")
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
#expect(ConditionProbe.secondCount == 1)
|
||||
}
|
||||
|
||||
// MARK: - OptionalView
|
||||
|
||||
@Test func optionalViewMountsChildOnce() {
|
||||
guard Gtk.initCheck() else { return }
|
||||
ConditionProbe.reset()
|
||||
|
||||
let box = StateBox(true)
|
||||
let binding = Binding(box)
|
||||
let view = OptionalView(binding) { FirstProbeLabel() }
|
||||
let widget = AnyView(view).makeWidget(MountContext())
|
||||
let revealer = widget as! Gtk.Revealer
|
||||
|
||||
// Initially revealed, child mounted once.
|
||||
#expect(revealer.getRevealChild() == true)
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
|
||||
// Toggle to false — child still mounted, just hidden.
|
||||
box.set(false)
|
||||
#expect(revealer.getRevealChild() == false)
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
|
||||
// Toggle back to true — still same child.
|
||||
box.set(true)
|
||||
#expect(revealer.getRevealChild() == true)
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
}
|
||||
|
||||
@Test func optionalViewHidesWhenConditionFalse() {
|
||||
guard Gtk.initCheck() else { return }
|
||||
ConditionProbe.reset()
|
||||
|
||||
let box = StateBox(false)
|
||||
let binding = Binding(box)
|
||||
let view = OptionalView(binding) { FirstProbeLabel() }
|
||||
let widget = AnyView(view).makeWidget(MountContext())
|
||||
let revealer = widget as! Gtk.Revealer
|
||||
|
||||
// Initially hidden, but child WAS mounted (build-once).
|
||||
#expect(revealer.getRevealChild() == false)
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
|
||||
// Toggle to true — child visible, no remount.
|
||||
box.set(true)
|
||||
#expect(revealer.getRevealChild() == true)
|
||||
#expect(ConditionProbe.firstCount == 1)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test helpers
|
||||
|
||||
@MainActor fileprivate enum ConditionProbe {
|
||||
static var firstCount = 0
|
||||
static var secondCount = 0
|
||||
|
||||
static func reset() {
|
||||
firstCount = 0
|
||||
secondCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor fileprivate struct FirstProbeLabel: View, Mountable {
|
||||
var body: Never { fatalError() }
|
||||
func mount(_ ctx: MountContext) -> Gtk.Widget {
|
||||
ConditionProbe.firstCount += 1
|
||||
return Gtk.Label(str: "first")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor fileprivate struct SecondProbeLabel: View, Mountable {
|
||||
var body: Never { fatalError() }
|
||||
func mount(_ ctx: MountContext) -> Gtk.Widget {
|
||||
ConditionProbe.secondCount += 1
|
||||
return Gtk.Label(str: "second")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue