[gjs: 6/7] tests: Fully implement gobject-introspection test suites



commit 0239415ae0e1c6eb254e0836c3ac5a0efd61f461
Author: Philip Chimento <philip chimento gmail com>
Date:   Wed Aug 7 23:12:02 2019 -0700

    tests: Fully implement gobject-introspection test suites
    
    This implements all the possible tests from the gobject-introspection
    test suites GIMarshallingTests, Regress, and WarnLib. For anything that
    is not supported or buggy in GJS, we still write a test but skip it,
    adding the bug link as the skip message.
    
    The point of this is that we can get as much test coverage of the GJS
    codebase as possible by using the tests that already exist for
    gobject-introspection. Doing this has already yielded a number of bug
    reports which would be suitable for contributors new to GJS to work on.
    
    After merging this we'll also be able to use our code coverage report to
    get a clearer picture of what parts of the GJS codebase aren't exercised
    at all, so where we should add more testing.

 Makefile-test.am                                   |    4 +-
 installed-tests/js/jsunit.gresources.xml           |    1 +
 .../js/modules/overrides/GIMarshallingTests.js     |   24 +
 installed-tests/js/testCairo.js                    |   56 +-
 installed-tests/js/testEverythingBasic.js          |  725 ----------
 installed-tests/js/testEverythingEncapsulated.js   |  303 ----
 installed-tests/js/testFundamental.js              |    8 -
 installed-tests/js/testGIMarshalling.js            | 1345 +++++++++++++----
 installed-tests/js/testRegress.js                  | 1519 ++++++++++++++++++++
 installed-tests/js/testWarnLib.js                  |   38 +
 10 files changed, 2714 insertions(+), 1309 deletions(-)
---
diff --git a/Makefile-test.am b/Makefile-test.am
index ddef920d..a87e1b51 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -200,8 +200,6 @@ common_jstests_files =                                              \
        installed-tests/js/testself.js                          \
        installed-tests/js/testByteArray.js                     \
        installed-tests/js/testExceptions.js                    \
-       installed-tests/js/testEverythingBasic.js               \
-       installed-tests/js/testEverythingEncapsulated.js        \
        installed-tests/js/testFormat.js                        \
        installed-tests/js/testFundamental.js                   \
        installed-tests/js/testGettext.js                       \
@@ -223,9 +221,11 @@ common_jstests_files =                                             \
        installed-tests/js/testNamespace.js                     \
        installed-tests/js/testPackage.js                       \
        installed-tests/js/testParamSpec.js                     \
+       installed-tests/js/testRegress.js                       \
        installed-tests/js/testSignals.js                       \
        installed-tests/js/testSystem.js                        \
        installed-tests/js/testTweener.js                       \
+       installed-tests/js/testWarnLib.js                       \
        $(NULL)
 
 jasmine_tests = $(common_jstests_files)
diff --git a/installed-tests/js/jsunit.gresources.xml b/installed-tests/js/jsunit.gresources.xml
index 936bbbf9..21002005 100644
--- a/installed-tests/js/jsunit.gresources.xml
+++ b/installed-tests/js/jsunit.gresources.xml
@@ -14,6 +14,7 @@
     <file>modules/modunicode.js</file>
     <file>modules/mutualImport/a.js</file>
     <file>modules/mutualImport/b.js</file>
+    <file>modules/overrides/GIMarshallingTests.js</file>
     <file>modules/subA/subB/__init__.js</file>
     <file>modules/subA/subB/baz.js</file>
     <file>modules/subA/subB/foobar.js</file>
diff --git a/installed-tests/js/modules/overrides/GIMarshallingTests.js 
b/installed-tests/js/modules/overrides/GIMarshallingTests.js
new file mode 100644
index 00000000..7fee4285
--- /dev/null
+++ b/installed-tests/js/modules/overrides/GIMarshallingTests.js
@@ -0,0 +1,24 @@
+function _init() {
+    const GIMarshallingTests = this;
+
+    GIMarshallingTests.OVERRIDES_CONSTANT = 7;
+
+    GIMarshallingTests.OverridesStruct.prototype._real_method =
+        GIMarshallingTests.OverridesStruct.prototype.method;
+    GIMarshallingTests.OverridesStruct.prototype.method = function () {
+        return this._real_method() / 7;
+    };
+
+    GIMarshallingTests.OverridesObject.prototype._realInit =
+        GIMarshallingTests.OverridesObject.prototype._init;
+    GIMarshallingTests.OverridesObject.prototype._init = function (num, ...args) {
+        this._realInit(...args);
+        this.num = num;
+    };
+
+    GIMarshallingTests.OverridesObject.prototype._realMethod =
+        GIMarshallingTests.OverridesObject.prototype.method;
+    GIMarshallingTests.OverridesObject.prototype.method = function () {
+        return this._realMethod() / 7;
+    };
+}
diff --git a/installed-tests/js/testCairo.js b/installed-tests/js/testCairo.js
index 656326fc..8c10766d 100644
--- a/installed-tests/js/testCairo.js
+++ b/installed-tests/js/testCairo.js
@@ -17,7 +17,7 @@ describe('Cairo', function () {
 
     let cr, surface;
     beforeEach(function () {
-        surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 1, 1);
+        surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 10, 10);
         cr = new Cairo.Context(surface);
     });
 
@@ -167,14 +167,6 @@ describe('Cairo', function () {
             }).not.toThrow();
         });
 
-        it('can be marshalled through a signal handler', function () {
-            let o = new Regress.TestObj();
-            let foreignSpy = jasmine.createSpy('sig-with-foreign-struct');
-            o.connect('sig-with-foreign-struct', foreignSpy);
-            o.emit_sig_with_foreign_struct();
-            expect(foreignSpy).toHaveBeenCalledWith(o, cr);
-        });
-
         it('has methods when created from a C function', function () {
             let win = new Gtk.OffscreenWindow();
             let da = new Gtk.DrawingArea();
@@ -229,6 +221,52 @@ describe('Cairo', function () {
             expect(_ts(cr.getSource())).toEqual('RadialGradient');
         });
     });
+
+    describe('GI test suite', function () {
+        describe('for context', function () {
+            it('can be marshalled as a return value', function () {
+                const outCr = Regress.test_cairo_context_full_return();
+                const outSurface = outCr.getTarget();
+                expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32);
+                expect(outSurface.getWidth()).toEqual(10);
+                expect(outSurface.getHeight()).toEqual(10);
+            });
+
+            it('can be marshalled as an in parameter', function () {
+                expect(() => Regress.test_cairo_context_none_in(cr)).not.toThrow();
+            });
+        });
+
+        describe('for surface', function () {
+            ['none', 'full'].forEach(transfer => {
+                it(`can be marshalled as a transfer-${transfer} return value`, function () {
+                    const outSurface = Regress[`test_cairo_surface_${transfer}_return`]();
+                    expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32);
+                    expect(outSurface.getWidth()).toEqual(10);
+                    expect(outSurface.getHeight()).toEqual(10);
+                });
+            });
+
+            it('can be marshalled as an in parameter', function () {
+                expect(() => Regress.test_cairo_surface_none_in(surface)).not.toThrow();
+            });
+
+            it('can be marshalled as an out parameter', function () {
+                const outSurface = Regress.test_cairo_surface_full_out();
+                expect(outSurface.getFormat()).toEqual(Cairo.Format.ARGB32);
+                expect(outSurface.getWidth()).toEqual(10);
+                expect(outSurface.getHeight()).toEqual(10);
+            });
+        });
+
+        it('can be marshalled through a signal handler', function () {
+            let o = new Regress.TestObj();
+            let foreignSpy = jasmine.createSpy('sig-with-foreign-struct');
+            o.connect('sig-with-foreign-struct', foreignSpy);
+            o.emit_sig_with_foreign_struct();
+            expect(foreignSpy).toHaveBeenCalledWith(o, cr);
+        });
+    });
 });
 
 describe('Cairo imported via GI', function () {
diff --git a/installed-tests/js/testFundamental.js b/installed-tests/js/testFundamental.js
index dcd2d4a8..6384793a 100644
--- a/installed-tests/js/testFundamental.js
+++ b/installed-tests/js/testFundamental.js
@@ -1,14 +1,6 @@
 const {GObject, Regress} = imports.gi;
 
 describe('Fundamental type support', function () {
-    it('constructs a subtype of a fundamental type', function () {
-        expect(() => new Regress.TestFundamentalSubObject('plop')).not.toThrow();
-    });
-
-    it('constructs a subtype of a hidden (no introspection data) fundamental type', function () {
-        expect(() => Regress.test_create_fundamental_hidden_class_instance()).not.toThrow();
-    });
-
     it('can marshal a subtype of a custom fundamental type into a GValue', function () {
         const fund = new Regress.TestFundamentalSubObject('plop');
         expect(() => GObject.strdup_value_contents(fund)).not.toThrow();
diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js
index 915261a3..dd072beb 100644
--- a/installed-tests/js/testGIMarshalling.js
+++ b/installed-tests/js/testGIMarshalling.js
@@ -1,3 +1,6 @@
+// Load overrides for GIMarshallingTests
+imports.overrides.searchPath.unshift('resource:///org/gjs/jsunit/modules/overrides');
+
 const ByteArray = imports.byteArray;
 const GIMarshallingTests = imports.gi.GIMarshallingTests;
 
@@ -6,17 +9,349 @@ const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const GObject = imports.gi.GObject;
 
-describe('C array', function () {
-    function createStructArray() {
+// Some helpers to cut down on repetitive marshalling tests.
+// - options.omit: the test doesn't exist, don't create a test case
+// - options.skip: the test does exist, but doesn't pass, either unsupported or
+//   a bug in GJS. Create the test case and mark it pending
+
+function testReturnValue(root, value, {omit, skip, funcName = `${root}_return`} = {}) {
+    if (omit)
+        return;
+    it('marshals as a return value', function () {
+        if (skip)
+            pending(skip);
+        expect(GIMarshallingTests[funcName]()).toEqual(value);
+    });
+}
+
+function testInParameter(root, value, {omit, skip, funcName = `${root}_in`} = {}) {
+    if (omit)
+        return;
+    it('marshals as an in parameter', function () {
+        if (skip)
+            pending(skip);
+        expect(() => GIMarshallingTests[funcName](value)).not.toThrow();
+    });
+}
+
+function testOutParameter(root, value, {omit, skip, funcName = `${root}_out`} = {}) {
+    if (omit)
+        return;
+    it('marshals as an out parameter', function () {
+        if (skip)
+            pending(skip);
+        expect(GIMarshallingTests[funcName]()).toEqual(value);
+    });
+}
+
+function testInoutParameter(root, inValue, outValue,
+    {omit, skip, funcName = `${root}_inout`} = {}) {
+    if (omit)
+        return;
+    it('marshals as an inout parameter', function () {
+        if (skip)
+            pending(skip);
+        expect(GIMarshallingTests[funcName](inValue)).toEqual(outValue);
+    });
+}
+
+function testSimpleMarshalling(root, value, inoutValue, options = {}) {
+    testReturnValue(root, value, options.returnv);
+    testInParameter(root, value, options.in);
+    testOutParameter(root, value, options.out);
+    testInoutParameter(root, value, inoutValue, options.inout);
+}
+
+function testTransferMarshalling(root, value, inoutValue, options = {}) {
+    describe('with transfer none', function () {
+        testSimpleMarshalling(`${root}_none`, value, inoutValue, options.none);
+    });
+    describe('with transfer full', function () {
+        const fullOptions = {
+            in: {
+                omit: true,  // this case is not in the test suite
+            },
+            inout: {
+                skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192',
+            },
+        };
+        Object.assign(fullOptions, options.full);
+        testSimpleMarshalling(`${root}_full`, value, inoutValue, fullOptions);
+    });
+}
+
+function testContainerMarshalling(root, value, inoutValue, options = {}) {
+    testTransferMarshalling(root, value, inoutValue, options);
+    describe('with transfer container', function () {
+        const containerOptions = {
+            in: {
+                omit: true,  // this case is not in the test suite
+            },
+            inout: {
+                skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44',
+            },
+        };
+        Object.assign(containerOptions, options.container);
+        testSimpleMarshalling(`${root}_container`, value, inoutValue, containerOptions);
+    });
+}
+
+// Integer limits, defined without reference to GLib (because the GLib.MAXINT8
+// etc. constants are also subject to marshalling)
+const Limits = {
+    int8: {
+        min: -(2 ** 7),
+        max: 2 ** 7 - 1,
+        umax: 2 ** 8 - 1,
+    },
+    int16: {
+        min: -(2 ** 15),
+        max: 2 ** 15 - 1,
+        umax: 2 ** 16 - 1,
+    },
+    int32: {
+        min: -(2 ** 31),
+        max: 2 ** 31 - 1,
+        umax: 2 ** 32 - 1,
+    },
+    int64: {
+        min: -(2 ** 63),
+        max: 2 ** 63 - 1,
+        umax: 2 ** 64 - 1,
+        bit64: true,  // note: unsafe, values will not be accurate!
+    },
+    short: {},
+    int: {},
+    long: {},
+    ssize: {
+        utype: 'size',
+    },
+};
+Object.assign(Limits.short, Limits.int16);
+Object.assign(Limits.int, Limits.int32);
+// Platform dependent sizes; expand definitions as needed
+if (GLib.SIZEOF_LONG === 8)
+    Object.assign(Limits.long, Limits.int64);
+else
+    Object.assign(Limits.long, Limits.int32);
+if (GLib.SIZEOF_SSIZE_T === 8)
+    Object.assign(Limits.ssize, Limits.int64);
+else
+    Object.assign(Limits.ssize, Limits.int32);
+
+// Functions for dealing with tests that require or return unsafe 64-bit ints,
+// until we get BigInts.
+
+// Sometimes tests pass if we are comparing two inaccurate values in JS with
+// each other. That's fine for now. Then we just have to suppress the warnings.
+function warn64(is64bit, func, ...args) {
+    if (is64bit) {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            '*cannot be safely stored*');
+    }
+    const retval = func(...args);
+    if (is64bit) {
+        GLib.test_assert_expected_messages_internal('Gjs',
+            'testGIMarshalling.js', 0, 'Ignore message');
+    }
+    return retval;
+}
+
+// Other times we compare an inaccurate value marshalled from JS into C, with an
+// accurate value in C. Those tests we have to skip.
+function skip64(is64bit) {
+    if (is64bit)
+        pending('https://gitlab.gnome.org/GNOME/gjs/issues/271');
+}
+
+describe('Boolean', function () {
+    [true, false].forEach(bool => {
+        describe(`${bool}`, function () {
+            testSimpleMarshalling('boolean', bool, !bool, {
+                returnv: {
+                    funcName: `boolean_return_${bool}`,
+                },
+                in: {
+                    funcName: `boolean_in_${bool}`,
+                },
+                out: {
+                    funcName: `boolean_out_${bool}`,
+                },
+                inout: {
+                    funcName: `boolean_inout_${bool}_${!bool}`,
+                },
+            });
+        });
+    });
+});
+
+describe('Integer', function () {
+    Object.entries(Limits).forEach(([type, {min, max, umax, bit64, utype = `u${type}`}]) => {
+        describe(`${type}-typed`, function () {
+            it('marshals signed value as a return value', function () {
+                expect(warn64(bit64, GIMarshallingTests[`${type}_return_max`])).toEqual(max);
+                expect(warn64(bit64, GIMarshallingTests[`${type}_return_min`])).toEqual(min);
+            });
+
+            it('marshals signed value as an in parameter', function () {
+                skip64(bit64);
+                expect(() => GIMarshallingTests[`${type}_in_max`](max)).not.toThrow();
+                expect(() => GIMarshallingTests[`${type}_in_min`](min)).not.toThrow();
+            });
+
+            it('marshals signed value as an out parameter', function () {
+                expect(warn64(bit64, GIMarshallingTests[`${type}_out_max`])).toEqual(max);
+                expect(warn64(bit64, GIMarshallingTests[`${type}_out_min`])).toEqual(min);
+            });
+
+            it('marshals as an inout parameter', function () {
+                skip64(bit64);
+                expect(GIMarshallingTests[`${type}_inout_max_min`](max)).toEqual(min);
+                expect(GIMarshallingTests[`${type}_inout_min_max`](min)).toEqual(max);
+            });
+
+            it('marshals unsigned value as a return value', function () {
+                expect(warn64(bit64, GIMarshallingTests[`${utype}_return`])).toEqual(umax);
+            });
+
+            it('marshals unsigned value as an in parameter', function () {
+                skip64(bit64);
+                expect(() => GIMarshallingTests[`${utype}_in`](umax)).not.toThrow();
+            });
+
+            it('marshals unsigned value as an out parameter', function () {
+                expect(warn64(bit64, GIMarshallingTests[`${utype}_out`])).toEqual(umax);
+            });
+
+            it('marshals unsigned value as an inout parameter', function () {
+                skip64(bit64);
+                expect(GIMarshallingTests[`${utype}_inout`](umax)).toEqual(0);
+            });
+        });
+    });
+});
+
+describe('Floating point', function () {
+    const FloatLimits = {
+        float: {
+            min: 2 ** -126,
+            max: (2 - 2 ** -23) * 2 ** 127,
+        },
+        double: {
+            // GLib.MINDOUBLE is the minimum normal value, which is not the same
+            // as the minimum denormal value Number.MIN_VALUE
+            min: 2 ** -1022,
+            max: Number.MAX_VALUE,
+        },
+    };
+
+    Object.entries(FloatLimits).forEach(([type, {min, max}]) => {
+        describe(`${type}-typed`, function () {
+            it('marshals value as a return value', function () {
+                expect(GIMarshallingTests[`${type}_return`]()).toBeCloseTo(max, 10);
+            });
+
+            testInParameter(type, max);
+
+            it('marshals value as an out parameter', function () {
+                expect(GIMarshallingTests[`${type}_out`]()).toBeCloseTo(max, 10);
+            });
+
+            it('marshals value as an inout parameter', function () {
+                expect(GIMarshallingTests[`${type}_inout`](max)).toBeCloseTo(min, 10);
+            });
+        });
+    });
+});
+
+describe('time_t', function () {
+    testSimpleMarshalling('time_t', 1234567890, 0);
+});
+
+describe('GType', function () {
+    describe('void', function () {
+        testSimpleMarshalling('gtype', GObject.TYPE_NONE, GObject.TYPE_INT);
+    });
+
+    describe('string', function () {
+        testSimpleMarshalling('gtype_string', GObject.TYPE_STRING, null, {
+            inout: {omit: true},
+        });
+    });
+
+    it('can be implicitly converted from a GObject type alias', function () {
+        expect(() => GIMarshallingTests.gtype_in(GObject.VoidType)).not.toThrow();
+    });
+
+    it('can be implicitly converted from a JS type', function () {
+        expect(() => GIMarshallingTests.gtype_string_in(String)).not.toThrow();
+    });
+});
+
+describe('UTF-8 string', function () {
+    testTransferMarshalling('utf8', 'const ♥ utf8', '');
+
+    it('marshals value as a byte array', function () {
+        expect(() => GIMarshallingTests.utf8_as_uint8array_in('const ♥ utf8')).not.toThrow();
+    });
+
+    it('makes a default out value for a broken C function', function () {
+        expect(GIMarshallingTests.utf8_dangling_out()).toBeNull();
+    });
+});
+
+describe('In-out array in the style of gtk_init()', function () {
+    it('marshals null', function () {
+        const [, newArray] = GIMarshallingTests.init_function(null);
+        expect(newArray).toEqual([]);
+    });
+
+    xit('marshals an inout empty array', function () {
+        const [, newArray] = GIMarshallingTests.init_function([]);
+        expect(newArray).toEqual([]);
+    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/88');
+
+    xit('marshals an inout array', function () {
+        const [, newArray] = GIMarshallingTests.init_function(['--foo', '--bar']);
+        expect(newArray).toEqual(['--foo']);
+    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/88');
+});
+
+describe('Fixed-size C array', function () {
+    describe('of ints', function () {
+        testReturnValue('array_fixed_int', [-1, 0, 1, 2]);
+        testInParameter('array_fixed_int', [-1, 0, 1, 2]);
+        testInoutParameter('array_fixed', [-1, 0, 1, 2], [2, 1, 0, -1]);
+    });
+
+    describe('of shorts', function () {
+        testReturnValue('array_fixed_short', [-1, 0, 1, 2]);
+        testInParameter('array_fixed_short', [-1, 0, 1, 2]);
+    });
+
+    it('marshals a struct array as an out parameter', function () {
+        expect(GIMarshallingTests.array_fixed_out_struct()).toEqual([
+            jasmine.objectContaining({long_: 7, int8: 6}),
+            jasmine.objectContaining({long_: 6, int8: 7}),
+        ]);
+    });
+});
+
+describe('C array with length', function () {
+    function createStructArray(StructType = GIMarshallingTests.BoxedStruct) {
         return [1, 2, 3].map(num => {
-            let struct = new GIMarshallingTests.BoxedStruct();
+            let struct = new StructType();
             struct.long_ = num;
             return struct;
         });
     }
 
-    it('can be passed to a function', function () {
-        expect(() => GIMarshallingTests.array_in([-1, 0, 1, 2])).not.toThrow();
+    testSimpleMarshalling('array', [-1, 0, 1, 2], [-2, -1, 0, 1, 2]);
+
+    it('can be returned along with other arguments', function () {
+        let [array, sum] = GIMarshallingTests.array_return_etc(9, 5);
+        expect(sum).toEqual(14);
+        expect(array).toEqual([9, 0, 1, 5]);
     });
 
     it('can be passed to a function with its length parameter before it', function () {
@@ -29,45 +364,73 @@ describe('C array', function () {
             .not.toThrow();
     });
 
-    it('can be passed to a function in the style of gtk_init()', function () {
-        let [, newArray] = GIMarshallingTests.init_function(null);
-        expect(newArray).toEqual([]);
+    describe('of strings', function () {
+        testInParameter('array_string', ['foo', 'bar']);
     });
 
-    it('can be returned with zero terminator', function () {
-        expect(GIMarshallingTests.array_zero_terminated_return())
-            .toEqual(['0', '1', '2']);
+    it('marshals a byte array as an in parameter', function () {
+        expect(() => GIMarshallingTests.array_uint8_in('abcd')).not.toThrow();
+        expect(() => GIMarshallingTests.array_uint8_in([97, 98, 99, 100])).not.toThrow();
+        expect(() => GIMarshallingTests.array_uint8_in(ByteArray.fromString('abcd')))
+            .not.toThrow();
     });
 
-    it('can be returned', function () {
-        expect(GIMarshallingTests.array_return()).toEqual([-1, 0, 1, 2]);
+    describe('of signed 64-bit ints', function () {
+        testInParameter('array_int64', [-1, 0, 1, 2]);
     });
 
-    it('can be returned along with other arguments', function () {
-        let [array, sum] = GIMarshallingTests.array_return_etc(9, 5);
-        expect(sum).toEqual(14);
-        expect(array).toEqual([9, 0, 1, 5]);
+    describe('of unsigned 64-bit ints', function () {
+        testInParameter('array_uint64', [-1, 0, 1, 2]);
     });
 
-    it('can be an out argument', function () {
-        expect(GIMarshallingTests.array_out()).toEqual([-1, 0, 1, 2]);
+    describe('of unichars', function () {
+        testInParameter('array_unichar', 'const ♥ utf8');
+        testOutParameter('array_unichar', 'const ♥ utf8');
+
+        it('marshals from an array of codepoints', function () {
+            const codepoints = [...'const ♥ utf8'].map(c => c.codePointAt(0));
+            expect(() => GIMarshallingTests.array_unichar_in(codepoints)).not.toThrow();
+        });
     });
 
-    it('can be an out argument along with other arguments', function () {
-        let [array, sum] = GIMarshallingTests.array_out_etc(9, 5);
-        expect(sum).toEqual(14);
-        expect(array).toEqual([9, 0, 1, 5]);
+    describe('of booleans', function () {
+        testInParameter('array_bool', [true, false, true, true]);
+        testOutParameter('array_bool', [true, false, true, true]);
+
+        it('marshals from an array of numbers', function () {
+            expect(() => GIMarshallingTests.array_bool_in([-1, 0, 1, 2])).not.toThrow();
+        });
     });
 
-    it('can be an in-out argument', function () {
-        expect(GIMarshallingTests.array_inout([-1, 0, 1, 2]))
-            .toEqual([-2, -1, 0, 1, 2]);
+    describe('of boxed structs', function () {
+        testInParameter('array_struct', createStructArray());
+
+        describe('passed by value', function () {
+            testInParameter('array_struct_value', createStructArray(), {
+                skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44',
+            });
+        });
     });
 
-    it('can be an in-out argument along with other arguments', function () {
-        let [array, sum] = GIMarshallingTests.array_inout_etc(9, [-1, 0, 1, 2], 5);
-        expect(sum).toEqual(14);
-        expect(array).toEqual([9, -1, 0, 1, 5]);
+    describe('of simple structs', function () {
+        testInParameter('array_simple_struct',
+            createStructArray(GIMarshallingTests.SimpleStruct), {
+                skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/44',
+            });
+    });
+
+    it('marshals two arrays with the same length parameter', function () {
+        const keys = ['one', 'two', 'three'];
+        const values = [1, 2, 3];
+
+        // Intercept message; see https://gitlab.gnome.org/GNOME/gjs/issues/267
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_MESSAGE,
+            '*Too many arguments*');
+
+        expect(() => GIMarshallingTests.multi_array_key_value_in(keys, values)).not.toThrow();
+
+        GLib.test_assert_expected_messages_internal('Gjs',
+            'testGIMarshalling.js', 0, 'Ignore message');
     });
 
     // Run twice to ensure that copies are correctly made for (transfer full)
@@ -79,143 +442,126 @@ describe('C array', function () {
         }).not.toThrow();
     });
 
-    describe('of structs', function () {
-        it('can be passed to a function', function () {
-            expect(() => GIMarshallingTests.array_struct_in(createStructArray()))
-                .not.toThrow();
-        });
-
-        it('can be returned with zero terminator', function () {
-            let structArray = GIMarshallingTests.array_zero_terminated_return_struct();
-            expect(structArray.map(e => e.long_)).toEqual([42, 43, 44]);
-        });
+    describe('of enums', function () {
+        testInParameter('array_enum', [
+            GIMarshallingTests.Enum.VALUE1,
+            GIMarshallingTests.Enum.VALUE2,
+            GIMarshallingTests.Enum.VALUE3,
+        ]);
     });
 
-    describe('of booleans', function () {
-        it('is coerced to true/false when passed to a function', function () {
-            expect(() => GIMarshallingTests.array_bool_in([-1, 0, 1, 2]))
-                .not.toThrow();
-        });
-
-        it('can be an out argument', function () {
-            expect(GIMarshallingTests.array_bool_out())
-                .toEqual([true, false, true, true]);
-        });
+    it('marshals an array with a 64-bit length parameter', function () {
+        expect(() => GIMarshallingTests.array_in_guint64_len([-1, 0, 1, 2])).not.toThrow();
     });
 
-    describe('of unichars', function () {
-        it('can be passed to a function', function () {
-            expect(() => GIMarshallingTests.array_unichar_in('const \u2665 utf8'))
-                .not.toThrow();
-        });
+    it('marshals an array with an 8-bit length parameter', function () {
+        expect(() => GIMarshallingTests.array_in_guint8_len([-1, 0, 1, 2])).not.toThrow();
+    });
 
-        it('can be an out argument', function () {
-            expect(GIMarshallingTests.array_unichar_out())
-                .toEqual('const \u2665 utf8');
-        });
+    it('can be an out argument along with other arguments', function () {
+        let [array, sum] = GIMarshallingTests.array_out_etc(9, 5);
+        expect(sum).toEqual(14);
+        expect(array).toEqual([9, 0, 1, 5]);
+    });
 
-        it('can be returned with zero terminator', function () {
-            expect(GIMarshallingTests.array_zero_terminated_return_unichar())
-                .toEqual('const \u2665 utf8');
-        });
+    it('can be an in-out argument along with other arguments', function () {
+        let [array, sum] = GIMarshallingTests.array_inout_etc(9, [-1, 0, 1, 2], 5);
+        expect(sum).toEqual(14);
+        expect(array).toEqual([9, -1, 0, 1, 5]);
+    });
 
-        it('can be implicitly converted from a number array', function () {
-            expect(() => GIMarshallingTests.array_unichar_in([0x63, 0x6f, 0x6e, 0x73,
-                0x74, 0x20, 0x2665, 0x20, 0x75, 0x74, 0x66, 0x38])).not.toThrow();
-        });
+    it('does not interpret an unannotated integer as a length parameter', function () {
+        expect(() => GIMarshallingTests.array_in_nonzero_nonlen(42, 'abcd')).not.toThrow();
     });
+});
 
+describe('Zero-terminated C array', function () {
     describe('of strings', function () {
-        it('can be passed to a function', function () {
-            expect(() => GIMarshallingTests.array_string_in(['foo', 'bar']))
-                .not.toThrow();
-        });
+        testSimpleMarshalling('array_zero_terminated', ['0', '1', '2'],
+            ['-1', '0', '1', '2']);
     });
 
-    describe('of enums', function () {
-        it('can be passed to a function', function () {
-            expect(() => GIMarshallingTests.array_enum_in([GIMarshallingTests.Enum.VALUE1,
-                GIMarshallingTests.Enum.VALUE2, GIMarshallingTests.Enum.VALUE3])).not.toThrow();
-        });
+    it('marshals null as a zero-terminated array return value', function () {
+        expect(GIMarshallingTests.array_zero_terminated_return_null()).toEqual(null);
     });
 
-    describe('of bytes', function () {
-        it('can be an in argument with length', function () {
-            expect(() => GIMarshallingTests.array_in_guint8_len([-1, 0, 1, 2]))
-                .not.toThrow();
-        });
+    it('marshals an array of structs as a return value', function () {
+        let structArray = GIMarshallingTests.array_zero_terminated_return_struct();
+        expect(structArray.map(e => e.long_)).toEqual([42, 43, 44]);
+    });
 
-        it('can be implicitly converted from a string', function () {
-            expect(() => GIMarshallingTests.array_uint8_in('abcd')).not.toThrow();
-        });
+    it('marshals an array of unichars as a return value', function () {
+        expect(GIMarshallingTests.array_zero_terminated_return_unichar())
+            .toEqual('const ♥ utf8');
     });
 
-    describe('of 64-bit ints', function () {
-        it('can be passed to a function', function () {
-            expect(() => GIMarshallingTests.array_int64_in([-1, 0, 1, 2]))
-                .not.toThrow();
-            expect(() => GIMarshallingTests.array_uint64_in([-1, 0, 1, 2]))
-                .not.toThrow();
+    describe('of GLib.Variants', function () {
+        let variantArray;
+
+        beforeEach(function () {
+            variantArray = [
+                new GLib.Variant('i', 27),
+                new GLib.Variant('s', 'Hello'),
+            ];
         });
 
-        it('can be an in argument with length', function () {
-            expect(() => GIMarshallingTests.array_in_guint64_len([-1, 0, 1, 2]))
-                .not.toThrow();
+        ['none', 'container', 'full'].forEach(transfer => {
+            xit(`marshals as a transfer-${transfer} in and out parameter`, function () {
+                const returnedArray =
+                    GIMarshallingTests[`array_gvariant_${transfer}_in`](variantArray);
+                expect(returnedArray.map(v => v.deepUnpack())).toEqual([27, 'Hello']);
+            }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/269');
         });
     });
 });
 
 describe('GArray', function () {
-    describe('of integers', function () {
-        it('can be passed in with transfer none', function () {
-            expect(() => GIMarshallingTests.garray_int_none_in([-1, 0, 1, 2]))
-                .not.toThrow();
-        });
+    describe('of ints with transfer none', function () {
+        testReturnValue('garray_int_none', [-1, 0, 1, 2]);
+        testInParameter('garray_int_none', [-1, 0, 1, 2]);
+    });
 
-        it('can be returned with transfer none', function () {
-            expect(GIMarshallingTests.garray_int_none_return())
-                .toEqual([-1, 0, 1, 2]);
-        });
+    it('marshals int64s as a transfer-none return value', function () {
+        expect(warn64(true, GIMarshallingTests.garray_uint64_none_return))
+            .toEqual([0, Limits.int64.umax]);
     });
 
     describe('of strings', function () {
-        it('can be passed in with transfer none', function () {
-            expect(() => GIMarshallingTests.garray_utf8_none_in(['0', '1', '2']))
-                .not.toThrow();
-        });
+        testContainerMarshalling('garray_utf8', ['0', '1', '2'], ['-2', '-1', '0', '1']);
 
-        ['return', 'out'].forEach(method => {
-            ['none', 'container', 'full'].forEach(transfer => {
-                it(`can be passed as ${method} with transfer ${transfer}`, function () {
-                    expect(GIMarshallingTests[`garray_utf8_${transfer}_${method}`]())
-                        .toEqual(['0', '1', '2']);
-                });
-            });
-        });
+        it('marshals as a transfer-full caller-allocated out parameter', function () {
+            expect(GIMarshallingTests.garray_utf8_full_out_caller_allocated())
+                .toEqual(['0', '1', '2']);
+        }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/106');
     });
 
-    describe('of booleans', function () {
-        it('can be passed in with transfer none', function () {
-            expect(() => GIMarshallingTests.garray_bool_none_in([-1, 0, 1, 2]))
-                .not.toThrow();
-        });
+    xit('marshals boxed structs as a transfer-full return value', function () {
+        expect(GIMarshallingTests.garray_boxed_struct_full_return().map(e => e.long_))
+            .toEqual([42, 43, 44]);
+    }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/160');
+
+    describe('of booleans with transfer none', function () {
+        testInParameter('garray_bool_none', [-1, 0, 1, 2]);
     });
 
     describe('of unichars', function () {
         it('can be passed in with transfer none', function () {
             expect(() => GIMarshallingTests.garray_unichar_none_in('const \u2665 utf8'))
                 .not.toThrow();
-        });
-
-        it('can be implicitly converted from a number array', function () {
             expect(() => GIMarshallingTests.garray_unichar_none_in([0x63, 0x6f, 0x6e,
                 0x73, 0x74, 0x20, 0x2665, 0x20, 0x75, 0x74, 0x66, 0x38])).not.toThrow();
         });
     });
+});
+
+describe('GPtrArray', function () {
+    describe('of strings', function () {
+        testContainerMarshalling('garray_utf8', ['0', '1', '2'], ['-2', '-1', '0', '1']);
+    });
 
     describe('of structs', function () {
         xit('can be returned with transfer full', function () {
-            expect(GIMarshallingTests.garray_boxed_struct_full_return().map(e => e.long_))
+            expect(GIMarshallingTests.gptrarray_boxed_struct_full_return().map(e => e.long_))
                 .toEqual([42, 43, 44]);
         }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/160');
     });
@@ -224,16 +570,11 @@ describe('GArray', function () {
 describe('GByteArray', function () {
     const refByteArray = Uint8Array.from([0, 49, 0xFF, 51]);
 
+    testReturnValue('bytearray_full', refByteArray);
+
     it('can be passed in with transfer none', function () {
         expect(() => GIMarshallingTests.bytearray_none_in(refByteArray))
             .not.toThrow();
-    });
-
-    it('can be returned with transfer full', function () {
-        expect(GIMarshallingTests.bytearray_full_return()).toEqual(refByteArray);
-    });
-
-    it('can be implicitly converted from a normal array', function () {
         expect(() => GIMarshallingTests.bytearray_none_in([0, 49, 0xFF, 51]))
             .not.toThrow();
     });
@@ -242,6 +583,10 @@ describe('GByteArray', function () {
 describe('GBytes', function () {
     const refByteArray = Uint8Array.from([0, 49, 0xFF, 51]);
 
+    it('marshals as a transfer-full return value', function () {
+        expect(GIMarshallingTests.gbytes_full_return().toArray()).toEqual(refByteArray);
+    });
+
     it('can be created from an array and passed in', function () {
         let bytes = GLib.Bytes.new([0, 49, 0xFF, 51]);
         expect(() => GIMarshallingTests.gbytes_none_in(bytes)).not.toThrow();
@@ -283,150 +628,139 @@ describe('GBytes', function () {
     });
 });
 
-describe('GPtrArray', function () {
-    describe('of strings', function () {
-        const refArray = ['0', '1', '2'];
+describe('GStrv', function () {
+    testSimpleMarshalling('gstrv', ['0', '1', '2'], ['-1', '0', '1', '2']);
+});
 
-        it('can be passed to a function with transfer none', function () {
-            expect(() => GIMarshallingTests.gptrarray_utf8_none_in(refArray))
-                .not.toThrow();
+['GList', 'GSList'].forEach(listKind => {
+    const list = listKind.toLowerCase();
+
+    describe(listKind, function () {
+        describe('of ints with transfer none', function () {
+            testReturnValue(`${list}_int_none`, [-1, 0, 1, 2]);
+            testInParameter(`${list}_int_none`, [-1, 0, 1, 2]);
         });
 
-        ['return', 'out'].forEach(method => {
-            ['none', 'container', 'full'].forEach(transfer => {
-                it(`can be passed as ${method} with transfer ${transfer}`, function () {
-                    expect(GIMarshallingTests[`gptrarray_utf8_${transfer}_${method}`]())
-                        .toEqual(refArray);
-                });
+        if (listKind === 'GList') {
+            describe('of unsigned 32-bit ints with transfer none', function () {
+                testReturnValue('glist_uint32_none', [0, Limits.int32.umax]);
+                testInParameter('glist_uint32_none', [0, Limits.int32.umax]);
             });
-        });
-    });
+        }
 
-    describe('of structs', function () {
-        xit('can be returned with transfer full', function () {
-            expect(GIMarshallingTests.gptrarray_boxed_struct_full_return().map(e => e.long_))
-                .toEqual([42, 43, 44]);
-        }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/160');
+        describe('of strings', function () {
+            testContainerMarshalling(`${list}_utf8`, ['0', '1', '2'],
+                ['-2', '-1', '0', '1']);
+        });
     });
 });
 
 describe('GHashTable', function () {
-    const INT_DICT = {
-        '-1': 1,
-        0: 0,
-        1: -1,
-        2: -2,
-    };
-    const STRING_DICT = {
-        '-1': '1',
-        0: '0',
-        1: '-1',
-        2: '-2',
-    };
-    const NUMBER_DICT = {
+    const numberDict = {
         '-1': -0.1,
         0: 0,
         1: 0.1,
         2: 0.2,
     };
-    const STRING_DICT_OUT = {
-        '-1': '1',
-        0: '0',
-        1: '1',
-    };
 
-    it('can be passed in with integer value type', function () {
-        expect(() => GIMarshallingTests.ghashtable_int_none_in(INT_DICT))
-            .not.toThrow();
+    describe('with integer values', function () {
+        const intDict = {
+            '-1': 1,
+            0: 0,
+            1: -1,
+            2: -2,
+        };
+        testReturnValue('ghashtable_int_none', intDict);
+        testInParameter('ghashtable_int_none', intDict);
     });
 
-    it('can be passed in with string value type', function () {
-        expect(() => GIMarshallingTests.ghashtable_utf8_none_in(STRING_DICT))
-            .not.toThrow();
+    describe('with string values', function () {
+        const stringDict = {
+            '-1': '1',
+            0: '0',
+            1: '-1',
+            2: '-2',
+        };
+        const stringDictOut = {
+            '-1': '1',
+            0: '0',
+            1: '1',
+        };
+        testContainerMarshalling('ghashtable_utf8', stringDict, stringDictOut);
     });
 
-    it('can be passed in with float value type', function () {
-        expect(() => GIMarshallingTests.ghashtable_float_in(NUMBER_DICT))
-            .not.toThrow();
+    describe('with double values', function () {
+        testInParameter('ghashtable_double', numberDict);
     });
 
-    it('can be passed in with double value type', function () {
-        expect(() => GIMarshallingTests.ghashtable_double_in(NUMBER_DICT))
-            .not.toThrow();
+    describe('with float values', function () {
+        testInParameter('ghashtable_float', numberDict);
     });
 
-    it('can be passed in with int64 value type', function () {
+    describe('with 64-bit int values', function () {
         const int64Dict = {
             '-1': -1,
             0: 0,
             1: 1,
             2: 0x100000000,
         };
-        expect(() => GIMarshallingTests.ghashtable_int64_in(int64Dict))
-            .not.toThrow();
+        testInParameter('ghashtable_int64', int64Dict);
     });
 
-    it('can be passed in with uint64 value type', function () {
+    describe('with unsigned 64-bit int values', function () {
         const uint64Dict = {
             '-1': 0x100000000,
             0: 0,
             1: 1,
             2: 2,
         };
-        expect(() => GIMarshallingTests.ghashtable_uint64_in(uint64Dict))
-            .not.toThrow();
-    });
-
-    it('can be returned with integer value type', function () {
-        expect(GIMarshallingTests.ghashtable_int_none_return()).toEqual(INT_DICT);
-    });
-
-    ['return', 'out'].forEach(method => {
-        ['none', 'container', 'full'].forEach(transfer => {
-            it(`can be passed as ${method} with transfer ${transfer}`, function () {
-                expect(GIMarshallingTests[`ghashtable_utf8_${transfer}_${method}`]())
-                    .toEqual(STRING_DICT);
-            });
-        });
-    });
-
-    it('can be passed as inout with transfer none', function () {
-        expect(GIMarshallingTests.ghashtable_utf8_none_inout(STRING_DICT))
-            .toEqual(STRING_DICT_OUT);
+        testInParameter('ghashtable_uint64', uint64Dict);
     });
-
-    xit('can be passed as inout with transfer container', function () {
-        expect(GIMarshallingTests.ghashtable_utf8_container_inout(STRING_DICT))
-            .toEqual(STRING_DICT_OUT);
-    }).pend('Container transfer for in parameters not supported');
-
-    xit('can be passed as inout with transfer full', function () {
-        expect(GIMarshallingTests.ghashtable_utf8_full_inout(STRING_DICT))
-            .toEqual(STRING_DICT_OUT);
-    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/100');
 });
 
 describe('GValue', function () {
-    it('can be passed into a function and packed', function () {
-        expect(() => GIMarshallingTests.gvalue_in(42)).not.toThrow();
+    testSimpleMarshalling('gvalue', 42, '42', {
+        inout: {
+            skip: 'https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192',
+        },
     });
 
-    it('array can be passed into a function and packed', function () {
-        expect(() => GIMarshallingTests.gvalue_flat_array([42, '42', true]))
+    xit('marshals as an int64 in parameter', function () {
+        expect(() => GIMarshallingTests.gvalue_int64_in(Limits.int64.max)).not.toThrow();
+    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271');
+
+    it('type objects can be converted from primitive-like types', function () {
+        expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.Int))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, GObject.Double))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, Number))
             .not.toThrow();
     });
 
+    it('can be passed into a function and modified', function () {
+        expect(() => GIMarshallingTests.gvalue_in_with_modification(42)).not.toThrow();
+        // Let's assume this test doesn't expect that the modified GValue makes
+        // it back to the caller; I don't see how that could be achieved.
+        // See https://gitlab.gnome.org/GNOME/gjs/issues/80
+    });
+
     xit('enum can be passed into a function and packed', function () {
         expect(() => GIMarshallingTests.gvalue_in_enum(GIMarshallingTests.Enum.VALUE3))
             .not.toThrow();
     }).pend("GJS doesn't support native enum types");
 
-    it('can be returned and unpacked', function () {
-        expect(GIMarshallingTests.gvalue_return()).toEqual(42);
+    it('marshals as an int64 out parameter', function () {
+        expect(GIMarshallingTests.gvalue_int64_out()).toEqual(Limits.int64.max);
+    });
+
+    it('marshals as a caller-allocated out parameter', function () {
+        expect(GIMarshallingTests.gvalue_out_caller_allocates()).toEqual(42);
     });
 
-    it('can be passed as an out argument and unpacked', function () {
-        expect(GIMarshallingTests.gvalue_out()).toEqual(42);
+    it('array can be passed into a function and packed', function () {
+        expect(() => GIMarshallingTests.gvalue_flat_array([42, '42', true]))
+            .not.toThrow();
     });
 
     it('array can be passed as an out argument and unpacked', function () {
@@ -434,6 +768,11 @@ describe('GValue', function () {
             .toEqual([42, '42', true]);
     });
 
+    xit('array can roundtrip with GValues intact', function () {
+        expect(GIMarshallingTests.gvalue_flat_array_round_trip(42, '42', true))
+            .toEqual([42, '42', true]);
+    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/272');
+
     it('can have its type inferred from primitive values', function () {
         expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.TYPE_INT))
             .not.toThrow();
@@ -445,14 +784,7 @@ describe('GValue', function () {
             .not.toThrow();
     });
 
-    it('type objects can be converted from primitive-like types', function () {
-        expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.Int))
-            .not.toThrow();
-        expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, GObject.Double))
-            .not.toThrow();
-        expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, Number))
-            .not.toThrow();
-    });
+    // supplementary tests for gvalue_in_with_type()
 
     it('can have its type inferred as a GObject type', function () {
         expect(() => GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction(), Gio.SimpleAction))
@@ -522,36 +854,18 @@ describe('GValue', function () {
     });
 });
 
-describe('GType', function () {
-    it('can be passed into a function', function () {
-        expect(() => GIMarshallingTests.gtype_in(GObject.TYPE_NONE)).not.toThrow();
-        expect(() => GIMarshallingTests.gtype_in(GObject.VoidType)).not.toThrow();
-        expect(() => GIMarshallingTests.gtype_string_in(GObject.TYPE_STRING)).not.toThrow();
-        expect(() => GIMarshallingTests.gtype_string_in(GObject.String)).not.toThrow();
-    });
-
-    it('can be returned', function () {
-        expect(GIMarshallingTests.gtype_return()).toEqual(GObject.TYPE_NONE);
-        expect(GIMarshallingTests.gtype_string_return())
-            .toEqual(GObject.TYPE_STRING);
-    });
-
-    it('can be passed as an out argument', function () {
-        expect(GIMarshallingTests.gtype_out()).toEqual(GObject.TYPE_NONE);
-        expect(GIMarshallingTests.gtype_string_out()).toEqual(GObject.TYPE_STRING);
-    });
+describe('Callback', function () {
+    describe('GClosure', function () {
+        testInParameter('gclosure', () => 42);
 
-    it('can be passed as an inout argument', function () {
-        expect(GIMarshallingTests.gtype_inout(GObject.TYPE_NONE))
-            .toEqual(GObject.TYPE_INT);
+        xit('marshals a GClosure as a return value', function () {
+            // Currently a GObject.Closure instance is returned, upon which it's
+            // not possible to call invoke() because that method takes a bare
+            // pointer as an argument.
+            expect(GIMarshallingTests.gclosure_return()()).toEqual(42);
+        }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/80');
     });
 
-    it('can be implicitly converted from a JS type', function () {
-        expect(() => GIMarshallingTests.gtype_string_in(String)).not.toThrow();
-    });
-});
-
-describe('Callback', function () {
     it('marshals a return value', function () {
         expect(GIMarshallingTests.callback_return_value_only(() => 42))
             .toEqual(42);
@@ -583,6 +897,201 @@ describe('Callback', function () {
     }).pend('Function not added to gobject-introspection test suite yet');
 });
 
+describe('Raw pointers', function () {
+    xit('can be roundtripped at least if the pointer is null', function () {
+        expect(GIMarshallingTests.pointer_in_return(null)).toBeNull();
+    }).pend('https://gitlab.gnome.org/GNOME/gjs/merge_requests/46');
+});
+
+describe('Registered enum type', function () {
+    testSimpleMarshalling('genum', GIMarshallingTests.GEnum.VALUE3,
+        GIMarshallingTests.GEnum.VALUE1, {
+            returnv: {
+                funcName: 'genum_returnv',
+            },
+        });
+});
+
+describe('Bare enum type', function () {
+    testSimpleMarshalling('enum', GIMarshallingTests.Enum.VALUE3,
+        GIMarshallingTests.Enum.VALUE1, {
+            returnv: {
+                funcName: 'enum_returnv',
+            },
+        });
+});
+
+describe('Registered flags type', function () {
+    testSimpleMarshalling('flags', GIMarshallingTests.Flags.VALUE2,
+        GIMarshallingTests.Flags.VALUE1, {
+            returnv: {
+                funcName: 'flags_returnv',
+            },
+        });
+});
+
+describe('Bare flags type', function () {
+    testSimpleMarshalling('no_type_flags', GIMarshallingTests.NoTypeFlags.VALUE2,
+        GIMarshallingTests.NoTypeFlags.VALUE1, {
+            returnv: {
+                funcName: 'no_type_flags_returnv',
+            },
+        });
+});
+
+describe('Simple struct', function () {
+    it('marshals as a return value', function () {
+        expect(GIMarshallingTests.simple_struct_returnv()).toEqual(jasmine.objectContaining({
+            long_: 6,
+            int8: 7,
+        }));
+    });
+
+    it('marshals as the this-argument of a method', function () {
+        const struct = new GIMarshallingTests.SimpleStruct({
+            long_: 6,
+            int8: 7,
+        });
+        expect(() => struct.inv()).not.toThrow();  // was this supposed to be static?
+        expect(() => struct.method()).not.toThrow();
+    });
+});
+
+describe('Pointer struct', function () {
+    it('marshals as a return value', function () {
+        expect(GIMarshallingTests.pointer_struct_returnv()).toEqual(jasmine.objectContaining({
+            long_: 42,
+        }));
+    });
+
+    it('marshals as the this-argument of a method', function () {
+        const struct = new GIMarshallingTests.PointerStruct({
+            long_: 42,
+        });
+        expect(() => struct.inv()).not.toThrow();
+    });
+});
+
+describe('Boxed struct', function () {
+    it('marshals as a return value', function () {
+        expect(GIMarshallingTests.boxed_struct_returnv()).toEqual(jasmine.objectContaining({
+            long_: 42,
+            string_: 'hello',
+            g_strv: ['0', '1', '2'],
+        }));
+    });
+
+    it('marshals as the this-argument of a method', function () {
+        const struct = new GIMarshallingTests.BoxedStruct({
+            long_: 42,
+        });
+        expect(() => struct.inv()).not.toThrow();
+    });
+
+    it('marshals as an out parameter', function () {
+        expect(GIMarshallingTests.boxed_struct_out()).toEqual(jasmine.objectContaining({
+            long_: 42,
+        }));
+    });
+
+    it('marshals as an inout parameter', function () {
+        const struct = new GIMarshallingTests.BoxedStruct({
+            long_: 42,
+        });
+        expect(GIMarshallingTests.boxed_struct_inout(struct)).toEqual(jasmine.objectContaining({
+            long_: 0,
+        }));
+    });
+});
+
+describe('Union', function () {
+    let union;
+    beforeEach(function () {
+        union = GIMarshallingTests.union_returnv();
+    });
+
+    xit('marshals as a return value', function () {
+        expect(union.long_).toEqual(42);
+    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/273');
+
+    it('marshals as the this-argument of a method', function () {
+        expect(() => union.inv()).not.toThrow();  // was this supposed to be static?
+        expect(() => union.method()).not.toThrow();
+    });
+});
+
+describe('GObject', function () {
+    it('has a static method that can be called', function () {
+        expect(() => GIMarshallingTests.Object.static_method()).not.toThrow();
+    });
+
+    it('has a method that can be called', function () {
+        const o = new GIMarshallingTests.Object({int: 42});
+        expect(() => o.method()).not.toThrow();
+    });
+
+    it('has an overridden method that can be called', function () {
+        const o = new GIMarshallingTests.Object({int: 0});
+        expect(() => o.overridden_method()).not.toThrow();
+    });
+
+    it('can be created from a static constructor', function () {
+        const o = GIMarshallingTests.Object.new(42);
+        expect(o.int).toEqual(42);
+    });
+
+    it('can have a static constructor that fails', function () {
+        expect(() => GIMarshallingTests.Object.new_fail(42)).toThrow();
+    });
+
+    describe('method', function () {
+        let o;
+        beforeEach(function () {
+            o = new GIMarshallingTests.Object();
+        });
+
+        it('marshals an int array as an in parameter', function () {
+            expect(() => o.method_array_in([-1, 0, 1, 2])).not.toThrow();
+        });
+
+        it('marshals an int array as an out parameter', function () {
+            expect(o.method_array_out()).toEqual([-1, 0, 1, 2]);
+        });
+
+        it('marshals an int array as an inout parameter', function () {
+            expect(o.method_array_inout([-1, 0, 1, 2])).toEqual([-2, -1, 0, 1, 2]);
+        });
+
+        it('marshals an int array as a return value', function () {
+            expect(o.method_array_return()).toEqual([-1, 0, 1, 2]);
+        });
+
+        it('with default implementation can be called', function () {
+            o = new GIMarshallingTests.Object({int: 42});
+            o.method_with_default_implementation(43);
+            expect(o.int).toEqual(43);
+        });
+    });
+
+    ['none', 'full'].forEach(transfer => {
+        ['return', 'out'].forEach(mode => {
+            it(`marshals as a ${mode} parameter with transfer ${transfer}`, function () {
+                expect(GIMarshallingTests.Object[`${transfer}_${mode}`]().int).toEqual(0);
+            });
+        });
+
+        it(`marshals as an inout parameter with transfer ${transfer}`, function () {
+            const o = new GIMarshallingTests.Object({int: 42});
+            expect(GIMarshallingTests.Object[`${transfer}_inout`](o).int).toEqual(0);
+        });
+    });
+
+    it('marshals as a this value with transfer none', function () {
+        const o = new GIMarshallingTests.Object({int: 42});
+        expect(() => o.none_in()).not.toThrow();
+    });
+});
+
 const VFuncTester = GObject.registerClass(class VFuncTester extends GIMarshallingTests.Object {
     vfunc_vfunc_return_value_only() {
         return 42;
@@ -608,6 +1117,10 @@ const VFuncTester = GObject.registerClass(class VFuncTester extends GIMarshallin
         return [50, 51];
     }
 
+    vfunc_vfunc_caller_allocated_out_parameter() {
+        return 52;
+    }
+
     vfunc_vfunc_meth_with_err(x) {
         switch (x) {
         case -1:
@@ -630,6 +1143,38 @@ const VFuncTester = GObject.registerClass(class VFuncTester extends GIMarshallin
             });
         }
     }
+
+    vfunc_vfunc_return_enum() {
+        return GIMarshallingTests.Enum.VALUE2;
+    }
+
+    vfunc_vfunc_out_enum() {
+        return GIMarshallingTests.Enum.VALUE3;
+    }
+
+    vfunc_vfunc_return_object_transfer_none() {
+        if (!this._returnObject)
+            this._returnObject = new GIMarshallingTests.Object({int: 53});
+        return this._returnObject;
+    }
+
+    vfunc_vfunc_return_object_transfer_full() {
+        return new GIMarshallingTests.Object({int: 54});
+    }
+
+    vfunc_vfunc_out_object_transfer_none() {
+        if (!this._outObject)
+            this._outObject = new GIMarshallingTests.Object({int: 55});
+        return this._outObject;
+    }
+
+    vfunc_vfunc_out_object_transfer_full() {
+        return new GIMarshallingTests.Object({int: 56});
+    }
+
+    vfunc_vfunc_in_object_transfer_none(object) {
+        void object;
+    }
 });
 
 describe('Virtual function', function () {
@@ -664,6 +1209,10 @@ describe('Virtual function', function () {
         expect(tester.vfunc_array_out_parameter()).toEqual([50, 51]);
     });
 
+    it('marshals a caller-allocated GValue out parameter', function () {
+        expect(tester.vfunc_caller_allocated_out_parameter()).toEqual(52);
+    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/74');
+
     it('marshals an error out parameter when no error', function () {
         expect(tester.vfunc_meth_with_error(-1)).toBeTruthy();
     });
@@ -690,6 +1239,71 @@ describe('Virtual function', function () {
             expect(e.message).toEqual('This test is Too Big to Fail');
         }
     });
+
+    it('marshals an enum return value', function () {
+        expect(tester.vfunc_return_enum()).toEqual(GIMarshallingTests.Enum.VALUE2);
+    });
+
+    it('marshals an enum out parameter', function () {
+        expect(tester.vfunc_out_enum()).toEqual(GIMarshallingTests.Enum.VALUE3);
+    });
+
+    // These tests check what the refcount is of the returned objects; see
+    // comments in gimarshallingtests.c.
+    // Objects that are exposed in JS always have at least one reference (the
+    // toggle reference.) JS never owns more than one reference. There may be
+    // other references owned on the C side.
+    // In any case the refs should not be floating. We never have floating refs
+    // in JS.
+    function testVfuncRefcount(mode, transfer, expectedRefcount, options = {}, ...args) {
+        it(`marshals an object ${mode} parameter with transfer ${transfer}`, function () {
+            if (options.skip)
+                pending(options.skip);
+            const [refcount, floating] =
+                tester[`get_ref_info_for_vfunc_${mode}_object_transfer_${transfer}`](...args);
+            expect(floating).toBeFalsy();
+            expect(refcount).toEqual(expectedRefcount);
+        });
+    }
+    // 1 reference = the object is owned only by JS.
+    // 2 references = the object is owned by JS and the vfunc caller.
+    testVfuncRefcount('return', 'none', 1);
+    testVfuncRefcount('return', 'full', 2);
+    testVfuncRefcount('out', 'none', 1);
+    testVfuncRefcount('out', 'full', 2, {
+        skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/275',
+    });
+    testVfuncRefcount('in', 'none', 2, {}, GIMarshallingTests.Object);
+    testVfuncRefcount('in', 'full', 1, {
+        skip: 'https://gitlab.gnome.org/GNOME/gjs/issues/275',
+    }, GIMarshallingTests.Object);
+});
+
+describe('Inherited GObject', function () {
+    ['SubObject', 'SubSubObject'].forEach(klass => {
+        describe(klass, function () {
+            it('has a parent method that can be called', function () {
+                const o = new GIMarshallingTests.SubObject({int: 42});
+                expect(() => o.method()).not.toThrow();
+            });
+
+            it('has a method that can be called', function () {
+                const o = new GIMarshallingTests.SubObject({int: 0});
+                expect(() => o.sub_method()).not.toThrow();
+            });
+
+            it('has an overridden method that can be called', function () {
+                const o = new GIMarshallingTests.SubObject({int: 0});
+                expect(() => o.overwritten_method()).not.toThrow();
+            });
+
+            it('has a method with default implementation can be called', function () {
+                const o = new GIMarshallingTests.SubObject({int: 42});
+                o.method_with_default_implementation(43);
+                expect(o.int).toEqual(43);
+            });
+        });
+    });
 });
 
 describe('Interface', function () {
@@ -698,6 +1312,169 @@ describe('Interface', function () {
         let itself = ifaceImpl.get_as_interface();
         expect(ifaceImpl).toEqual(itself);
     });
+
+    it('can call an interface vfunc in C', function () {
+        let ifaceImpl = new GIMarshallingTests.InterfaceImpl();
+        expect(() => ifaceImpl.test_int8_in(42)).not.toThrow();
+        expect(() => GIMarshallingTests.test_interface_test_int8_in(ifaceImpl, 42))
+            .not.toThrow();
+    });
+
+    it('can implement a C interface', function () {
+        const I2Impl = GObject.registerClass({
+            Implements: [GIMarshallingTests.Interface2],
+        }, class I2Impl extends GObject.Object {});
+        expect(() => new I2Impl()).not.toThrow();
+    });
+
+    it('can implement a C interface with a vfunc', function () {
+        const I3Impl = GObject.registerClass({
+            Implements: [GIMarshallingTests.Interface3],
+        }, class I3Impl extends GObject.Object {
+            vfunc_test_variant_array_in(variantArray) {
+                this.stuff = variantArray.map(v => v.deepUnpack());
+            }
+        });
+        const i3 = new I3Impl();
+        i3.test_variant_array_in([
+            new GLib.Variant('b', true),
+            new GLib.Variant('s', 'hello'),
+            new GLib.Variant('i', 42),
+        ]);
+        expect(i3.stuff).toEqual([true, 'hello', 42]);
+    });
+});
+
+describe('Configurations of return values', function () {
+    it('can handle two out parameters', function () {
+        expect(GIMarshallingTests.int_out_out()).toEqual([6, 7]);
+    });
+
+    it('can handle three in and three out parameters', function () {
+        expect(GIMarshallingTests.int_three_in_three_out(1, 2, 3)).toEqual([1, 2, 3]);
+    });
+
+    it('can handle a return value and an out parameter', function () {
+        expect(GIMarshallingTests.int_return_out()).toEqual([6, 7]);
+    });
+
+    it('can handle four in parameters, two of which are nullable', function () {
+        expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', '4'))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', null))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, null, '4'))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, null, null))
+            .not.toThrow();
+    });
+
+    it('can handle three in parameters, one of which is nullable and one not', function () {
+        expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', '3'))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, null, '3'))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', null))
+            .toThrow();
+    });
+
+    it('can handle an array in parameter and two nullable in parameters', function () {
+        expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', '2'))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', null))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], null, '2'))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], null, null))
+            .not.toThrow();
+    });
+
+    it('can handle an array in parameter and two nullable in parameters, mixed with the array length', 
function () {
+        expect(() =>
+            GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], '2'))
+            .not.toThrow();
+        expect(() =>
+            GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], null))
+            .not.toThrow();
+        expect(() =>
+            GIMarshallingTests.array_in_utf8_two_in_out_of_order(null, [-1, 0, 1, 2], '2'))
+            .not.toThrow();
+        expect(() =>
+            GIMarshallingTests.array_in_utf8_two_in_out_of_order(null, [-1, 0, 1, 2], null))
+            .not.toThrow();
+    });
+});
+
+describe('GError', function () {
+    it('marshals a GError** signature as an exception', function () {
+        expect(() => GIMarshallingTests.gerror()).toThrow();
+    });
+
+    it('marshals a GError** at the end of the signature as an exception', function () {
+        expect(() => GIMarshallingTests.gerror_array_in([-1, 0, 1, 2])).toThrow();
+    });
+
+    it('marshals a GError** elsewhere in the signature as an out parameter', function () {
+        expect(GIMarshallingTests.gerror_out()).toEqual([
+            jasmine.any(GLib.Error),
+            'we got an error, life is shit',
+        ]);
+    });
+
+    it('marshals a GError** elsewhere in the signature as an out parameter with transfer none', function () {
+        expect(GIMarshallingTests.gerror_out_transfer_none()).toEqual([
+            jasmine.any(GLib.Error),
+            'we got an error, life is shit',
+        ]);
+    });
+
+    it('marshals GError as a return value', function () {
+        expect(GIMarshallingTests.gerror_return()).toEqual(jasmine.any(GLib.Error));
+    });
+});
+
+describe('Overrides', function () {
+    it('can add constants', function () {
+        expect(GIMarshallingTests.OVERRIDES_CONSTANT).toEqual(7);
+    });
+
+    it('can override a struct method', function () {
+        const struct = new GIMarshallingTests.OverridesStruct();
+        expect(struct.method()).toEqual(6);
+    });
+
+    it('can override an object constructor', function () {
+        const obj = new GIMarshallingTests.OverridesObject(42);
+        expect(obj.num).toEqual(42);
+    });
+
+    it('can override an object method', function () {
+        const obj = new GIMarshallingTests.OverridesObject();
+        expect(obj.method()).toEqual(6);
+    });
+});
+
+describe('Filename', function () {
+    testReturnValue('filename_list', []);
+});
+
+describe('GObject.ParamSpec', function () {
+    const pspec = GObject.ParamSpec.boolean('mybool', 'My Bool', 'My boolean property',
+        GObject.ParamFlags.READWRITE, true);
+    testInParameter('param_spec', pspec, {
+        funcName: 'param_spec_in_bool',
+    });
+
+    const expectedProps = {
+        name: 'test-param',
+        nick: 'test',
+        blurb: 'This is a test',
+        default_value: '42',
+        flags: GObject.ParamFlags.READABLE,
+        value_type: GObject.TYPE_STRING,
+    };
+    testReturnValue('param_spec', jasmine.objectContaining(expectedProps));
+    testOutParameter('param_spec', jasmine.objectContaining(expectedProps));
 });
 
 describe('GObject properties', function () {
@@ -706,12 +1483,56 @@ describe('GObject properties', function () {
         obj = new GIMarshallingTests.PropertiesObject();
     });
 
-    it('can handle GValues', function () {
-        obj.some_gvalue = 42;
-        expect(obj.some_gvalue).toEqual(42);
-        obj.some_gvalue = 'foo';
-        expect(obj.some_gvalue).toEqual('foo');
-    });
+    function testPropertyGetSet(type, value1, value2, skip = false) {
+        it(`gets and sets a ${type} property`, function () {
+            if (skip)
+                pending(skip);
+            obj[`some_${type}`] = value1;
+            expect(obj[`some_${type}`]).toEqual(value1);
+            obj[`some_${type}`] = value2;
+            expect(obj[`some_${type}`]).toEqual(value2);
+        });
+    }
+    testPropertyGetSet('boolean', true, false);
+    testPropertyGetSet('char', 42, 64);
+    testPropertyGetSet('uchar', 42, 64);
+    testPropertyGetSet('int', 42, 64);
+    testPropertyGetSet('uint', 42, 64);
+    testPropertyGetSet('long', 42, 64);
+    testPropertyGetSet('ulong', 42, 64);
+    testPropertyGetSet('int64', 42, 64);
+    testPropertyGetSet('uint64', 42, 64);
+
+    it('gets and sets a float property', function () {
+        obj.some_float = Math.E;
+        expect(obj.some_float).toBeCloseTo(Math.E);
+        obj.some_float = Math.PI;
+        expect(obj.some_float).toBeCloseTo(Math.PI);
+    });
+
+    it('gets and sets a double property', function () {
+        obj.some_double = Math.E;
+        expect(obj.some_double).toBeCloseTo(Math.E);
+        obj.some_double = Math.PI;
+        expect(obj.some_double).toBeCloseTo(Math.PI);
+    });
+
+    testPropertyGetSet('strv', ['0', '1', '2'], []);
+    testPropertyGetSet('boxed_struct', new GIMarshallingTests.BoxedStruct(),
+        new GIMarshallingTests.BoxedStruct({long_: 42}));
+    testPropertyGetSet('boxed_glist', null, null);
+    testPropertyGetSet('gvalue', 42, 'foo');
+    testPropertyGetSet('variant', new GLib.Variant('b', true),
+        new GLib.Variant('s', 'hello'));
+    testPropertyGetSet('object', new GObject.Object(),
+        new GIMarshallingTests.Object({int: 42}));
+    testPropertyGetSet('flags', GIMarshallingTests.Flags.VALUE2,
+        GIMarshallingTests.Flags.VALUE1 | GIMarshallingTests.Flags.VALUE2);
+    testPropertyGetSet('enum', GIMarshallingTests.GEnum.VALUE2,
+        GIMarshallingTests.GEnum.VALUE3);
+    testPropertyGetSet('byte_array', Uint8Array.of(1, 2, 3),
+        ByteArray.fromString('👾'),
+        'https://gitlab.gnome.org/GNOME/gjs/issues/276');
 
     it('gets a read-only property', function () {
         expect(obj.some_readonly).toEqual(42);
diff --git a/installed-tests/js/testRegress.js b/installed-tests/js/testRegress.js
new file mode 100644
index 00000000..a84197e0
--- /dev/null
+++ b/installed-tests/js/testRegress.js
@@ -0,0 +1,1519 @@
+const Regress = imports.gi.Regress;
+
+// We use Gio to have some objects that we know exist
+imports.gi.versions.Gtk = '3.0';
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const GObject = imports.gi.GObject;
+
+describe('Life, the Universe and Everything', function () {
+    it('includes null return value', function () {
+        expect(Regress.test_return_allow_none()).toBeNull();
+        expect(Regress.test_return_nullable()).toBeNull();
+    });
+
+    it('includes booleans', function () {
+        expect(Regress.test_boolean(false)).toBe(false);
+        expect(Regress.test_boolean(true)).toBe(true);
+        expect(Regress.test_boolean_true(true)).toBe(true);
+        expect(Regress.test_boolean_false(false)).toBe(false);
+    });
+
+    [8, 16, 32, 64].forEach(bits => {
+        it(`includes ${bits}-bit integers`, function () {
+            const method = `test_int${bits}`;
+            expect(Regress[method](42)).toBe(42);
+            expect(Regress[method](-42)).toBe(-42);
+        });
+
+        it(`includes unsigned ${bits}-bit integers`, function () {
+            expect(Regress[`test_uint${bits}`](42)).toBe(42);
+        });
+    });
+
+    ['short', 'int', 'long', 'ssize', 'float', 'double'].forEach(type => {
+        it(`includes ${type}s`, function () {
+            const method = `test_${type}`;
+            expect(Regress[method](42)).toBe(42);
+            expect(Regress[method](-42)).toBe(-42);
+        });
+    });
+
+    ['ushort', 'uint', 'ulong', 'size'].forEach(type => {
+        it(`includes ${type}s`, function () {
+            expect(Regress[`test_${type}`](42)).toBe(42);
+        });
+    });
+
+    describe('No implicit conversion to unsigned', function () {
+        ['uint8', 'uint16', 'uint32', 'uint64', 'uint', 'size'].forEach(type => {
+            it(`for ${type}`, function () {
+                expect(() => Regress[`test_${type}`](-42)).toThrow();
+            });
+        });
+    });
+
+    it('includes wide characters', function () {
+        expect(Regress.test_unichar('c')).toBe('c');
+        expect(Regress.test_unichar('')).toBe('');
+        expect(Regress.test_unichar('\u2665')).toBe('\u2665');
+    });
+
+    it('includes time_t', function () {
+        const now = Math.floor(new Date().getTime() / 1000);
+        const bounced = Math.floor(Regress.test_timet(now));
+        expect(bounced).toEqual(now);
+    });
+
+    it('includes GTypes', function () {
+        expect(Regress.test_gtype(GObject.TYPE_NONE)).toBe(GObject.TYPE_NONE);
+        expect(Regress.test_gtype(String)).toBe(GObject.TYPE_STRING);
+        expect(Regress.test_gtype(GObject.Object)).toBe(GObject.Object.$gtype);
+    });
+
+    it('closures', function () {
+        const callback = jasmine.createSpy('callback').and.returnValue(42);
+        expect(Regress.test_closure(callback)).toEqual(42);
+        expect(callback).toHaveBeenCalledWith();
+    });
+
+    it('closures with one argument', function () {
+        const callback = jasmine.createSpy('callback')
+            .and.callFake(someValue => someValue);
+        expect(Regress.test_closure_one_arg(callback, 42)).toEqual(42);
+        expect(callback).toHaveBeenCalledWith(42);
+    });
+
+    it('closure with GLib.Variant argument', function () {
+        const callback = jasmine.createSpy('callback')
+            .and.returnValue(new GLib.Variant('s', 'hello'));
+        const variant = new GLib.Variant('i', 42);
+        expect(Regress.test_closure_variant(callback, variant).deepUnpack())
+            .toEqual('hello');
+        expect(callback).toHaveBeenCalledWith(variant);
+    });
+
+    describe('GValue marshalling', function () {
+        it('integer in', function () {
+            expect(Regress.test_int_value_arg(42)).toEqual(42);
+        });
+
+        it('integer out', function () {
+            expect(Regress.test_value_return(42)).toEqual(42);
+        });
+    });
+
+    // See testCairo.js for the following tests, since that will be skipped if
+    // we are building without Cairo support:
+    // Regress.test_cairo_context_full_return()
+    // Regress.test_cairo_context_none_in()
+    // Regress.test_cairo_surface_none_return()
+    // Regress.test_cairo_surface_full_return()
+    // Regress.test_cairo_surface_none_in()
+    // Regress.test_cairo_surface_full_out()
+    // Regress.TestObj.emit_sig_with_foreign_struct()
+
+    it('integer GLib.Variant', function () {
+        const ivar = Regress.test_gvariant_i();
+        expect(ivar.get_type_string()).toEqual('i');
+        expect(ivar.unpack()).toEqual(1);
+    });
+
+    it('string GLib.Variant', function () {
+        const svar = Regress.test_gvariant_s();
+        expect(String.fromCharCode(svar.classify())).toEqual('s');
+        expect(svar.unpack()).toEqual('one');
+    });
+
+    it('dictionary GLib.Variant', function () {
+        const asvvar = Regress.test_gvariant_asv();
+        expect(asvvar.recursiveUnpack()).toEqual({name: 'foo', timeout: 10});
+    });
+
+    it('variant GLib.Variant', function () {
+        const vvar = Regress.test_gvariant_v();
+        expect(vvar.unpack()).toEqual(jasmine.any(GLib.Variant));
+        expect(vvar.recursiveUnpack()).toEqual('contents');
+    });
+
+    it('string array GLib.Variant', function () {
+        const asvar = Regress.test_gvariant_as();
+        expect(asvar.deepUnpack()).toEqual(['one', 'two', 'three']);
+    });
+
+    describe('UTF-8 strings', function () {
+        const CONST_STR = 'const ♥ utf8';
+        const NONCONST_STR = 'nonconst ♥ utf8';
+
+        it('as return types', function () {
+            expect(Regress.test_utf8_const_return()).toEqual(CONST_STR);
+            expect(Regress.test_utf8_nonconst_return()).toEqual(NONCONST_STR);
+        });
+
+        it('as in parameters', function () {
+            Regress.test_utf8_const_in(CONST_STR);
+        });
+
+        it('as out parameters', function () {
+            expect(Regress.test_utf8_out()).toEqual(NONCONST_STR);
+        });
+
+        xit('as in-out parameters', function () {
+            expect(Regress.test_utf8_inout(CONST_STR)).toEqual(NONCONST_STR);
+        }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192');
+    });
+
+    it('return values in filename encoding', function () {
+        const filenames = Regress.test_filename_return();
+        expect(filenames).toEqual(['\u00e5\u00e4\u00f6', '/etc/fstab']);
+    });
+
+    describe('Various configurations of arguments', function () {
+        it('in after out', function () {
+            const str = 'hello';
+            const len = Regress.test_int_out_utf8(str);
+            expect(len).toEqual(str.length);
+        });
+
+        it('multiple number args', function () {
+            const [times2, times3] = Regress.test_multi_double_args(2.5);
+            expect(times2).toEqual(5);
+            expect(times3).toEqual(7.5);
+        });
+
+        it('multiple string out parameters', function () {
+            const [first, second] = Regress.test_utf8_out_out();
+            expect(first).toEqual('first');
+            expect(second).toEqual('second');
+        });
+
+        it('strings as return value and output parameter', function () {
+            const [first, second] = Regress.test_utf8_out_nonconst_return();
+            expect(first).toEqual('first');
+            expect(second).toEqual('second');
+        });
+
+        it('nullable string in parameter', function () {
+            expect(() => Regress.test_utf8_null_in(null)).not.toThrow();
+        });
+
+        it('nullable string out parameter', function () {
+            expect(Regress.test_utf8_null_out()).toBeNull();
+        });
+    });
+
+    ['int', 'gint8', 'gint16', 'gint32', 'gint64'].forEach(inttype => {
+        it(`arrays of ${inttype} in`, function () {
+            expect(Regress[`test_array_${inttype}_in`]([1, 2, 3, 4])).toEqual(10);
+        });
+    });
+
+    it('implicit conversions from strings to int arrays', function () {
+        expect(Regress.test_array_gint8_in('\x01\x02\x03\x04')).toEqual(10);
+        expect(Regress.test_array_gint16_in('\x01\x02\x03\x04')).toEqual(10);
+        expect(Regress.test_array_gint16_in('\u0100\u0200\u0300\u0400')).toEqual(2560);
+    });
+
+    it('out arrays of integers', function () {
+        expect(Regress.test_array_int_out()).toEqual([0, 1, 2, 3, 4]);
+    });
+
+    xit('inout arrays of integers', function () {
+        expect(Regress.test_array_int_inout([0, 1, 2, 3, 4])).toEqual([2, 3, 4, 5]);
+    }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/issues/192');
+
+    describe('String arrays', function () {
+        it('marshalling in', function () {
+            expect(Regress.test_strv_in(['1', '2', '3'])).toBeTruthy();
+            expect(Regress.test_strv_in(['4', '5', '6'])).toBeFalsy();
+            // Second two are deliberately not strings
+            expect(() => Regress.test_strv_in(['1', 2, 3])).toThrow();
+        });
+
+        it('marshalling out', function () {
+            expect(Regress.test_strv_out())
+                .toEqual(['thanks', 'for', 'all', 'the', 'fish']);
+        });
+
+        it('marshalling return value with container transfer', function () {
+            expect(Regress.test_strv_out_container()).toEqual(['1', '2', '3']);
+        });
+
+        it('marshalling out parameter with container transfer', function () {
+            expect(Regress.test_strv_outarg()).toEqual(['1', '2', '3']);
+        });
+    });
+
+    it('GType arrays', function () {
+        expect(Regress.test_array_gtype_in([Gio.SimpleAction, Gio.Icon, GObject.TYPE_BOXED]))
+            .toEqual('[GSimpleAction,GIcon,GBoxed,]');
+        expect(() => Regress.test_array_gtype_in(42)).toThrow();
+        expect(() => Regress.test_array_gtype_in([undefined])).toThrow();
+        // 80 is G_TYPE_OBJECT, but we don't want it to work
+        expect(() => Regress.test_array_gtype_in([80])).toThrow();
+    });
+
+    describe('Fixed arrays of integers', function () {
+        it('marshals as an in parameter', function () {
+            expect(Regress.test_array_fixed_size_int_in([1, 2, 3, 4])).toEqual(10);
+        });
+
+        it('marshals as an out parameter', function () {
+            expect(Regress.test_array_fixed_size_int_out()).toEqual([0, 1, 2, 3, 4]);
+        });
+
+        it('marshals as a return value', function () {
+            expect(Regress.test_array_fixed_size_int_return()).toEqual([0, 1, 2, 3, 4]);
+        });
+    });
+
+    it("string array that's const in C", function () {
+        expect(Regress.test_strv_out_c()).toEqual(['thanks', 'for', 'all', 'the', 'fish']);
+    });
+
+    describe('arrays of integers with length parameter', function () {
+        it('marshals as a return value with transfer full', function () {
+            expect(Regress.test_array_int_full_out()).toEqual([0, 1, 2, 3, 4]);
+        });
+
+        it('marshals as a return value with transfer none', function () {
+            expect(Regress.test_array_int_none_out()).toEqual([1, 2, 3, 4, 5]);
+        });
+
+        it('marshalls as a nullable in parameter', function () {
+            expect(() => Regress.test_array_int_null_in(null)).not.toThrow();
+        });
+
+        it('marshals as a nullable return value', function () {
+            expect(Regress.test_array_int_null_out()).toEqual([]);
+        });
+    });
+
+    ['glist', 'gslist'].forEach(list => {
+        describe(`${list} types`, function () {
+            const STR_LIST = ['1', '2', '3'];
+
+            it('return with transfer-none', function () {
+                expect(Regress[`test_${list}_nothing_return`]()).toEqual(STR_LIST);
+                expect(Regress[`test_${list}_nothing_return2`]()).toEqual(STR_LIST);
+            });
+
+            it('return with transfer-container', function () {
+                expect(Regress[`test_${list}_container_return`]()).toEqual(STR_LIST);
+            });
+
+            it('return with transfer-full', function () {
+                expect(Regress[`test_${list}_everything_return`]()).toEqual(STR_LIST);
+            });
+
+            it('in with transfer-none', function () {
+                Regress[`test_${list}_nothing_in`](STR_LIST);
+                Regress[`test_${list}_nothing_in2`](STR_LIST);
+            });
+
+            it('nullable in', function () {
+                expect(() => Regress[`test_${list}_null_in`]([])).not.toThrow();
+            });
+
+            it('nullable out', function () {
+                expect(Regress[`test_${list}_null_out`]()).toEqual([]);
+            });
+
+            xit('in with transfer-container', function () {
+                Regress[`test_${list}_container_in`](STR_LIST);
+            }).pend('Function not added to gobject-introspection test suite yet');
+        });
+    });
+
+    it('GList of GTypes in with transfer container', function () {
+        expect(() =>
+            Regress.test_glist_gtype_container_in([Regress.TestObj, Regress.TestSubObj]))
+            .not.toThrow();
+    });
+
+    describe('GHash type', function () {
+        const EXPECTED_HASH = {baz: 'bat', foo: 'bar', qux: 'quux'};
+
+        it('null GHash out', function () {
+            expect(Regress.test_ghash_null_return()).toBeNull();
+        });
+
+        it('out GHash', function () {
+            expect(Regress.test_ghash_nothing_return()).toEqual(EXPECTED_HASH);
+            expect(Regress.test_ghash_nothing_return2()).toEqual(EXPECTED_HASH);
+        });
+
+        const GVALUE_HASH_TABLE = {
+            'integer': 12,
+            'boolean': true,
+            'string': 'some text',
+            'strings': ['first', 'second', 'third'],
+            'flags': Regress.TestFlags.FLAG1 | Regress.TestFlags.FLAG3,
+            'enum': Regress.TestEnum.VALUE2,
+        };
+
+        it('with GValue value type out', function () {
+            expect(Regress.test_ghash_gvalue_return()).toEqual(GVALUE_HASH_TABLE);
+        });
+
+        xit('with GValue value type in', function () {
+            expect(() => Regress.test_ghash_gvalue_in(GVALUE_HASH_TABLE)).not.toThrow();
+        }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/272');
+
+        it('marshals as a return value with transfer container', function () {
+            expect(Regress.test_ghash_container_return()).toEqual(EXPECTED_HASH);
+        });
+
+        it('marshals as a return value with transfer full', function () {
+            expect(Regress.test_ghash_everything_return()).toEqual(EXPECTED_HASH);
+        });
+
+        it('null GHash in', function () {
+            Regress.test_ghash_null_in(null);
+        });
+
+        it('null GHashTable out', function () {
+            expect(Regress.test_ghash_null_out()).toBeNull();
+        });
+
+        it('in GHash', function () {
+            Regress.test_ghash_nothing_in(EXPECTED_HASH);
+            Regress.test_ghash_nothing_in2(EXPECTED_HASH);
+        });
+
+        it('nested GHash', function () {
+            const EXPECTED_NESTED_HASH = {wibble: EXPECTED_HASH};
+
+            expect(Regress.test_ghash_nested_everything_return())
+                .toEqual(EXPECTED_NESTED_HASH);
+            expect(Regress.test_ghash_nested_everything_return2())
+                .toEqual(EXPECTED_NESTED_HASH);
+        });
+    });
+
+    describe('GArray', function () {
+        it('marshals as a return value with transfer container', function () {
+            expect(Regress.test_garray_container_return()).toEqual(['regress']);
+        });
+
+        it('marshals as a return value with transfer full', function () {
+            expect(Regress.test_garray_full_return()).toEqual(['regress']);
+        });
+    });
+
+    it('enum parameter', function () {
+        expect(Regress.test_enum_param(Regress.TestEnum.VALUE1)).toEqual('value1');
+        expect(Regress.test_enum_param(Regress.TestEnum.VALUE3)).toEqual('value3');
+    });
+
+    it('unsigned enum parameter', function () {
+        expect(Regress.test_unsigned_enum_param(Regress.TestEnumUnsigned.VALUE1))
+            .toEqual('value1');
+        expect(Regress.test_unsigned_enum_param(Regress.TestEnumUnsigned.VALUE2))
+            .toEqual('value2');
+    });
+
+    it('flags parameter', function () {
+        expect(Regress.global_get_flags_out()).toEqual(Regress.TestFlags.FLAG1 |
+            Regress.TestFlags.FLAG3);
+    });
+
+    describe('Simple introspected struct', function () {
+        let struct;
+        beforeEach(function () {
+            struct = new Regress.TestStructA();
+            struct.some_int = 42;
+            struct.some_int8 = 43;
+            struct.some_double = 42.5;
+            struct.some_enum = Regress.TestEnum.VALUE3;
+        });
+
+        it('sets fields correctly', function () {
+            expect(struct.some_int).toEqual(42);
+            expect(struct.some_int8).toEqual(43);
+            expect(struct.some_double).toEqual(42.5);
+            expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3);
+        });
+
+        it('can clone', function () {
+            const b = struct.clone();
+            expect(b.some_int).toEqual(42);
+            expect(b.some_int8).toEqual(43);
+            expect(b.some_double).toEqual(42.5);
+            expect(b.some_enum).toEqual(Regress.TestEnum.VALUE3);
+        });
+
+        it('can be modified by a method', function () {
+            const c = Regress.TestStructA.parse('foobar');
+            expect(c.some_int).toEqual(23);
+        });
+
+        describe('constructors', function () {
+            beforeEach(function () {
+                struct = new Regress.TestStructA({
+                    some_int: 42,
+                    some_int8: 43,
+                    some_double: 42.5,
+                    some_enum: Regress.TestEnum.VALUE3,
+                });
+            });
+
+            it('"copies" an object from a hash of field values', function () {
+                expect(struct.some_int).toEqual(42);
+                expect(struct.some_int8).toEqual(43);
+                expect(struct.some_double).toEqual(42.5);
+                expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3);
+            });
+
+            it('catches bad field names', function () {
+                expect(() => new Regress.TestStructA({junk: 42})).toThrow();
+            });
+
+            it('copies an object from another object of the same type', function () {
+                const copy = new Regress.TestStructA(struct);
+                expect(copy.some_int).toEqual(42);
+                expect(copy.some_int8).toEqual(43);
+                expect(copy.some_double).toEqual(42.5);
+                expect(copy.some_enum).toEqual(Regress.TestEnum.VALUE3);
+            });
+        });
+    });
+
+    it('out arrays of structs', function () {
+        const array = Regress.test_array_struct_out();
+        const ints = array.map(struct => struct.some_int);
+        expect(ints).toEqual([22, 33, 44]);
+    });
+
+    describe('Introspected nested struct', function () {
+        let struct;
+        beforeEach(function () {
+            struct = new Regress.TestStructB();
+            struct.some_int8 = 43;
+            struct.nested_a.some_int8 = 66;
+        });
+
+        it('sets fields correctly', function () {
+            expect(struct.some_int8).toEqual(43);
+            expect(struct.nested_a.some_int8).toEqual(66);
+        });
+
+        it('can clone', function () {
+            const b = struct.clone();
+            expect(b.some_int8).toEqual(43);
+            expect(b.nested_a.some_int8).toEqual(66);
+        });
+    });
+
+    describe('Introspected simple boxed struct', function () {
+        let struct;
+        beforeEach(function () {
+            struct = new Regress.TestSimpleBoxedA();
+            struct.some_int = 42;
+            struct.some_int8 = 43;
+            struct.some_double = 42.5;
+            struct.some_enum = Regress.TestEnum.VALUE3;
+        });
+
+        it('sets fields correctly', function () {
+            expect(struct.some_int).toEqual(42);
+            expect(struct.some_int8).toEqual(43);
+            expect(struct.some_double).toEqual(42.5);
+            expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3);
+        });
+
+        it('can be passed to a method', function () {
+            const other = new Regress.TestSimpleBoxedA({
+                some_int: 42,
+                some_int8: 43,
+                some_double: 42.5,
+            });
+            expect(other.equals(struct)).toBeTruthy();
+        });
+
+        it('can be returned from a method', function () {
+            const other = Regress.TestSimpleBoxedA.const_return();
+            expect(other.some_int).toEqual(5);
+            expect(other.some_int8).toEqual(6);
+            expect(other.some_double).toEqual(7);
+        });
+
+        describe('constructors', function () {
+            beforeEach(function () {
+                struct = new Regress.TestSimpleBoxedA({
+                    some_int: 42,
+                    some_int8: 43,
+                    some_double: 42.5,
+                    some_enum: Regress.TestEnum.VALUE3,
+                });
+            });
+
+            it('"copies" an object from a hash of field values', function () {
+                expect(struct.some_int).toEqual(42);
+                expect(struct.some_int8).toEqual(43);
+                expect(struct.some_double).toEqual(42.5);
+                expect(struct.some_enum).toEqual(Regress.TestEnum.VALUE3);
+            });
+
+            it('catches bad field names', function () {
+                expect(() => new Regress.TestSimpleBoxedA({junk: 42})).toThrow();
+            });
+
+            it('copies an object from another object of the same type', function () {
+                const copy = new Regress.TestSimpleBoxedA(struct);
+                expect(copy).toEqual(jasmine.any(Regress.TestSimpleBoxedA));
+                expect(copy.some_int).toEqual(42);
+                expect(copy.some_int8).toEqual(43);
+                expect(copy.some_double).toEqual(42.5);
+                expect(copy.some_enum).toEqual(Regress.TestEnum.VALUE3);
+            });
+        });
+    });
+
+    describe('Introspected boxed nested struct', function () {
+        let struct;
+        beforeEach(function () {
+            struct = new Regress.TestSimpleBoxedB();
+            struct.some_int8 = 42;
+            struct.nested_a.some_int = 43;
+        });
+
+        it('reads fields and nested fields', function () {
+            expect(struct.some_int8).toEqual(42);
+            expect(struct.nested_a.some_int).toEqual(43);
+        });
+
+        it('assigns nested struct field from an instance', function () {
+            struct.nested_a = new Regress.TestSimpleBoxedA({some_int: 53});
+            expect(struct.nested_a.some_int).toEqual(53);
+        });
+
+        it('assigns nested struct field directly from a hash of field values', function () {
+            struct.nested_a = {some_int: 63};
+            expect(struct.nested_a.some_int).toEqual(63);
+        });
+
+        describe('constructors', function () {
+            it('constructs with a nested hash of field values', function () {
+                const simple2 = new Regress.TestSimpleBoxedB({
+                    some_int8: 42,
+                    nested_a: {
+                        some_int: 43,
+                        some_int8: 44,
+                        some_double: 43.5,
+                    },
+                });
+                expect(simple2.some_int8).toEqual(42);
+                expect(simple2.nested_a.some_int).toEqual(43);
+                expect(simple2.nested_a.some_int8).toEqual(44);
+                expect(simple2.nested_a.some_double).toEqual(43.5);
+            });
+
+            it('copies an object from another object of the same type', function () {
+                const copy = new Regress.TestSimpleBoxedB(struct);
+                expect(copy.some_int8).toEqual(42);
+                expect(copy.nested_a.some_int).toEqual(43);
+            });
+        });
+    });
+
+    describe('Introspected boxed types', function () {
+        describe('Opaque', function () {
+            it('constructs from a default constructor', function () {
+                const boxed = new Regress.TestBoxed();
+                expect(boxed).toEqual(jasmine.any(Regress.TestBoxed));
+            });
+
+            it('sets fields correctly', function () {
+                const boxed = new Regress.TestBoxed();
+                boxed.some_int8 = 42;
+                expect(boxed.some_int8).toEqual(42);
+            });
+
+            it('constructs from a static constructor', function () {
+                const boxed = Regress.TestBoxed.new_alternative_constructor1(42);
+                expect(boxed.some_int8).toEqual(42);
+            });
+
+            it('constructs from a static constructor with different args', function () {
+                const boxed = Regress.TestBoxed.new_alternative_constructor2(40, 2);
+                expect(boxed.some_int8).toEqual(42);
+            });
+
+            it('constructs from a static constructor with differently typed args', function () {
+                const boxed = Regress.TestBoxed.new_alternative_constructor3('42');
+                expect(boxed.some_int8).toEqual(42);
+            });
+
+            it('constructs from a another object of the same type', function () {
+                const boxed = new Regress.TestBoxed({some_int8: 42});
+                const copy = new Regress.TestBoxed(boxed);
+                expect(copy.some_int8).toEqual(42);
+                expect(copy.equals(boxed)).toBeTruthy();
+            });
+
+            it('ensures methods are named correctly', function () {
+                const boxed = new Regress.TestBoxed();
+                expect(boxed.s_not_a_method).not.toBeDefined();
+                expect(boxed.not_a_method).not.toBeDefined();
+                expect(() => Regress.test_boxeds_not_a_method(boxed)).not.toThrow();
+            });
+
+            it('ensures static methods are named correctly', function () {
+                expect(Regress.TestBoxed.s_not_a_static).not.toBeDefined();
+                expect(Regress.TestBoxed.not_a_static).not.toBeDefined();
+                expect(Regress.test_boxeds_not_a_static).not.toThrow();
+            });
+        });
+
+        describe('Simple', function () {
+            it('sets fields correctly', function () {
+                const boxed = new Regress.TestBoxedB();
+                boxed.some_int8 = 7;
+                boxed.some_long = 5;
+                expect(boxed.some_int8).toEqual(7);
+                expect(boxed.some_long).toEqual(5);
+            });
+
+            it('constructs from a static constructor', function () {
+                const boxed = Regress.TestBoxedB.new(7, 5);
+                expect(boxed.some_int8).toEqual(7);
+                expect(boxed.some_long).toEqual(5);
+            });
+
+            it('constructs from another object of the same type', function () {
+                const boxed = Regress.TestBoxedB.new(7, 5);
+                const copy = new Regress.TestBoxedB(boxed);
+                expect(copy.some_int8).toEqual(7);
+                expect(copy.some_long).toEqual(5);
+            });
+
+            // Regress.TestBoxedB has a constructor that takes multiple arguments,
+            // but since it is directly allocatable, we keep the old style of
+            // passing an hash of fields. The two real world structs that have this
+            // behavior are Clutter.Color and Clutter.ActorBox.
+            it('constructs in backwards compatibility mode', function () {
+                const boxed = new Regress.TestBoxedB({some_int8: 7, some_long: 5});
+                expect(boxed.some_int8).toEqual(7);
+                expect(boxed.some_long).toEqual(5);
+            });
+        });
+
+        describe('Refcounted', function () {
+            it('constructs from a default constructor', function () {
+                const boxed = new Regress.TestBoxedC();
+                expect(boxed.another_thing).toEqual(42);
+            });
+
+            it('constructs from another object of the same type', function () {
+                const boxed = new Regress.TestBoxedC({another_thing: 43});
+                const copy = new Regress.TestBoxedC(boxed);
+                expect(copy.another_thing).toEqual(43);
+            });
+        });
+
+        describe('Private', function () {
+            it('constructs using a custom constructor', function () {
+                const boxed = new Regress.TestBoxedD('abcd', 8);
+                expect(boxed.get_magic()).toEqual(12);
+            });
+
+            it('constructs from another object of the same type', function () {
+                const boxed = new Regress.TestBoxedD('abcd', 8);
+                const copy = new Regress.TestBoxedD(boxed);
+                expect(copy.get_magic()).toEqual(12);
+            });
+
+            it('does not construct with a default constructor', function () {
+                expect(() => new Regress.TestBoxedD()).toThrow();
+            });
+        });
+    });
+
+    describe('wrong type for GBoxed', function () {
+        let simpleBoxed, wrongObject, wrongBoxed;
+        beforeEach(function () {
+            simpleBoxed = new Regress.TestSimpleBoxedA();
+            wrongObject = new Gio.SimpleAction();
+            wrongBoxed = new GLib.KeyFile();
+        });
+
+        // simpleBoxed.equals expects a Everything.TestSimpleBoxedA
+        it('function does not accept a GObject of the wrong type', function () {
+            expect(() => simpleBoxed.equals(wrongObject)).toThrow();
+        });
+
+        it('function does not accept a GBoxed of the wrong type', function () {
+            expect(() => simpleBoxed.equals(wrongBoxed)).toThrow();
+        });
+
+        it('function does accept a GBoxed of the correct type', function () {
+            expect(simpleBoxed.equals(simpleBoxed)).toBeTruthy();
+        });
+
+        it('method cannot be called on a GObject', function () {
+            expect(() => Regress.TestSimpleBoxedA.prototype.copy.call(wrongObject))
+                .toThrow();
+        });
+
+        it('method cannot be called on a GBoxed of the wrong type', function () {
+            expect(() => Regress.TestSimpleBoxedA.protoype.copy.call(wrongBoxed))
+                .toThrow();
+        });
+
+        it('method can be called on correct GBoxed type', function () {
+            expect(() => Regress.TestSimpleBoxedA.prototype.copy.call(simpleBoxed))
+                .not.toThrow();
+        });
+    });
+
+    describe('Introspected GObject', function () {
+        let o;
+        beforeEach(function () {
+            o = new Regress.TestObj({
+                // These properties have backing public fields with different names
+                int: 42,
+                float: 3.1416,
+                double: 2.71828,
+            });
+        });
+
+        it('can access fields with simple types', function () {
+            // Compare the values gotten through the GObject property getters to the
+            // values of the backing fields
+            expect(o.some_int8).toEqual(o.int);
+            expect(o.some_float).toEqual(o.float);
+            expect(o.some_double).toEqual(o.double);
+        });
+
+        it('cannot access fields with complex types (GI limitation)', function () {
+            expect(() => o.parent_instance).toThrow();
+            expect(() => o.function_ptr).toThrow();
+        });
+
+        it('throws when setting a read-only field', function () {
+            expect(() => (o.some_int8 = 41)).toThrow();
+        });
+
+        it('has normal Object methods', function () {
+            o.ownprop = 'foo';
+            // eslint-disable-next-line no-prototype-builtins
+            expect(o.hasOwnProperty('ownprop')).toBeTruthy();
+        });
+
+        it('sets write-only properties', function () {
+            expect(o.int).not.toEqual(0);
+            o.write_only = true;
+            expect(o.int).toEqual(0);
+        });
+
+        it('gives undefined for write-only properties', function () {
+            expect(o.write_only).not.toBeDefined();
+        });
+
+        it('constructs from constructors annotated with (constructor)', function () {
+            expect(Regress.TestObj.new(o)).toEqual(jasmine.any(Regress.TestObj));
+            expect(Regress.TestObj.constructor()).toEqual(jasmine.any(Regress.TestObj));
+        });
+
+        it('static methods', function () {
+            const v = Regress.TestObj.new_from_file('/enoent');
+            expect(v).toEqual(jasmine.any(Regress.TestObj));
+        });
+
+        describe('Object-valued GProperty', function () {
+            let o1, t1, t2;
+            beforeEach(function () {
+                o1 = new GObject.Object();
+                t1 = new Regress.TestObj({bare: o1});
+                t2 = new Regress.TestSubObj();
+                t2.bare = o1;
+            });
+
+            it('marshals correctly in the getter', function () {
+                expect(t1.bare).toBe(o1);
+            });
+
+            it('marshals correctly when inherited', function () {
+                expect(t2.bare).toBe(o1);
+            });
+
+            it('marshals into setter function', function () {
+                const o2 = new GObject.Object();
+                t2.set_bare(o2);
+                expect(t2.bare).toBe(o2);
+            });
+
+            it('marshals null', function () {
+                t2.unset_bare();
+                expect(t2.bare).toBeNull();
+            });
+        });
+
+        describe('Signal connection', function () {
+            it('calls correct handlers with correct arguments', function () {
+                const handler = jasmine.createSpy('handler');
+                const handlerId = o.connect('test', handler);
+                handler.and.callFake(() => o.disconnect(handlerId));
+
+                o.emit('test');
+                expect(handler).toHaveBeenCalledTimes(1);
+                expect(handler).toHaveBeenCalledWith(o);
+
+                handler.calls.reset();
+                o.emit('test');
+                expect(handler).not.toHaveBeenCalled();
+            });
+
+            it('throws errors for invalid signals', function () {
+                expect(() => o.connect('invalid-signal', () => {})).toThrow();
+                expect(() => o.emit('invalid-signal')).toThrow();
+            });
+
+            it('signal handler with static scope arg gets arg passed by reference', function () {
+                const b = new Regress.TestSimpleBoxedA({
+                    some_int: 42,
+                    some_int8: 43,
+                    some_double: 42.5,
+                    some_enum: Regress.TestEnum.VALUE3,
+                });
+                o.connect('test-with-static-scope-arg', (signalObject, signalArg) => {
+                    signalArg.some_int = 44;
+                });
+                o.emit('test-with-static-scope-arg', b);
+                expect(b.some_int).toEqual(44);
+            });
+
+            it('signal with object gets correct arguments', function (done) {
+                o.connect('sig-with-obj', (self, objectParam) => {
+                    expect(objectParam.int).toEqual(3);
+                    done();
+                });
+                o.emit_sig_with_obj();
+            });
+
+            // See testCairo.js for a test of
+            // Regress.TestObj::sig-with-foreign-struct.
+
+            xit('signal with int64 gets correct value', function (done) {
+                o.connect('sig-with-int64-prop', (self, number) => {
+                    expect(number).toEqual(GLib.MAXINT64);
+                    done();
+                    return GLib.MAXINT64;
+                });
+                o.emit_sig_with_int64();
+            }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271');
+
+            xit('signal with uint64 gets correct value', function (done) {
+                o.connect('sig-with-uint64-prop', (self, number) => {
+                    expect(number).toEqual(GLib.MAXUINT64);
+                    done();
+                    return GLib.MAXUINT64;
+                });
+                o.emit_sig_with_uint64();
+            }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271');
+
+            it('signal with array len parameter is not passed correct array and no length arg', function 
(done) {
+                o.connect('sig-with-array-len-prop', (signalObj, signalArray, shouldBeUndefined) => {
+                    expect(shouldBeUndefined).not.toBeDefined();
+                    expect(signalArray).toEqual([0, 1, 2, 3, 4]);
+                    done();
+                });
+                o.emit_sig_with_array_len_prop();
+            });
+
+            xit('can pass parameter to signal with array len parameter via emit', function (done) {
+                o.connect('sig-with-array-len-prop', (signalObj, signalArray) => {
+                    expect(signalArray).toEqual([0, 1, 2, 3, 4]);
+                    done();
+                });
+                o.emit('sig-with-array-len-prop', [0, 1, 2, 3, 4]);
+            }).pend('Not yet implemented');
+
+            xit('can pass null to signal with array len parameter', function () {
+                const handler = jasmine.createSpy('handler');
+                o.connect('sig-with-array-len-prop', handler);
+                o.emit('sig-with-array-len-prop', null);
+                expect(handler).toHaveBeenCalledWith([jasmine.any(Object), null]);
+            }).pend('Not yet implemented');
+
+            xit('signal with int in-out parameter', function () {
+                const handler = jasmine.createSpy('handler').and.callFake(() => 43);
+                o.connect('sig-with-inout-int', handler);
+                o.emit_sig_with_inout_int();
+                expect(handler.toHaveBeenCalledWith([jasmine.any(Object), 42]));
+            }).pend('Not yet implemented');
+
+            it('GError signal with GError set', function (done) {
+                o.connect('sig-with-gerror', (obj, e) => {
+                    expect(e).toEqual(jasmine.any(Gio.IOErrorEnum));
+                    expect(e.domain).toEqual(Gio.io_error_quark());
+                    expect(e.code).toEqual(Gio.IOErrorEnum.FAILED);
+                    done();
+                });
+                o.emit_sig_with_error();
+            }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/169');
+
+            it('GError signal with no GError set', function (done) {
+                o.connect('sig-with-gerror', (obj, e) => {
+                    expect(e).toBeNull();
+                    done();
+                });
+                o.emit_sig_with_null_error();
+            }).pend('https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/169');
+        });
+
+        it('can call an instance method', function () {
+            expect(o.instance_method()).toEqual(-1);
+        });
+
+        it('can call a transfer-full instance method', function () {
+            expect(() => o.instance_method_full()).not.toThrow();
+        });
+
+        it('can call a static method', function () {
+            expect(Regress.TestObj.static_method(5)).toEqual(5);
+        });
+
+        it('can call a method annotated with (method)', function () {
+            expect(() => o.forced_method()).not.toThrow();
+        });
+
+        describe('Object torture signature', function () {
+            it('0', function () {
+                const [y, z, q] = o.torture_signature_0(42, 'foo', 7);
+                expect(Math.floor(y)).toEqual(42);
+                expect(z).toEqual(84);
+                expect(q).toEqual(10);
+            });
+
+            it('1 fail', function () {
+                expect(() => o.torture_signature_1(42, 'foo', 7)).toThrow();
+            });
+
+            it('1 success', function () {
+                const [, y, z, q] = o.torture_signature_1(11, 'barbaz', 8);
+                expect(Math.floor(y)).toEqual(11);
+                expect(z).toEqual(22);
+                expect(q).toEqual(14);
+            });
+        });
+
+        describe('Introspected function length', function () {
+            it('skips over instance parameters of methods', function () {
+                expect(o.set_bare.length).toEqual(1);
+            });
+
+            it('skips over out and GError parameters', function () {
+                expect(o.torture_signature_1.length).toEqual(3);
+            });
+
+            it('does not skip over inout parameters', function () {
+                expect(o.skip_return_val.length).toEqual(5);
+            });
+
+            xit('skips over return value annotated with skip', function () {
+                const [b, d, sum] = o.skip_return_val(1, 2, 3, 4, 5);
+                expect(b).toEqual(2);
+                expect(d).toEqual(4);
+                expect(sum).toEqual(54);
+
+                const retval = o.skip_return_val_no_out(1);
+                expect(retval).not.toBeDefined();
+            }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59');
+
+            xit('skips over parameters annotated with skip', function () {
+                expect(o.skip_param.length).toEqual(4);
+
+                const [success, b, d, sum] = o.skip_param(1, 2, 3, 4);
+                expect(success).toBeTruthy();
+                expect(b).toEqual(2);
+                expect(d).toEqual(3);
+                expect(sum).toEqual(43);
+            }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59');
+
+            xit('skips over out parameters annotated with skip', function () {
+                const [success, d, sum] = o.skip_out_param(1, 2, 3, 4, 5);
+                expect(success).toBeTruthy();
+                expect(d).toEqual(4);
+                expect(sum).toEqual(54);
+            }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59');
+
+            xit('skips over inout parameters annotated with skip', function () {
+                expect(o.skip_inout_param.length).toEqual(4);
+
+                const [success, b, sum] = o.skip_inout_param(1, 2, 3, 4);
+                expect(success).toBeTruthy();
+                expect(b).toEqual(2);
+                expect(sum).toEqual(43);
+            }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/59');
+
+            it('gives number of arguments for static methods', function () {
+                expect(Regress.TestObj.new_from_file.length).toEqual(1);
+            });
+
+            it('skips over destroy-notify and user-data parameters', function () {
+                expect(Regress.TestObj.new_callback.length).toEqual(1);
+            });
+        });
+
+        it('virtual function', function () {
+            expect(o.do_matrix('meaningless string')).toEqual(42);
+        });
+
+        describe('wrong type for GObject', function () {
+            let wrongObject, wrongBoxed, subclassObject;
+            beforeEach(function () {
+                wrongObject = new Gio.SimpleAction();
+                wrongBoxed = new GLib.KeyFile();
+                subclassObject = new Regress.TestSubObj();
+            });
+
+            // Regress.func_obj_null_in expects a Regress.TestObj
+            it('function does not accept a GObject of the wrong type', function () {
+                expect(() => Regress.func_obj_null_in(wrongObject)).toThrow();
+            });
+
+            it('function does not accept a GBoxed instead of GObject', function () {
+                expect(() => Regress.func_obj_null_in(wrongBoxed)).toThrow();
+            });
+
+            it('function does not accept returned GObject of the wrong type', function () {
+                const wrongReturnedObject = Gio.File.new_for_path('/');
+                expect(() => Regress.func_obj_null_in(wrongReturnedObject)).toThrow();
+            });
+
+            it('function accepts GObject of subclass of expected type', function () {
+                expect(() => Regress.func_obj_null_in(subclassObject)).not.toThrow();
+            });
+
+            it('method cannot be called on a GObject of the wrong type', function () {
+                expect(() => Regress.TestObj.prototype.instance_method.call(wrongObject))
+                    .toThrow();
+            });
+
+            it('method cannot be called on a GBoxed', function () {
+                expect(() => Regress.TestObj.prototype.instance_method.call(wrongBoxed))
+                    .toThrow();
+            });
+
+            it('method can be called on a GObject of subclass of expected type', function () {
+                expect(() => Regress.TestObj.prototype.instance_method.call(subclassObject))
+                    .not.toThrow();
+            });
+        });
+
+        it('marshals a null object in', function () {
+            expect(() => Regress.func_obj_null_in(null)).not.toThrow();
+            expect(() => Regress.func_obj_nullable_in(null)).not.toThrow();
+        });
+
+        it('marshals a null object out', function () {
+            expect(Regress.TestObj.null_out()).toBeNull();
+        });
+
+        it('marshals a gpointer with a type annotation in', function () {
+            const o2 = new GObject.Object();
+            expect(() => o.not_nullable_typed_gpointer_in(o2)).not.toThrow();
+        });
+
+        it('marshals a gpointer with an element-type annotation in', function () {
+            expect(() => o.not_nullable_element_typed_gpointer_in([1, 2])).not.toThrow();
+        });
+
+        // This test is not meant to be normative; a GObject behaving like this is
+        // doing something unsupported. However, we have been handling this so far
+        // in a certain way, and we don't want to break user code because of badly
+        // behaved libraries. This test ensures that any change to the behaviour
+        // must be intentional.
+        it('resolves properties when they are shadowed by methods', function () {
+            expect(o.name_conflict).toEqual(42);
+            expect(o.name_conflict).not.toEqual(jasmine.any(Function));
+        });
+    });
+
+    it('marshals a fixed-size array of objects out', function () {
+        expect(Regress.test_array_fixed_out_objects()).toEqual([
+            jasmine.any(Regress.TestObj),
+            jasmine.any(Regress.TestObj),
+        ]);
+    });
+
+    describe('Inherited GObject', function () {
+        let subobj;
+        beforeEach(function () {
+            subobj = new Regress.TestSubObj({
+                int: 42,
+                float: Math.PI,
+                double: Math.E,
+            });
+        });
+
+        it('can read fields from a parent class', function () {
+            // see "can access fields with simple types" above
+            expect(subobj.some_int8).toEqual(subobj.int);
+            expect(subobj.some_float).toEqual(subobj.float);
+            expect(subobj.some_double).toEqual(subobj.double);
+        });
+
+        it('can be constructed from a static constructor', function () {
+            expect(Regress.TestSubObj.new).not.toThrow();
+        });
+
+        it('can call an instance method that overrides the parent class', function () {
+            expect(subobj.instance_method()).toEqual(0);
+        });
+    });
+
+    describe('Overridden properties on interfaces', function () {
+        it('set and get properly', function () {
+            const o = new Regress.TestSubObj();
+            o.number = 4;
+            expect(o.number).toEqual(4);
+        });
+
+        it('default properly', function () {
+            const o = new Regress.TestSubObj();
+            expect(o.number).toBeDefined();
+            expect(o.number).toEqual(0);
+        });
+
+        it('construct properly', function () {
+            const o = new Regress.TestSubObj({number: 4});
+            expect(o.number).toEqual(4);
+        });
+    });
+
+    describe('Fundamental type', function () {
+        it('constructs a subtype of a fundamental type', function () {
+            expect(() => new Regress.TestFundamentalSubObject('plop')).not.toThrow();
+        });
+
+        it('constructs a subtype of a hidden (no introspection data) fundamental type', function () {
+            expect(() => Regress.test_create_fundamental_hidden_class_instance()).not.toThrow();
+        });
+    });
+
+    it('callbacks', function () {
+        const callback = jasmine.createSpy('callback').and.returnValue(42);
+        expect(Regress.test_callback(callback)).toEqual(42);
+    });
+
+    it('null / undefined callback', function () {
+        expect(Regress.test_callback(null)).toEqual(0);
+        expect(() => Regress.test_callback(undefined)).toThrow();
+    });
+
+    it('callback called more than once', function () {
+        const callback = jasmine.createSpy('callback').and.returnValue(21);
+        expect(Regress.test_multi_callback(callback)).toEqual(42);
+        expect(callback).toHaveBeenCalledTimes(2);
+    });
+
+    it('null callback called more than once', function () {
+        expect(Regress.test_multi_callback(null)).toEqual(0);
+    });
+
+    it('array callbacks', function () {
+        const callback = jasmine.createSpy('callback').and.returnValue(7);
+        expect(Regress.test_array_callback(callback)).toEqual(14);
+        expect(callback).toHaveBeenCalledWith([-1, 0, 1, 2], ['one', 'two', 'three']);
+    });
+
+    it('null array callback', function () {
+        expect(() => Regress.test_array_callback(null)).toThrow();
+    });
+
+    xit('callback with inout array', function () {
+        const callback = jasmine.createSpy('callback').and.callFake(arr => arr.slice(1));
+        expect(Regress.test_array_inout_callback(callback)).toEqual(3);
+        expect(callback).toHaveBeenCalledWith([-2, -1, 0, 1, 2], [-1, 0, 1, 2]);
+    });  // assertion failed, "Use gjs_value_from_explicit_array() for arrays with length param""
+
+    ['simple', 'noptr'].forEach(type => {
+        it(`${type} callback`, function () {
+            const callback = jasmine.createSpy('callback');
+            Regress[`test_${type}_callback`](callback);
+            expect(callback).toHaveBeenCalled();
+        });
+
+        it('null simple callback', function () {
+            expect(() => Regress[`test_${type}_callback`](null)).not.toThrow();
+        });
+    });
+
+    it('callback with user data', function () {
+        const callback = jasmine.createSpy('callback').and.returnValue(7);
+        expect(Regress.test_callback_user_data(callback)).toEqual(7);
+        expect(callback).toHaveBeenCalled();
+    });
+
+    it('callback with transfer-full return value', function () {
+        const callback = jasmine.createSpy('callback')
+            .and.returnValue(Regress.TestObj.new_from_file('/enoent'));
+        Regress.test_callback_return_full(callback);
+        expect(callback).toHaveBeenCalled();
+    });
+
+    it('callback with destroy-notify', function () {
+        const callback1 = jasmine.createSpy('callback').and.returnValue(42);
+        const callback2 = jasmine.createSpy('callback').and.returnValue(58);
+        expect(Regress.test_callback_destroy_notify(callback1)).toEqual(42);
+        expect(callback1).toHaveBeenCalledTimes(1);
+        expect(Regress.test_callback_destroy_notify(callback2)).toEqual(58);
+        expect(callback2).toHaveBeenCalledTimes(1);
+        expect(Regress.test_callback_thaw_notifications()).toEqual(100);
+        expect(callback1).toHaveBeenCalledTimes(2);
+        expect(callback2).toHaveBeenCalledTimes(2);
+    });
+
+    xit('callback with destroy-notify and no user data', function () {
+        const callback1 = jasmine.createSpy('callback').and.returnValue(42);
+        const callback2 = jasmine.createSpy('callback').and.returnValue(58);
+        expect(Regress.test_callback_destroy_notify_no_user_data(callback1)).toEqual(42);
+        expect(callback1).toHaveBeenCalledTimes(1);
+        expect(Regress.test_callback_destroy_notify_no_user_data(callback2)).toEqual(58);
+        expect(callback2).toHaveBeenCalledTimes(1);
+        expect(Regress.test_callback_thaw_notifications()).toEqual(100);
+        expect(callback1).toHaveBeenCalledTimes(2);
+        expect(callback2).toHaveBeenCalledTimes(2);
+    }).pend('Callback with destroy-notify and no user data not currently supported');
+
+    it('async callback', function () {
+        Regress.test_callback_async(() => 44);
+        expect(Regress.test_callback_thaw_async()).toEqual(44);
+    });
+
+    it('Gio.AsyncReadyCallback', function (done) {
+        Regress.test_async_ready_callback((obj, res) => {
+            expect(obj).toBeNull();
+            expect(res).toEqual(jasmine.any(Gio.SimpleAsyncResult));
+            done();
+        });
+    });
+
+    it('instance method taking a callback', function () {
+        const o = new Regress.TestObj();
+        const callback = jasmine.createSpy('callback');
+        o.instance_method_callback(callback);
+        expect(callback).toHaveBeenCalled();
+    });
+
+    it('constructor taking a callback', function () {
+        const callback = jasmine.createSpy('callback').and.returnValue(42);
+        void Regress.TestObj.new_callback(callback);
+        expect(callback).toHaveBeenCalled();
+        expect(Regress.test_callback_thaw_notifications()).toEqual(42);
+        expect(callback).toHaveBeenCalledTimes(2);
+    });
+
+    it('hash table passed to callback', function () {
+        const hashtable = {
+            a: 1,
+            b: 2,
+            c: 3,
+        };
+        const callback = jasmine.createSpy('callback');
+        Regress.test_hash_table_callback(hashtable, callback);
+        expect(callback).toHaveBeenCalledWith(hashtable);
+    });
+
+    it('GError callback', function (done) {
+        Regress.test_gerror_callback(e => {
+            expect(e).toEqual(jasmine.any(Gio.IOErrorEnum));
+            expect(e.domain).toEqual(Gio.io_error_quark());
+            expect(e.code).toEqual(Gio.IOErrorEnum.NOT_SUPPORTED);
+            done();
+        });
+    });
+
+    it('null GError callback', function () {
+        const callback = jasmine.createSpy('callback');
+        Regress.test_null_gerror_callback(callback);
+        expect(callback).toHaveBeenCalledWith(null);
+    });
+
+    it('owned GError callback', function (done) {
+        Regress.test_owned_gerror_callback(e => {
+            expect(e).toEqual(jasmine.any(Gio.IOErrorEnum));
+            expect(e.domain).toEqual(Gio.io_error_quark());
+            expect(e.code).toEqual(Gio.IOErrorEnum.PERMISSION_DENIED);
+            done();
+        });
+    });
+
+    describe('Introspected interface', function () {
+        const Implementor = GObject.registerClass({
+            Implements: [Regress.TestInterface],
+            Properties: {
+                number: GObject.ParamSpec.override('number', Regress.TestInterface),
+            },
+        }, class Implementor extends GObject.Object {
+            get number() {
+                return 5;
+            }
+        });
+
+        it('correctly emits interface signals', function () {
+            const obj = new Implementor();
+            const handler = jasmine.createSpy('handler').and.callFake(() => {});
+            obj.connect('interface-signal', handler);
+            obj.emit_signal();
+            expect(handler).toHaveBeenCalled();
+        });
+    });
+
+    describe('GObject with nonstandard prefix', function () {
+        let o;
+        beforeEach(function () {
+            o = new Regress.TestWi8021x();
+        });
+
+        it('sets and gets properties', function () {
+            expect(o.testbool).toBeTruthy();
+            o.testbool = false;
+            expect(o.testbool).toBeFalsy();
+        });
+
+        it('constructs via a static constructor', function () {
+            expect(Regress.TestWi8021x.new()).toEqual(jasmine.any(Regress.TestWi8021x));
+        });
+
+        it('calls methods', function () {
+            expect(o.get_testbool()).toBeTruthy();
+            o.set_testbool(false);
+            expect(o.get_testbool()).toBeFalsy();
+        });
+
+        it('calls a static method', function () {
+            expect(Regress.TestWi8021x.static_method(21)).toEqual(42);
+        });
+    });
+
+    describe('GObject.InitiallyUnowned', function () {
+        it('constructs', function () {
+            expect(new Regress.TestFloating()).toEqual(jasmine.any(Regress.TestFloating));
+        });
+
+        it('constructs via a static constructor', function () {
+            expect(Regress.TestFloating.new()).toEqual(jasmine.any(Regress.TestFloating));
+        });
+    });
+
+    it('torture signature 0', function () {
+        const [y, z, q] = Regress.test_torture_signature_0(42, 'foo', 7);
+        expect(Math.floor(y)).toEqual(42);
+        expect(z).toEqual(84);
+        expect(q).toEqual(10);
+    });
+
+    it('torture signature 1 fail', function () {
+        expect(() => Regress.test_torture_signature_1(42, 'foo', 7)).toThrow();
+    });
+
+    it('torture signature 1 success', function () {
+        const [, y, z, q] = Regress.test_torture_signature_1(11, 'barbaz', 8);
+        expect(Math.floor(y)).toEqual(11);
+        expect(z).toEqual(22);
+        expect(q).toEqual(14);
+    });
+
+    it('torture signature 2', function () {
+        const [y, z, q] = Regress.test_torture_signature_2(42, () => 0, 'foo', 7);
+        expect(Math.floor(y)).toEqual(42);
+        expect(z).toEqual(84);
+        expect(q).toEqual(10);
+    });
+
+    describe('GValue boxing and unboxing', function () {
+        it('date in', function () {
+            const date = Regress.test_date_in_gvalue();
+            expect(date.get_year()).toEqual(1984);
+            expect(date.get_month()).toEqual(GLib.DateMonth.DECEMBER);
+            expect(date.get_day()).toEqual(5);
+        });
+
+        it('strv in', function () {
+            expect(Regress.test_strv_in_gvalue()).toEqual(['one', 'two', 'three']);
+        });
+
+        it('correctly converts a NULL strv in a GValue to an empty array', function () {
+            expect(Regress.test_null_strv_in_gvalue()).toEqual([]);
+        });
+    });
+
+    it("code coverage for documentation tests that don't do anything", function () {
+        expect(() => {
+            Regress.test_multiline_doc_comments();
+            Regress.test_nested_parameter(5);
+            Regress.test_versioning();
+        }).not.toThrow();
+    });
+
+    it('marshals an aliased type', function () {
+        // GLib.PtrArray is not introspectable, so neither is an alias of it
+        // Regress.introspectable_via_alias(new GLib.PtrArray());
+        expect(Regress.aliased_caller_alloc()).toEqual(jasmine.any(Regress.TestBoxed));
+    });
+
+    it('deals with a fixed-size array in a struct', function () {
+        const struct = new Regress.TestStructFixedArray();
+        struct.frob();
+        expect(struct.just_int).toEqual(7);
+        expect(struct.array).toEqual([42, 43, 44, 45, 46, 47, 48, 49, 50, 51]);
+    });
+
+    it('marshals a fixed-size int array as a gpointer', function () {
+        expect(() => Regress.has_parameter_named_attrs(0, Array(32).fill(42))).not.toThrow();
+    });
+
+    it('deals with a fixed-size and also zero-terminated array in a struct', function () {
+        const x = new Regress.LikeXklConfigItem();
+        x.set_name('foo');
+        expect(x.name).toEqual([...'foo'].map(c => c.codePointAt()).concat(Array(29).fill(0)));
+        x.set_name('*'.repeat(33));
+        expect(x.name).toEqual(Array(31).fill('*'.codePointAt()).concat([0]));
+    });
+
+    it('marshals a transfer-floating GLib.Variant', function () {
+        expect(Regress.get_variant().unpack()).toEqual(42);
+    });
+
+    describe('Flat array of structs', function () {
+        it('out parameter with transfer none', function () {
+            const expected = [111, 222, 333].map(some_int =>
+                jasmine.objectContaining({some_int}));
+            expect(Regress.test_array_struct_out_none()).toEqual(expected);
+        });
+
+        it('out parameter with transfer container', function () {
+            const expected = [11, 13, 17, 19, 23].map(some_int =>
+                jasmine.objectContaining({some_int}));
+            expect(Regress.test_array_struct_out_container()).toEqual(expected);
+        });
+
+        it('out parameter with transfer full', function () {
+            const expected = [2, 3, 5, 7].map(some_int =>
+                jasmine.objectContaining({some_int}));
+            expect(Regress.test_array_struct_out_full_fixed()).toEqual(expected);
+        });
+
+        xit('caller-allocated out parameter', function () {
+            // With caller-allocated array in, there's no way to supply the
+            // length. This happens in GLib.MainContext.query()
+            expect(Regress.test_array_struct_out_caller_alloc()).toEqual([]);
+        }).pend('Not supported');
+
+        xit('transfer-full in parameter', function () {
+            const array = [201, 202].map(some_int =>
+                new Regress.TestStructA({some_int}));
+            expect(() => Regress.test_array_struct_in_full(array)).not.toThrow();
+        }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/44');
+
+        xit('transfer-none in parameter', function () {
+            const array = [301, 302, 303].map(some_int =>
+                new Regress.TestStructA({some_int}));
+            expect(() => Regress.test_array_struct_in_none(array)).not.toThrow();
+        }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/44');
+    });
+});
diff --git a/installed-tests/js/testWarnLib.js b/installed-tests/js/testWarnLib.js
new file mode 100644
index 00000000..40971868
--- /dev/null
+++ b/installed-tests/js/testWarnLib.js
@@ -0,0 +1,38 @@
+// File with tests from the WarnLib-1.0.gir test suite from GI
+
+const {Gio, GObject, WarnLib} = imports.gi;
+
+describe('WarnLib', function () {
+    // Calling matches() on an unpaired error used to JSUnit.assert:
+    // https://bugzilla.gnome.org/show_bug.cgi?id=689482
+    it('bug 689482', function () {
+        try {
+            WarnLib.throw_unpaired();
+            fail();
+        } catch (e) {
+            expect(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)).toBeFalsy();
+        }
+    });
+
+    const WhateverImpl = GObject.registerClass({
+        Implements: [WarnLib.Whatever],
+    }, class WhateverImpl extends GObject.Object {
+        vfunc_do_moo(x) {
+            expect(x).toEqual(5);
+            this.mooCalled = true;
+        }
+
+        vfunc_do_boo(x) {
+            expect(x).toEqual(6);
+            this.booCalled = true;
+        }
+    });
+
+    it('calls vfuncs with unnamed parameters', function () {
+        const o = new WhateverImpl();
+        o.do_moo(5, null);
+        o.do_boo(6, null);
+        expect(o.mooCalled).toBeTruthy();  // spies don't work on vfuncs
+        expect(o.booCalled).toBeTruthy();
+    });
+});


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]