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 42\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 = """\
GTK_ORIENTATION_HORIZONTAL
4
""".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"""\
42
""".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 = """\
42
""".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"""\
42
""".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 = """
"Hellow World"
"""
@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