339 lines
12 KiB
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()
|
|
}
|
|
|
|
}
|