portico/Tests/PorticoTests/ViewBuilderSlotTests.swift

196 lines
6.6 KiB
Swift

import Testing
import Adw
@_spi(SGTKInternal) import Gtk
@_spi(Portico) import Portico
@MainActor @Suite(.serialized) struct ViewBuilderSlotTests {
// MARK: - Single-slot initializer (Clamp)
@Test func clampInitBuilderMountsChild() {
guard Gtk.initCheck() else { return }
let w = AnyView(Clamp { Label(str: "hi") }).makeWidget(MountContext())
guard let clamp = w as? Adw.Clamp else {
Issue.record("Expected Adw.Clamp, got \(type(of: w))")
return
}
#expect(clamp.getChild() != nil)
}
// MARK: - Single-slot modifier
@Test func clampModifierBuilderMountsChild() {
guard Gtk.initCheck() else { return }
let w = AnyView(Clamp().child { Label(str: "hi") }).makeWidget(MountContext())
guard let clamp = w as? Adw.Clamp else {
Issue.record("Expected Adw.Clamp, got \(type(of: w))")
return
}
#expect(clamp.getChild() != nil)
}
// MARK: - Empty closure leaves slot nil
@Test func clampInitBuilderEmptyLeavesChildNil() {
guard Gtk.initCheck() else { return }
let w = AnyView(Clamp { }).makeWidget(MountContext())
guard let clamp = w as? Adw.Clamp else {
Issue.record("Expected Adw.Clamp, got \(type(of: w))")
return
}
#expect(clamp.getChild() == nil)
}
// MARK: - Multi-slot initializer (CenterBox)
@Test func centerBoxInitBuilderMountsAllSlots() {
guard Gtk.initCheck() else { return }
let w = AnyView(CenterBox(
centerWidget: { Label(str: "c") },
endWidget: { Label(str: "e") },
startWidget: { Label(str: "s") }
)).makeWidget(MountContext())
guard let cb = w as? Gtk.CenterBox else {
Issue.record("Expected Gtk.CenterBox, got \(type(of: w))")
return
}
#expect(cb.getCenterWidget() != nil)
#expect(cb.getEndWidget() != nil)
#expect(cb.getStartWidget() != nil)
}
// MARK: - Multi-slot trailing closure (unlabeled = first in declaration order = centerWidget)
@Test func centerBoxTrailingClosureBindsFirstParam() {
guard Gtk.initCheck() else { return }
let w = AnyView(CenterBox {
Label(str: "c")
} endWidget: {
Label(str: "e")
} startWidget: {
Label(str: "s")
}).makeWidget(MountContext())
guard let cb = w as? Gtk.CenterBox else {
Issue.record("Expected Gtk.CenterBox, got \(type(of: w))")
return
}
#expect(cb.getCenterWidget() != nil)
}
@Test func centerBoxNamedSlotsKeepRequiredValue() {
guard Gtk.initCheck() else { return }
let w = AnyView(CenterBox(
baselinePosition: .top,
endWidget: { Label(str: "r") },
startWidget: { Label(str: "l") }
)).makeWidget(MountContext())
guard let cb = w as? Gtk.CenterBox else {
Issue.record("Expected Gtk.CenterBox, got \(type(of: w))")
return
}
#expect(cb.getStartWidget() != nil)
#expect(cb.getEndWidget() != nil)
#expect(cb.getBaselinePosition() == .top)
}
}
@MainActor @Suite(.serialized) struct SequentialContainerInitTests {
/// Counts the direct children of a widget by walking the GTK sibling chain.
private func childCount(of w: Gtk.Widget) -> Int {
var n = 0
var child = w.getFirstChild()
while let c = child {
n += 1
child = c.getNextSibling()
}
return n
}
// MARK: - No-arg base init (WrapBox)
@Test func wrapBoxAppendsAllChildren() {
guard Gtk.initCheck() else { return }
let w = AnyView(WrapBox {
Label(str: "a")
Label(str: "b")
Label(str: "c")
}).makeWidget(MountContext())
#expect(childCount(of: w) == 3)
}
@Test func wrapBoxEmptyClosureLeavesNoChildren() {
guard Gtk.initCheck() else { return }
let w = AnyView(WrapBox { }).makeWidget(MountContext())
#expect(childCount(of: w) == 0)
}
// MARK: - Base params preserved alongside children (Box)
@Test func boxAppendsChildrenAndKeepsBaseParams() {
guard Gtk.initCheck() else { return }
let w = AnyView(Box(orientation: .vertical, spacing: 8) {
Label(str: "a")
Label(str: "b")
}).makeWidget(MountContext())
guard let box = w as? Gtk.Box else {
Issue.record("Expected Gtk.Box, got \(type(of: w))")
return
}
#expect(box.getSpacing() == 8)
#expect(box.getOrientation() == .vertical)
#expect(childCount(of: w) == 2)
}
// MARK: - Ordering is preserved
@Test func boxPreservesChildOrder() {
guard Gtk.initCheck() else { return }
let w = AnyView(Box(orientation: .horizontal, spacing: 0) {
Label(str: "first")
Label(str: "second")
}).makeWidget(MountContext())
// getFirstChild returns a base Widget; reconstruct Label from the pointer.
guard let a = w.getFirstChild(), let b = a.getNextSibling() else {
Issue.record("Expected two children")
return
}
#expect(Gtk.Label(retaining: a.pointer).getText() == "first")
#expect(Gtk.Label(retaining: b.pointer).getText() == "second")
}
// MARK: - Value-returning append (Leaflet / TabView)
/// `Adw.Leaflet.append` returns a `LeafletPage` and wraps children in internal
/// structure, so assert the semantic outcome rather than the raw sibling count.
@Test func leafletAppendsChildrenDespiteReturningPage() {
guard Gtk.initCheck() else { return }
let w = AnyView(Leaflet {
Label(str: "a")
Label(str: "b")
}).makeWidget(MountContext())
guard let leaflet = w as? Adw.Leaflet else {
Issue.record("Expected Adw.Leaflet, got \(type(of: w))")
return
}
#expect(leaflet.getVisibleChild() != nil)
let empty = AnyView(Leaflet { }).makeWidget(MountContext())
guard let emptyLeaflet = empty as? Adw.Leaflet else {
Issue.record("Expected Adw.Leaflet, got \(type(of: empty))")
return
}
#expect(emptyLeaflet.getVisibleChild() == nil)
}
// MARK: - ListBox wraps each child in a row, so assert via first child
@Test func listBoxAppendsChildren() {
guard Gtk.initCheck() else { return }
let w = AnyView(ListBox {
Label(str: "a")
Label(str: "b")
}).makeWidget(MountContext())
#expect(childCount(of: w) == 2)
}
}