import tempfile import os import pytest Gtk = pytest.importorskip("gi.repository.Gtk") GLib = pytest.importorskip("gi.repository.GLib") GObject = pytest.importorskip("gi.repository.GObject") Gio = pytest.importorskip("gi.repository.Gio") from .helper import capture_exceptions GTK4 = (Gtk._version == "4.0") def new_gtype_name(_count=[0]): _count[0] += 1 return "GtkTemplateTest%d" % _count[0] def ensure_resource_registered(): resource_path = "/org/gnome/pygobject/test/a.ui" def is_registered(path): try: Gio.resources_get_info(path, Gio.ResourceLookupFlags.NONE) except GLib.Error: return False return True if is_registered(resource_path): return resource_path gresource_data = ( b'GVariant\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00' b'\xc8\x00\x00\x00\x00\x00\x00(\x06\x00\x00\x00\x00\x00\x00\x00' b'\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00' b'\x06\x00\x00\x00KP\x90\x0b\x03\x00\x00\x00\xc8\x00\x00\x00' b'\x04\x00L\x00\xcc\x00\x00\x00\xd0\x00\x00\x00\xb0\xb7$0' b'\x00\x00\x00\x00\xd0\x00\x00\x00\x06\x00L\x00\xd8\x00\x00\x00' b'\xdc\x00\x00\x00f\xc30\xd1\x01\x00\x00\x00\xdc\x00\x00\x00' b'\n\x00L\x00\xe8\x00\x00\x00\xec\x00\x00\x00\xd4\xb5\x02\x00' b'\xff\xff\xff\xff\xec\x00\x00\x00\x01\x00L\x00\xf0\x00\x00\x00' b'\xf4\x00\x00\x005H}\xe3\x02\x00\x00\x00\xf4\x00\x00\x00' b'\x05\x00L\x00\xfc\x00\x00\x00\x00\x01\x00\x00\xa2^\xd6t' b'\x04\x00\x00\x00\x00\x01\x00\x00\x04\x00v\x00\x08\x01\x00\x00' b'\xa5\x01\x00\x00org/\x01\x00\x00\x00gnome/\x00\x00\x02\x00\x00\x00' b'pygobject/\x00\x00\x04\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00' b'test/\x00\x00\x00\x05\x00\x00\x00a.ui\x00\x00\x00\x00' b'\x8d\x00\x00\x00\x00\x00\x00\x00\n \n\n\x00\x00(uuay)' ) resource = Gio.Resource.new_from_data(GLib.Bytes.new(gresource_data)) Gio.resources_register(resource) assert is_registered(resource_path) return resource_path def test_allow_init_template_call(): type_name = new_gtype_name() xml = """\ """.format(type_name) @Gtk.Template.from_string(xml) class Foo(Gtk.Box): __gtype_name__ = type_name def __init__(self): super(Foo, self).__init__() self.init_template() # Stop current pygobject from handling the initialisation del Foo.__dontuse_ginstance_init__ Foo() def test_init_template_second_instance(): type_name = new_gtype_name() xml = """\ """.format(type_name) @Gtk.Template.from_string(xml) class Foo(Gtk.Box): __gtype_name__ = type_name label = Gtk.Template.Child("label") def __init__(self): super(Foo, self).__init__() self.init_template() # Stop current pygobject from handling the initialisation del Foo.__dontuse_ginstance_init__ foo = Foo() assert isinstance(foo.label, Gtk.Label) foo2 = Foo() assert isinstance(foo2.label, Gtk.Label) def test_main_example(): type_name = new_gtype_name() example_xml = """\ """.format(type_name) @Gtk.Template.from_string(example_xml) class Foo(Gtk.Box): __gtype_name__ = type_name def __init__(self): super(Foo, self).__init__() self.callback_hello = [] self.callback_hello_after = [] self.callback_goodbye = [] self.callback_goodbye_after = [] @Gtk.Template.Callback("hello_button_clicked") def _hello_button_clicked(self, *args): self.callback_hello.append(args) @Gtk.Template.Callback("hello_button_clicked_after") def _hello_after(self, *args): self.callback_hello_after.append(args) _hello_button = Gtk.Template.Child("hello_button") goodbye_button = Gtk.Template.Child() @Gtk.Template.Callback("goodbye_button_clicked") def _goodbye_button_clicked(self, *args): self.callback_goodbye.append(args) @Gtk.Template.Callback("goodbye_button_clicked_after") def _goodbye_after(self, *args): self.callback_goodbye_after.append(args) w = Foo() assert w.__gtype__.name == type_name assert w.props.orientation == Gtk.Orientation.HORIZONTAL assert w.props.spacing == 4 assert isinstance(w._hello_button, Gtk.Button) assert w._hello_button.props.label == "Hello World" assert isinstance(w.goodbye_button, Gtk.Button) assert w.goodbye_button.props.label == "Goodbye World" assert w.callback_hello == [] w._hello_button.emit("clicked") assert w.callback_hello == [(w,)] assert w.callback_hello_after == [(w,)] assert w.callback_goodbye == [] w.goodbye_button.emit("clicked") assert w.callback_goodbye == [(w.goodbye_button,)] assert w.callback_goodbye_after == [(w.goodbye_button,)] def test_duplicate_handler(): type_name = new_gtype_name() xml = """\ """.format(type_name) class Foo(Gtk.Box): __gtype_name__ = type_name @Gtk.Template.Callback("hello_button_clicked") def _hello_button_clicked(self, *args): pass @Gtk.Template.Callback() def hello_button_clicked(self, *args): pass with pytest.raises(RuntimeError, match=".*hello_button_clicked.*"): Gtk.Template.from_string(xml)(Foo) def test_duplicate_child(): type_name = new_gtype_name() xml = """\ """.format(type_name) class Foo(Gtk.Box): __gtype_name__ = type_name foo = Gtk.Template.Child("hello_button") hello_button = Gtk.Template.Child() with pytest.raises(RuntimeError, match=".*hello_button.*"): Gtk.Template.from_string(xml)(Foo) def test_nonexist_handler(): type_name = new_gtype_name() xml = """\ """.format(type_name) @Gtk.Template.from_string(xml) class Foo(Gtk.Box): __gtype_name__ = type_name @Gtk.Template.Callback("nonexit") def foo(self, *args): pass with capture_exceptions() as exc_info: Foo() assert "nonexit" in str(exc_info[0].value) assert exc_info[0].type is RuntimeError def test_missing_handler_callback(): type_name = new_gtype_name() xml = """\ """.format(type_name) class Foo(Gtk.Box): __gtype_name__ = type_name Gtk.Template.from_string(xml)(Foo)() def test_handler_swapped_not_supported(): type_name = new_gtype_name() xml = """\ """.format(type_name) @Gtk.Template.from_string(xml) class Foo(Gtk.Box): __gtype_name__ = type_name hello_button = Gtk.Template.Child() @Gtk.Template.Callback("hello_button_clicked") def foo(self, *args): pass with capture_exceptions() as exc_info: Foo() assert "G_CONNECT_SWAPPED" in str(exc_info[0].value) def test_handler_class_staticmethod(): type_name = new_gtype_name() xml = """\ """.format(type_name) signal_args_class = [] signal_args_static = [] @Gtk.Template.from_string(xml) class Foo(Gtk.Box): __gtype_name__ = type_name hello_button = Gtk.Template.Child() @Gtk.Template.Callback("clicked_class") @classmethod def cb1(*args): signal_args_class.append(args) @Gtk.Template.Callback("clicked_static") @staticmethod def cb2(*args): signal_args_static.append(args) foo = Foo() foo.hello_button.emit("clicked") assert signal_args_class == [(Foo, foo.hello_button)] assert signal_args_static == [(foo.hello_button,)] @pytest.mark.skipif(Gtk._version == "4.0", reason="errors out first with gtk4") def test_check_decorated_class(): NonWidget = type("Foo", (object,), {}) with pytest.raises(TypeError, match=".*on Widgets.*"): Gtk.Template.from_string("")(NonWidget) Widget = type("Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()}) with pytest.raises(TypeError, match=".*Cannot nest.*"): Gtk.Template.from_string("")(Gtk.Template.from_string("")(Widget)) Widget = type("Foo", (Gtk.Widget,), {}) with pytest.raises(TypeError, match=".*__gtype_name__.*"): Gtk.Template.from_string("")(Widget) with pytest.raises(TypeError, match=".*on Widgets.*"): Gtk.Template.from_string("")(object()) @pytest.mark.skipif(Gtk._version == "4.0", reason="errors out first with gtk4") def test_subclass_fail(): @Gtk.Template.from_string("") class Base(Gtk.Widget): __gtype_name__ = new_gtype_name() with capture_exceptions() as exc_info: type("Sub", (Base,), {})() assert "not allowed at this time" in str(exc_info[0].value) assert exc_info[0].type is TypeError def test_from_file(): fd, name = tempfile.mkstemp() try: os.close(fd) type_name = new_gtype_name() with open(name, "wb") as h: h.write(u"""\ """.format(type_name).encode()) @Gtk.Template.from_file(name) class Foo(Gtk.Box): __gtype_name__ = type_name foo = Foo() assert foo.props.spacing == 42 finally: os.remove(name) def test_property_override(): type_name = new_gtype_name() xml = """\ """.format(type_name) @Gtk.Template.from_string(xml) class Foo(Gtk.Box): __gtype_name__ = type_name foo = Foo() assert foo.props.spacing == 42 foo = Foo(spacing=124) assert foo.props.spacing == 124 def test_from_file_non_exist(): dirname = tempfile.mkdtemp() try: path = os.path.join(dirname, "noexist") Widget = type( "Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()}) with pytest.raises(GLib.Error, match=".*No such file.*"): Gtk.Template.from_file(path)(Widget) finally: os.rmdir(dirname) def test_from_string_bytes(): type_name = new_gtype_name() xml = u"""\ """.format(type_name).encode() @Gtk.Template.from_string(xml) class Foo(Gtk.Box): __gtype_name__ = type_name foo = Foo() assert foo.props.spacing == 42 def test_from_resource(): resource_path = ensure_resource_registered() @Gtk.Template.from_resource(resource_path) class Foo(Gtk.Box): __gtype_name__ = "GtkTemplateTestResource" foo = Foo() assert foo.props.spacing == 42 def test_from_resource_non_exit(): Widget = type("Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()}) with pytest.raises(GLib.Error, match=".*/or/gnome/pygobject/noexit.*"): Gtk.Template.from_resource("/or/gnome/pygobject/noexit")(Widget) def test_constructors(): with pytest.raises(TypeError): Gtk.Template() with pytest.raises(TypeError): Gtk.Template(foo=1) Gtk.Template(filename="foo") Gtk.Template(resource_path="foo") Gtk.Template(string="foo") with pytest.raises(TypeError): Gtk.Template(filename="foo", resource_path="bar") with pytest.raises(TypeError): Gtk.Template(filename="foo", nope="bar") Gtk.Template.from_string("bla") Gtk.Template.from_resource("foo") Gtk.Template.from_file("foo") def test_child_construct(): Gtk.Template.Child() Gtk.Template.Child("name") with pytest.raises(TypeError): Gtk.Template.Child("name", True) Gtk.Template.Child("name", internal=True) with pytest.raises(TypeError): Gtk.Template.Child("name", internal=True, something=False) def test_internal_child(): main_type_name = new_gtype_name() xml = """\ """.format(main_type_name) @Gtk.Template.from_string(xml) class MainThing(Gtk.Box): __gtype_name__ = main_type_name somechild = Gtk.Template.Child(internal=True) thing = MainThing() assert thing.somechild.props.margin_top == 42 other_type_name = new_gtype_name() xml = """\ """.format(other_type_name, main_type_name) @Gtk.Template.from_string(xml) class OtherThing(Gtk.Box): __gtype_name__ = other_type_name other = OtherThing() if not GTK4: child = other.get_children()[0] else: child = other.get_first_child() assert isinstance(child, MainThing) if not GTK4: child = child.get_children()[0] else: child = child.get_first_child() assert isinstance(child, Gtk.Box) assert child.props.margin_top == 24 if not GTK4: child = child.get_children()[0] else: child = child.get_first_child() assert isinstance(child, Gtk.Label) assert child.props.label == "foo" def test_template_hierarchy(): testlabel = """ """ @Gtk.Template(string=testlabel) class TestLabel(Gtk.Label): __gtype_name__ = "TestLabel" def __init__(self): super().__init__() self.props.label = "TestLabel" testbox = """ """ @Gtk.Template(string=testbox) class TestBox(Gtk.Box): __gtype_name__ = "TestBox" _testlabel = Gtk.Template.Child() def __init__(self): super().__init__() assert isinstance(self._testlabel, TestLabel) window = """ """ @Gtk.Template(string=window) class MyWindow(Gtk.Window): __gtype_name__ = "MyWindow" _testbox = Gtk.Template.Child() _testlabel = Gtk.Template.Child() def __init__(self): super().__init__() assert isinstance(self._testbox, TestBox) assert isinstance(self._testlabel, TestLabel) if not GTK4: children = self._testbox.get_children() else: children = list(self._testbox) assert len(children) == 2 win = MyWindow() assert isinstance(win, MyWindow) def test_multiple_init_template_calls(): xml = """ """ @Gtk.Template(string=xml) class MyBox(Gtk.Box): __gtype_name__ = "MyBox" _label = Gtk.Template.Child() def __init__(self): super().__init__() self._label.props.label = "awesome label" my_box = MyBox() assert isinstance(my_box, MyBox) if not GTK4: children = my_box.get_children() else: children = list(my_box) assert len(children) == 1 my_box.init_template() assert isinstance(my_box, MyBox) if not GTK4: children = my_box.get_children() else: children = list(my_box) assert len(children) == 1