diff options
Diffstat (limited to 'installed-tests/js/testGtk3.js')
-rw-r--r-- | installed-tests/js/testGtk3.js | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/installed-tests/js/testGtk3.js b/installed-tests/js/testGtk3.js new file mode 100644 index 00000000..288bdbdc --- /dev/null +++ b/installed-tests/js/testGtk3.js @@ -0,0 +1,242 @@ +imports.gi.versions.Gtk = '3.0'; + +const ByteArray = imports.byteArray; +const {GLib, Gio, GObject, Gtk} = imports.gi; +const System = imports.system; + +// This is ugly here, but usually it would be in a resource +function createTemplate(className) { + return ` +<interface> + <template class="${className}" parent="GtkGrid"> + <property name="margin_top">10</property> + <property name="margin_bottom">10</property> + <property name="margin_start">10</property> + <property name="margin_end">10</property> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="label-child"> + <property name="label">Complex!</property> + <property name="visible">True</property> + <signal name="grab-focus" handler="templateCallback" swapped="no"/> + </object> + </child> + <child> + <object class="GtkLabel" id="label-child2"> + <property name="label">Complex as well!</property> + <property name="visible">True</property> + <signal name="grab-focus" handler="boundCallback" object="label-child" swapped="no"/> + </object> + </child> + <child> + <object class="GtkLabel" id="internal-label-child"> + <property name="label">Complex and internal!</property> + <property name="visible">True</property> + </object> + </child> + </template> +</interface>`; +} + +const MyComplexGtkSubclass = GObject.registerClass({ + Template: ByteArray.fromString(createTemplate('Gjs_MyComplexGtkSubclass')), + Children: ['label-child', 'label-child2'], + InternalChildren: ['internal-label-child'], + CssName: 'complex-subclass', +}, class MyComplexGtkSubclass extends Gtk.Grid { + templateCallback(widget) { + this.callbackEmittedBy = widget; + } + + boundCallback(widget) { + widget.callbackBoundTo = this; + } +}); + +// Sadly, putting this in the body of the class will prevent calling +// get_template_child, since MyComplexGtkSubclass will be bound to the ES6 +// class name without the GObject goodies in it +MyComplexGtkSubclass.prototype.testChildrenExist = function () { + this._internalLabel = this.get_template_child(MyComplexGtkSubclass, 'label-child'); + expect(this._internalLabel).toEqual(jasmine.anything()); + + expect(this.label_child2).toEqual(jasmine.anything()); + expect(this._internal_label_child).toEqual(jasmine.anything()); +}; + +const MyComplexGtkSubclassFromResource = GObject.registerClass({ + Template: 'resource:///org/gjs/jsunit/complex3.ui', + Children: ['label-child', 'label-child2'], + InternalChildren: ['internal-label-child'], +}, class MyComplexGtkSubclassFromResource extends Gtk.Grid { + testChildrenExist() { + expect(this.label_child).toEqual(jasmine.anything()); + expect(this.label_child2).toEqual(jasmine.anything()); + expect(this._internal_label_child).toEqual(jasmine.anything()); + } + + templateCallback(widget) { + this.callbackEmittedBy = widget; + } + + boundCallback(widget) { + widget.callbackBoundTo = this; + } +}); + +const [templateFile, stream] = Gio.File.new_tmp(null); +const baseStream = stream.get_output_stream(); +const out = new Gio.DataOutputStream({baseStream}); +out.put_string(createTemplate('Gjs_MyComplexGtkSubclassFromFile'), null); +out.close(null); + +const MyComplexGtkSubclassFromFile = GObject.registerClass({ + Template: templateFile.get_uri(), + Children: ['label-child', 'label-child2'], + InternalChildren: ['internal-label-child'], +}, class MyComplexGtkSubclassFromFile extends Gtk.Grid { + testChildrenExist() { + expect(this.label_child).toEqual(jasmine.anything()); + expect(this.label_child2).toEqual(jasmine.anything()); + expect(this._internal_label_child).toEqual(jasmine.anything()); + } + + templateCallback(widget) { + this.callbackEmittedBy = widget; + } + + boundCallback(widget) { + widget.callbackBoundTo = this; + } +}); + +const SubclassSubclass = GObject.registerClass( + class SubclassSubclass extends MyComplexGtkSubclass {}); + +function validateTemplate(description, ClassName, pending = false) { + let suite = pending ? xdescribe : describe; + suite(description, function () { + let win, content; + beforeEach(function () { + win = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); + content = new ClassName(); + content.label_child.emit('grab-focus'); + content.label_child2.emit('grab-focus'); + win.add(content); + }); + + it('sets up internal and public template children', function () { + content.testChildrenExist(); + }); + + it('sets up public template children with the correct widgets', function () { + expect(content.label_child.get_label()).toEqual('Complex!'); + expect(content.label_child2.get_label()).toEqual('Complex as well!'); + }); + + it('sets up internal template children with the correct widgets', function () { + expect(content._internal_label_child.get_label()) + .toEqual('Complex and internal!'); + }); + + it('connects template callbacks to the correct handler', function () { + expect(content.callbackEmittedBy).toBe(content.label_child); + }); + + it('binds template callbacks to the correct object', function () { + expect(content.label_child2.callbackBoundTo).toBe(content.label_child); + }); + + afterEach(function () { + win.destroy(); + }); + }); +} + +describe('Gtk overrides', function () { + beforeAll(function () { + Gtk.init(null); + }); + + afterAll(function () { + templateFile.delete(null); + }); + + validateTemplate('UI template', MyComplexGtkSubclass); + validateTemplate('UI template from resource', MyComplexGtkSubclassFromResource); + validateTemplate('UI template from file', MyComplexGtkSubclassFromFile); + validateTemplate('Class inheriting from template class', SubclassSubclass, true); + + it('sets CSS names on classes', function () { + expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass'); + }); + + it('avoid crashing when GTK vfuncs are called in garbage collection', function () { + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + '*during garbage collection*'); + GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL, + '*destroy*'); + + let BadLabel = GObject.registerClass(class BadLabel extends Gtk.Label { + vfunc_destroy() {} + }); + + let w = new Gtk.Window(); + w.add(new BadLabel()); + + w.destroy(); + System.gc(); + + GLib.test_assert_expected_messages_internal('Gjs', 'testGtk3.js', 0, + 'Gtk overrides avoid crashing and print a stack trace'); + }); + + it('accepts string in place of GdkAtom', function () { + expect(() => Gtk.Clipboard.get(1)).toThrow(); + expect(() => Gtk.Clipboard.get(true)).toThrow(); + expect(() => Gtk.Clipboard.get(() => undefined)).toThrow(); + + const clipboard = Gtk.Clipboard.get('CLIPBOARD'); + const primary = Gtk.Clipboard.get('PRIMARY'); + const anotherClipboard = Gtk.Clipboard.get('CLIPBOARD'); + + expect(clipboard).toBeTruthy(); + expect(primary).toBeTruthy(); + expect(clipboard).not.toBe(primary); + expect(clipboard).toBe(anotherClipboard); + }); + + it('accepts null in place of GdkAtom as GDK_NONE', function () { + /** + * When you pass GDK_NONE (an atom, interned from the 'NONE' string) + * to Gtk.Clipboard.get(), it throws an error, mentioning null in + * its message. + */ + expect(() => Gtk.Clipboard.get('NONE')).toThrowError(/null/); + + /** + * Null is converted to GDK_NONE, so you get the same message. If you + * know an API function that accepts GDK_NONE without throwing, and + * returns something different when passed another atom, consider + * adding a less confusing example here. + */ + expect(() => Gtk.Clipboard.get(null)).toThrowError(/null/); + }); + + it('uses the correct GType for null child properties', function () { + let s = new Gtk.Stack(); + let p = new Gtk.Box(); + + s.add_named(p, 'foo'); + expect(s.get_child_by_name('foo')).toBe(p); + + s.child_set_property(p, 'name', null); + expect(s.get_child_by_name('foo')).toBeNull(); + }); + + it('can create a Gtk.TreeIter with accessible stamp field', function () { + const iter = new Gtk.TreeIter(); + iter.stamp = 42; + expect(iter.stamp).toEqual(42); + }); +}); |