portico/Tests/PorticoTests/GeneratedWidgetTests.swift

339 lines
12 KiB
Swift

import Testing
import Adw
@_spi(SGTKInternal) import Gtk
@_spi(Portico) import Portico
@MainActor @Suite(.serialized) struct GeneratedWidgetTests {
// MARK: - Avatar: static init
@Test func avatarStaticInitAndGetter() {
guard Gtk.initCheck() else { return }
let w = AnyView(Avatar(size: 48, text: "AB", showInitials: true))
.makeWidget(MountContext())
guard let avatar = w as? Adw.Avatar else {
Issue.record("Expected Adw.Avatar, got \(type(of: w))")
return
}
#expect(avatar.getSize() == 48)
#expect(avatar.getText() == "AB")
#expect(avatar.getShowInitials() == true)
}
// MARK: - Avatar: binding init
@Test func avatarBindingInitReactive() {
guard Gtk.initCheck() else { return }
let sizeBox = StateBox<Int32>(48)
let textBinding = Binding(get: { Optional("AB") }, set: { _ in })
let initialsBinding = Binding(get: { true }, set: { _ in })
let context = MountContext()
let w = AnyView(Avatar(
size: Binding(sizeBox), text: textBinding, showInitials: initialsBinding
)).makeWidget(context)
guard let avatar = w as? Adw.Avatar else { return }
#expect(avatar.getSize() == 48)
sizeBox.set(64)
#expect(avatar.getSize() == 64)
}
// MARK: - Avatar: closure init
@Test func avatarClosureInitReactive() {
guard Gtk.initCheck() else { return }
let sizeBox = StateBox<Int32>(48)
let w = AnyView(Avatar(
size: { sizeBox.get() },
text: { "X" },
showInitials: { true }
)).makeWidget(MountContext())
guard let avatar = w as? Adw.Avatar else { return }
#expect(avatar.getSize() == 48)
sizeBox.set(96)
#expect(avatar.getSize() == 96)
}
// MARK: - Avatar: property modifier static
@Test func avatarModifierStatic() {
guard Gtk.initCheck() else { return }
let w = AnyView(Avatar(size: 48, text: "X", showInitials: true)
.size(10))
.makeWidget(MountContext())
guard let avatar = w as? Adw.Avatar else { return }
#expect(avatar.getSize() == 10)
}
// MARK: - Avatar: property modifier binding
@Test func avatarModifierBinding() {
guard Gtk.initCheck() else { return }
let box = StateBox<Int32>(32)
let context = MountContext()
let w = AnyView(Avatar(size: 48, text: "X", showInitials: true)
.size(Portico.Binding(box))).makeWidget(context)
guard let avatar = w as? Adw.Avatar else { return }
#expect(avatar.getSize() == 32)
box.set(96)
#expect(avatar.getSize() == 96)
}
// MARK: - Avatar: property modifier closure
@Test func avatarModifierClosure() {
guard Gtk.initCheck() else { return }
let box = StateBox<Int32>(32)
let w = AnyView(Avatar(size: 48, text: "X", showInitials: true)
.size { box.get() })
.makeWidget(MountContext())
guard let avatar = w as? Adw.Avatar else { return }
#expect(avatar.getSize() == 32)
box.set(96)
#expect(avatar.getSize() == 96)
}
// MARK: - ComboRow: property modifier
@Test func comboRowUseSubtitleModifierBinding() {
guard Gtk.initCheck() else { return }
let box = StateBox<Bool>(false)
let context = MountContext()
let w = AnyView(ComboRow().useSubtitle(Portico.Binding(box))).makeWidget(context)
guard let row = w as? Adw.ComboRow else { return }
#expect(row.getUseSubtitle() == false)
box.set(true)
#expect(row.getUseSubtitle() == true)
}
// MARK: - Button: signal modifier and teardown
@Test func buttonSignalModifierMountsAndTearsDown() {
guard Gtk.initCheck() else { return }
let ctx = MountContext()
let w = AnyView(
Button()
.onClicked { }
.onActivate { }
).makeWidget(ctx)
guard let button = w as? Gtk.Button else {
Issue.record("Expected Gtk.Button, got \(type(of: w))")
return
}
_ = button
ctx.registry.teardown()
}
// MARK: - Spinner: no properties, just mounts
@Test func spinnerHasNoProps() {
guard Gtk.initCheck() else { return }
let w = AnyView(Spinner()).makeWidget(MountContext())
#expect(w is Adw.Spinner)
}
// MARK: - Banner: optional title
@Test func bannerTitleIsOptionalString() {
guard Gtk.initCheck() else { return }
let w = AnyView(Banner(title: "Test")).makeWidget(MountContext())
guard let banner = w as? Adw.Banner else { return }
#expect(banner.getTitle() == "Test")
}
// MARK: - StatusPage: nullable property driven by a non-optional binding
@Test func nullablePropertyAcceptsNonOptionalBinding() {
guard Gtk.initCheck() else { return }
let box = StateBox<String>("first")
let context = MountContext()
let w = AnyView(StatusPage().description(Portico.Binding(box))).makeWidget(context)
guard let page = w as? Adw.StatusPage else { return }
#expect(page.getDescription() == "first")
box.set("second")
#expect(page.getDescription() == "second")
}
// MARK: - DropDown: nullable property cleared through an optional binding
@Test func nullablePropertyBindingCanClear() {
guard Gtk.initCheck() else { return }
let factory = Gtk.SignalListItemFactory()
let box = StateBox<Gtk.ListItemFactory?>(factory)
let context = MountContext()
let w = AnyView(DropDown(model: nil, expression: nil).factory(Portico.Binding(box)))
.makeWidget(context)
guard let dropDown = w as? Gtk.DropDown else { return }
#expect(dropDown.getFactory() != nil)
box.set(nil)
#expect(dropDown.getFactory() == nil)
}
// MARK: - Reactive teardown
@Test func reactiveTeardownStopsUpdates() {
guard Gtk.initCheck() else { return }
let box = StateBox<Int32>(48)
let ctx = MountContext()
let w = AnyView(Avatar(size: 48, text: "T", showInitials: false).size(Binding(box)))
.makeWidget(ctx)
guard let avatar = w as? Adw.Avatar else { return }
#expect(avatar.getSize() == 48)
ctx.registry.teardown()
box.set(100)
#expect(avatar.getSize() == 48, "size should not change after teardown")
}
// MARK: - Child-adder generated APIs
/// Two rows plus a header suffix, proving children land as rows and the slot still binds.
@Test func preferencesGroupChildrenAndHeaderSuffix() {
guard Gtk.initCheck() else { return }
let w = AnyView(PreferencesGroup {
ActionRow()
ActionRow()
} headerSuffix: {
Label(str: "suffix")
}).makeWidget(MountContext())
guard let group = w as? Adw.PreferencesGroup else {
Issue.record("Expected Adw.PreferencesGroup, got \(type(of: w))")
return
}
#expect(group.getRow(index: 0) != nil)
#expect(group.getRow(index: 1) != nil)
#expect(group.getRow(index: 2) == nil)
#expect(group.getHeaderSuffix() != nil)
}
/// The generated `children:` builder adds children in closure order.
@Test func stackChildrenInitAddsInOrder() {
guard Gtk.initCheck() else { return }
let w = AnyView(Stack {
Label(str: "first")
Label(str: "second")
}).makeWidget(MountContext())
guard let stack = w as? Gtk.Stack else {
Issue.record("Expected Gtk.Stack, got \(type(of: w))")
return
}
// getFirstChild returns a base Widget; reconstruct Label from the pointer.
guard let a = stack.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")
#expect(b.getNextSibling() == nil)
}
/// `Adw.Squeezer` has no `Widget` slot, so its whole content API is generated from
/// `add(child:)`; this also exercises the static `Gtk.Widget` modifier overload.
@Test func squeezerStaticAddModifierAddsChild() {
guard Gtk.initCheck() else { return }
let child = Gtk.Button(label: "a")
let w = AnyView(Squeezer().add(child)).makeWidget(MountContext())
guard let squeezer = w as? Adw.Squeezer else {
Issue.record("Expected Adw.Squeezer, got \(type(of: w))")
return
}
#expect(squeezer.getVisibleChild()?.pointer == child.pointer)
}
/// `Adw.ViewStack.visibleChild` is deliberately not a slot; the children builder is
/// what makes a page visible.
@Test func viewStackChildrenInitMakesChildVisible() {
guard Gtk.initCheck() else { return }
let w = AnyView(ViewStack { Label(str: "x") }).makeWidget(MountContext())
guard let stack = w as? Adw.ViewStack else {
Issue.record("Expected Adw.ViewStack, got \(type(of: w))")
return
}
#expect(stack.getVisibleChild() != nil)
}
// MARK: - Optional initializer values
@Test func optionalInitializerValuesApply() {
guard Gtk.initCheck() else { return }
let w = AnyView(Label(str: "x", useMarkup: true, xalign: 0.25))
.makeWidget(MountContext())
guard let label = w as? Gtk.Label else { return }
#expect(label.getUseMarkup() == true)
#expect(label.getXalign() == 0.25)
}
@Test func omittedOptionalInitializerValueKeepsDefault() {
guard Gtk.initCheck() else { return }
let w = AnyView(Label(str: "x")).makeWidget(MountContext())
guard let label = w as? Gtk.Label else { return }
#expect(label.getUseMarkup() == false)
}
// MARK: - Signal initializer parameter
@Test func signalInitializerClosureFires() {
guard Gtk.initCheck() else { return }
var fired = false
let ctx = MountContext()
let w = AnyView(CheckButton(label: "x", onToggled: { fired = true }))
.makeWidget(ctx)
guard let check = w as? Gtk.CheckButton else { return }
check.setActive(setting: true)
#expect(fired)
ctx.registry.teardown()
}
// MARK: - Optional binding initializer parameter
@Test func optionalInitializerBindingIsTwoWay() {
guard Gtk.initCheck() else { return }
let size = StateBox<Int32>(48)
let text = StateBox<String?>("AB")
let showInitials = StateBox<Bool>(true)
let icon = StateBox<String?>(nil)
let ctx = MountContext()
let w = AnyView(Avatar(
size: Binding(size),
text: Binding(text),
showInitials: Binding(showInitials),
iconName: Binding(icon)
)).makeWidget(ctx)
guard let avatar = w as? Adw.Avatar else { return }
icon.set("face-smile-symbolic")
#expect(avatar.getIconName() == "face-smile-symbolic")
avatar.setIconName(iconName: "face-sad-symbolic")
#expect(icon.get() == "face-sad-symbolic")
ctx.registry.teardown()
}
// MARK: - Required single-view builder
@Test func requiredBuilderMountsNavigationPageChild() {
guard Gtk.initCheck() else { return }
let w = AnyView(NavigationPage(title: "x") { Label(str: "body") })
.makeWidget(MountContext())
guard let page = w as? Adw.NavigationPage, let child = page.getChild() else {
Issue.record("Expected NavigationPage child")
return
}
#expect(Gtk.Label(retaining: child.pointer).getText() == "body")
}
/// Inherited properties, slots and signals are available on generated initializers.
@Test func inheritedInitializerParamsApplyAndBind() {
guard Gtk.initCheck() else { return }
let ctx = MountContext()
let view = AnyView(
ButtonRow(title: "Row", activatable: false, selectable: true, child: {
Label(str: "child")
}, onActivate: { })
)
let w = view.makeWidget(ctx) as! Adw.ButtonRow
#expect(w.getTitle() == "Row")
#expect(w.getActivatable() == false)
#expect(w.getSelectable() == true)
#expect(w.getChild().map { Gtk.Label(retaining: $0.pointer).getText() } == "child")
#expect(!ctx.registry.isEmpty)
ctx.registry.teardown()
}
}