[gjs/wip/ptomato/tests: 3/3] WIP - minijasmine



commit ead4f646f9fd23df8e6324fb8acbdcf77685312d
Author: Philip Chimento <philip chimento gmail com>
Date:   Mon Dec 12 22:27:36 2016 -0800

    WIP - minijasmine

 Makefile-insttest.am                             |    2 +-
 Makefile-test.am                                 |   81 +-
 Makefile.am                                      |    2 +-
 installed-tests/js/jasmine.js                    | 3655 ++++++++++++++++++++++
 installed-tests/js/jsunit.gresources.xml         |    2 +
 installed-tests/js/minijasmine.js                |  113 +
 installed-tests/js/test0020importer.js           |   14 +-
 installed-tests/js/test0030basicBoxed.js         |   15 +-
 installed-tests/js/test0040mainloop.js           |   28 +-
 installed-tests/js/testByteArray.js              |  230 +-
 installed-tests/js/testClass.js                  |  199 +-
 installed-tests/js/testCoverage.js               | 2565 +++++++---------
 installed-tests/js/testEverythingBasic.js        | 1213 ++++----
 installed-tests/js/testEverythingEncapsulated.js |  335 ++-
 installed-tests/js/testExceptions.js             |  265 +-
 installed-tests/js/testFormat.js                 |  120 +-
 installed-tests/js/testFundamental.js            |   21 +-
 installed-tests/js/testGIMarshalling.js          |  899 ++++---
 installed-tests/js/testGLib.js                   |   66 +-
 installed-tests/js/testGObjectClass.js           |  286 +-
 installed-tests/js/testGObjectInterface.js       |  503 ++--
 installed-tests/js/testGTypeClass.js             |   93 +-
 installed-tests/js/testGettext.js                |   13 +-
 installed-tests/js/testImporter.js               |  219 +-
 installed-tests/js/testInterface.js              |  445 ++--
 installed-tests/js/testLang.js                   |  203 +-
 installed-tests/js/testLocale.js                 |   49 +-
 installed-tests/js/testMainloop.js               |  185 +-
 installed-tests/js/testMetaClass.js              |   93 +-
 installed-tests/js/testNamespace.js              |   13 +-
 installed-tests/js/testParamSpec.js              |  129 +-
 installed-tests/js/testSignals.js                |  238 +-
 installed-tests/js/testSystem.js                 |   28 +-
 installed-tests/js/testself.js                   |   55 +-
 installed-tests/minijasmine.cpp                  |  143 +
 35 files changed, 8043 insertions(+), 4477 deletions(-)
---
diff --git a/Makefile-insttest.am b/Makefile-insttest.am
index 0eec7b0..5cbe4dd 100644
--- a/Makefile-insttest.am
+++ b/Makefile-insttest.am
@@ -20,7 +20,7 @@ pkglib_LTLIBRARIES =
 
 if BUILDOPT_INSTALL_TESTS
 
-gjsinsttest_PROGRAMS += jsunit
+## gjsinsttest_PROGRAMS += jsunit
 gjsinsttest_DATA += $(TEST_INTROSPECTION_TYPELIBS)
 installedtestmeta_DATA += jsunit.test
 jstests_DATA += $(common_jstests_files)
diff --git a/Makefile-test.am b/Makefile-test.am
index 65acffc..ded14c7 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -95,7 +95,7 @@ CLEANFILES +=                                         \
 # as well as installed if --enable-installed-tests is given at configure time.
 # See Makefile-insttest.am for the build rules installing the tests.
 
-check_PROGRAMS += gjs-tests jsunit
+check_PROGRAMS += gjs-tests minijasmine
 
 gjs_tests_CPPFLAGS =                           \
        $(AM_CPPFLAGS)                          \
@@ -122,23 +122,20 @@ gjs_tests_DEPENDENCIES =                          \
        mock-cache-invalidation-after.gresource         \
        $(NULL)
 
-jsunit_CPPFLAGS =                              \
+minijasmine_SOURCES =                  \
+       installed-tests/minijasmine.cpp \
+       jsunit-resources.c              \
+       jsunit-resources.h              \
+       $(NULL)
+
+minijasmine_CPPFLAGS =                         \
        $(AM_CPPFLAGS)                          \
        $(GJS_CFLAGS)                           \
-       -DPKGLIBDIR=\"$(pkglibdir)\"            \
-       -DINSTTESTDIR=\"$(gjsinsttestdir)\"     \
        -I$(top_srcdir)                         \
+       -DINSTTESTDIR=\"$(gjsinsttestdir)\"     \
        $(NULL)
 
-jsunit_LDADD = $(GJS_LIBS) libgjs.la
-
-jsunit_LDFLAGS = -rpath $(pkglibdir)
-
-jsunit_SOURCES = \
-       installed-tests/gjs-unit.cpp    \
-       jsunit-resources.c              \
-       jsunit-resources.h              \
-       $(NULL)
+minijasmine_LDADD = $(GJS_LIBS) libgjs.la
 
 ### TEST GIRS ##########################################################
 
@@ -230,6 +227,33 @@ CLEANFILES += $(TEST_INTROSPECTION_GIRS) $(TEST_INTROSPECTION_TYPELIBS)
 ### JAVASCRIPT TESTS ###################################################
 
 common_jstests_files =                                         \
+       installed-tests/js/testTweener.js                       \
+       $(NULL)
+
+EXTRA_DIST +=                                          \
+       $(common_jstests_files)                         \
+       installed-tests/js/testCairo.js                 \
+       installed-tests/js/testGtk.js                   \
+       installed-tests/js/testGDBus.js                 \
+       $(NULL)
+
+### TEST EXECUTION #####################################################
+
+# GJS_PATH is empty here since we want to force the use of our own
+# resources. G_FILENAME_ENCODING ensures filenames are not UTF-8.
+AM_TESTS_ENVIRONMENT =                                 \
+       export TOP_SRCDIR="$(abs_top_srcdir)";          \
+       export TOP_BUILDDIR="$(abs_top_builddir)";      \
+       export GJS_USE_UNINSTALLED_FILES=1;             \
+       export GJS_TEST_SKIP="$(SKIPPED_TESTS)";        \
+       export GJS_PATH=;                               \
+       export GI_TYPELIB_PATH="$(builddir)";           \
+       export G_FILENAME_ENCODING=latin1;              \
+       $(XVFB_START)                                   \
+       $(NULL)
+
+simple_tests =                                                 \
+       test/testCommandLine.sh                                 \
        installed-tests/js/test0020importer.js                  \
        installed-tests/js/test0030basicBoxed.js                \
        installed-tests/js/test0040mainloop.js                  \
@@ -237,9 +261,9 @@ common_jstests_files =                                              \
        installed-tests/js/testByteArray.js                     \
        installed-tests/js/testClass.js                         \
        installed-tests/js/testCoverage.js                      \
-       installed-tests/js/testExceptions.js                    \
        installed-tests/js/testEverythingBasic.js               \
        installed-tests/js/testEverythingEncapsulated.js        \
+       installed-tests/js/testExceptions.js                    \
        installed-tests/js/testFormat.js                        \
        installed-tests/js/testFundamental.js                   \
        installed-tests/js/testGettext.js                       \
@@ -258,32 +282,7 @@ common_jstests_files =                                             \
        installed-tests/js/testParamSpec.js                     \
        installed-tests/js/testSignals.js                       \
        installed-tests/js/testSystem.js                        \
-       installed-tests/js/testTweener.js                       \
-       $(NULL)
-
-EXTRA_DIST +=                                          \
-       $(common_jstests_files)                         \
-       installed-tests/js/testCairo.js                 \
-       installed-tests/js/testGtk.js                   \
-       installed-tests/js/testGDBus.js                 \
        $(NULL)
-
-### TEST EXECUTION #####################################################
-
-# GJS_PATH is empty here since we want to force the use of our own
-# resources. G_FILENAME_ENCODING ensures filenames are not UTF-8.
-AM_TESTS_ENVIRONMENT =                                 \
-       export TOP_SRCDIR="$(abs_top_srcdir)";          \
-       export TOP_BUILDDIR="$(abs_top_builddir)";      \
-       export GJS_USE_UNINSTALLED_FILES=1;             \
-       export GJS_TEST_SKIP="$(SKIPPED_TESTS)";        \
-       export GJS_PATH=;                               \
-       export GI_TYPELIB_PATH="$(builddir)";           \
-       export G_FILENAME_ENCODING=latin1;              \
-       $(XVFB_START)                                   \
-       $(NULL)
-
-simple_tests = test/testCommandLine.sh
 EXTRA_DIST += $(simple_tests)
 TESTS += $(simple_tests)
 
@@ -297,6 +296,10 @@ LOG_COMPILER = $(top_srcdir)/test/run-test
 AM_LOG_FLAGS =
 endif !DBUS_TESTS
 
+TEST_EXTENSIONS = .js
+JS_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
+JS_LOG_COMPILER = $(top_builddir)/minijasmine
+
 if CODE_COVERAGE_ENABLED
 AM_TESTS_ENVIRONMENT +=                                                \
        export GJS_UNIT_COVERAGE_OUTPUT=lcov;                           \
diff --git a/Makefile.am b/Makefile.am
index 66c82d5..a632a51 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -14,7 +14,7 @@ CLEANFILES =
 EXTRA_DIST =
 check_PROGRAMS =
 check_LTLIBRARIES =
-TESTS = $(check_PROGRAMS)
+TESTS =
 INTROSPECTION_GIRS =
 ## ACLOCAL_AMFLAGS can be removed for Automake 1.13
 ACLOCAL_AMFLAGS = -I m4
diff --git a/installed-tests/js/jasmine.js b/installed-tests/js/jasmine.js
new file mode 100644
index 0000000..7cab7e0
--- /dev/null
+++ b/installed-tests/js/jasmine.js
@@ -0,0 +1,3655 @@
+/*
+Copyright (c) 2008-2016 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+var getJasmineRequireObj = (function (jasmineGlobal) {
+  var jasmineRequire;
+
+  if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined') {
+    if (typeof global !== 'undefined') {
+      jasmineGlobal = global;
+    } else {
+      jasmineGlobal = {};
+    }
+    jasmineRequire = exports;
+  } else {
+    if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === 
'[object GjsGlobal]') {
+      jasmineGlobal = window;
+    }
+    jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
+  }
+
+  function getJasmineRequire() {
+    return jasmineRequire;
+  }
+
+  getJasmineRequire().core = function(jRequire) {
+    var j$ = {};
+
+    jRequire.base(j$, jasmineGlobal);
+    j$.util = jRequire.util();
+    j$.errors = jRequire.errors();
+    j$.formatErrorMsg = jRequire.formatErrorMsg();
+    j$.Any = jRequire.Any(j$);
+    j$.Anything = jRequire.Anything(j$);
+    j$.CallTracker = jRequire.CallTracker(j$);
+    j$.MockDate = jRequire.MockDate();
+    j$.Clock = jRequire.Clock();
+    j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+    j$.Env = jRequire.Env(j$);
+    j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+    j$.Expectation = jRequire.Expectation();
+    j$.buildExpectationResult = jRequire.buildExpectationResult();
+    j$.JsApiReporter = jRequire.JsApiReporter();
+    j$.matchersUtil = jRequire.matchersUtil(j$);
+    j$.ObjectContaining = jRequire.ObjectContaining(j$);
+    j$.ArrayContaining = jRequire.ArrayContaining(j$);
+    j$.pp = jRequire.pp(j$);
+    j$.QueueRunner = jRequire.QueueRunner(j$);
+    j$.ReportDispatcher = jRequire.ReportDispatcher();
+    j$.Spec = jRequire.Spec(j$);
+    j$.SpyRegistry = jRequire.SpyRegistry(j$);
+    j$.SpyStrategy = jRequire.SpyStrategy(j$);
+    j$.StringMatching = jRequire.StringMatching(j$);
+    j$.Suite = jRequire.Suite(j$);
+    j$.Timer = jRequire.Timer();
+    j$.TreeProcessor = jRequire.TreeProcessor();
+    j$.version = jRequire.version();
+    j$.Order = jRequire.Order();
+
+    j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+    return j$;
+  };
+
+  return getJasmineRequire;
+})(this);
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+  var availableMatchers = [
+      'toBe',
+      'toBeCloseTo',
+      'toBeDefined',
+      'toBeFalsy',
+      'toBeGreaterThan',
+      'toBeGreaterThanOrEqual',
+      'toBeLessThanOrEqual',
+      'toBeLessThan',
+      'toBeNaN',
+      'toBeNull',
+      'toBeTruthy',
+      'toBeUndefined',
+      'toContain',
+      'toEqual',
+      'toHaveBeenCalled',
+      'toHaveBeenCalledWith',
+      'toHaveBeenCalledTimes',
+      'toMatch',
+      'toThrow',
+      'toThrowError'
+    ],
+    matchers = {};
+
+  for (var i = 0; i < availableMatchers.length; i++) {
+    var name = availableMatchers[i];
+    matchers[name] = jRequire[name](j$);
+  }
+
+  return matchers;
+};
+
+getJasmineRequireObj().base = function(j$, jasmineGlobal) {
+  j$.unimplementedMethod_ = function() {
+    throw new Error('unimplemented method');
+  };
+
+  j$.MAX_PRETTY_PRINT_DEPTH = 40;
+  j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
+  j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+  j$.getGlobal = function() {
+    return jasmineGlobal;
+  };
+
+  j$.getEnv = function(options) {
+    var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+    //jasmine. singletons in here (setTimeout blah blah).
+    return env;
+  };
+
+  j$.isArray_ = function(value) {
+    return j$.isA_('Array', value);
+  };
+
+  j$.isString_ = function(value) {
+    return j$.isA_('String', value);
+  };
+
+  j$.isNumber_ = function(value) {
+    return j$.isA_('Number', value);
+  };
+
+  j$.isFunction_ = function(value) {
+    return j$.isA_('Function', value);
+  };
+
+  j$.isA_ = function(typeName, value) {
+    return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+  };
+
+  j$.isDomNode = function(obj) {
+    return obj.nodeType > 0;
+  };
+
+  j$.fnNameFor = function(func) {
+    if (func.name) {
+      return func.name;
+    }
+
+    var matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/);
+    return matches ? matches[1] : '<anonymous>';
+  };
+
+  j$.any = function(clazz) {
+    return new j$.Any(clazz);
+  };
+
+  j$.anything = function() {
+    return new j$.Anything();
+  };
+
+  j$.objectContaining = function(sample) {
+    return new j$.ObjectContaining(sample);
+  };
+
+  j$.stringMatching = function(expected) {
+    return new j$.StringMatching(expected);
+  };
+
+  j$.arrayContaining = function(sample) {
+    return new j$.ArrayContaining(sample);
+  };
+
+  j$.createSpy = function(name, originalFn) {
+
+    var spyStrategy = new j$.SpyStrategy({
+        name: name,
+        fn: originalFn,
+        getSpy: function() { return spy; }
+      }),
+      callTracker = new j$.CallTracker(),
+      spy = function() {
+        var callData = {
+          object: this,
+          args: Array.prototype.slice.apply(arguments)
+        };
+
+        callTracker.track(callData);
+        var returnValue = spyStrategy.exec.apply(this, arguments);
+        callData.returnValue = returnValue;
+
+        return returnValue;
+      };
+
+    for (var prop in originalFn) {
+      if (prop === 'and' || prop === 'calls') {
+        throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object 
being spied upon');
+      }
+
+      spy[prop] = originalFn[prop];
+    }
+
+    spy.and = spyStrategy;
+    spy.calls = callTracker;
+
+    return spy;
+  };
+
+  j$.isSpy = function(putativeSpy) {
+    if (!putativeSpy) {
+      return false;
+    }
+    return putativeSpy.and instanceof j$.SpyStrategy &&
+      putativeSpy.calls instanceof j$.CallTracker;
+  };
+
+  j$.createSpyObj = function(baseName, methodNames) {
+    if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) {
+      methodNames = baseName;
+      baseName = 'unknown';
+    }
+
+    if (!j$.isArray_(methodNames) || methodNames.length === 0) {
+      throw 'createSpyObj requires a non-empty array of method names to create spies for';
+    }
+    var obj = {};
+    for (var i = 0; i < methodNames.length; i++) {
+      obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+    }
+    return obj;
+  };
+};
+
+getJasmineRequireObj().util = function() {
+
+  var util = {};
+
+  util.inherit = function(childClass, parentClass) {
+    var Subclass = function() {
+    };
+    Subclass.prototype = parentClass.prototype;
+    childClass.prototype = new Subclass();
+  };
+
+  util.htmlEscape = function(str) {
+    if (!str) {
+      return str;
+    }
+    return str.replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;');
+  };
+
+  util.argsToArray = function(args) {
+    var arrayOfArgs = [];
+    for (var i = 0; i < args.length; i++) {
+      arrayOfArgs.push(args[i]);
+    }
+    return arrayOfArgs;
+  };
+
+  util.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  util.arrayContains = function(array, search) {
+    var i = array.length;
+    while (i--) {
+      if (array[i] === search) {
+        return true;
+      }
+    }
+    return false;
+  };
+
+  util.clone = function(obj) {
+    if (Object.prototype.toString.apply(obj) === '[object Array]') {
+      return obj.slice();
+    }
+
+    var cloned = {};
+    for (var prop in obj) {
+      if (obj.hasOwnProperty(prop)) {
+        cloned[prop] = obj[prop];
+      }
+    }
+
+    return cloned;
+  };
+
+  return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+  function Spec(attrs) {
+    this.expectationFactory = attrs.expectationFactory;
+    this.resultCallback = attrs.resultCallback || function() {};
+    this.id = attrs.id;
+    this.description = attrs.description || '';
+    this.queueableFn = attrs.queueableFn;
+    this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; };
+    this.userContext = attrs.userContext || function() { return {}; };
+    this.onStart = attrs.onStart || function() {};
+    this.getSpecName = attrs.getSpecName || function() { return ''; };
+    this.expectationResultFactory = attrs.expectationResultFactory || function() { };
+    this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
+    this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+    this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+    if (!this.queueableFn.fn) {
+      this.pend();
+    }
+
+    this.result = {
+      id: this.id,
+      description: this.description,
+      fullName: this.getFullName(),
+      failedExpectations: [],
+      passedExpectations: [],
+      pendingReason: ''
+    };
+  }
+
+  Spec.prototype.addExpectationResult = function(passed, data, isError) {
+    var expectationResult = this.expectationResultFactory(data);
+    if (passed) {
+      this.result.passedExpectations.push(expectationResult);
+    } else {
+      this.result.failedExpectations.push(expectationResult);
+
+      if (this.throwOnExpectationFailure && !isError) {
+        throw new j$.errors.ExpectationFailed();
+      }
+    }
+  };
+
+  Spec.prototype.expect = function(actual) {
+    return this.expectationFactory(actual, this);
+  };
+
+  Spec.prototype.execute = function(onComplete, enabled) {
+    var self = this;
+
+    this.onStart(this);
+
+    if (!this.isExecutable() || this.markedPending || enabled === false) {
+      complete(enabled);
+      return;
+    }
+
+    var fns = this.beforeAndAfterFns();
+    var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters);
+
+    this.queueRunnerFactory({
+      queueableFns: allFns,
+      onException: function() { self.onException.apply(self, arguments); },
+      onComplete: complete,
+      userContext: this.userContext()
+    });
+
+    function complete(enabledAgain) {
+      self.result.status = self.status(enabledAgain);
+      self.resultCallback(self.result);
+
+      if (onComplete) {
+        onComplete();
+      }
+    }
+  };
+
+  Spec.prototype.onException = function onException(e) {
+    if (Spec.isPendingSpecException(e)) {
+      this.pend(extractCustomPendingMessage(e));
+      return;
+    }
+
+    if (e instanceof j$.errors.ExpectationFailed) {
+      return;
+    }
+
+    this.addExpectationResult(false, {
+      matcherName: '',
+      passed: false,
+      expected: '',
+      actual: '',
+      error: e
+    }, true);
+  };
+
+  Spec.prototype.disable = function() {
+    this.disabled = true;
+  };
+
+  Spec.prototype.pend = function(message) {
+    this.markedPending = true;
+    if (message) {
+      this.result.pendingReason = message;
+    }
+  };
+
+  Spec.prototype.getResult = function() {
+    this.result.status = this.status();
+    return this.result;
+  };
+
+  Spec.prototype.status = function(enabled) {
+    if (this.disabled || enabled === false) {
+      return 'disabled';
+    }
+
+    if (this.markedPending) {
+      return 'pending';
+    }
+
+    if (this.result.failedExpectations.length > 0) {
+      return 'failed';
+    } else {
+      return 'passed';
+    }
+  };
+
+  Spec.prototype.isExecutable = function() {
+    return !this.disabled;
+  };
+
+  Spec.prototype.getFullName = function() {
+    return this.getSpecName(this);
+  };
+
+  var extractCustomPendingMessage = function(e) {
+    var fullMessage = e.toString(),
+        boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
+        boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length;
+
+    return fullMessage.substr(boilerplateEnd);
+  };
+
+  Spec.pendingSpecExceptionMessage = '=> marked Pending';
+
+  Spec.isPendingSpecException = function(e) {
+    return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1);
+  };
+
+  return Spec;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+  exports.Spec = jasmineRequire.Spec;
+}
+
+/*jshint bitwise: false*/
+
+getJasmineRequireObj().Order = function() {
+  function Order(options) {
+    this.random = 'random' in options ? options.random : true;
+    var seed = this.seed = options.seed || generateSeed();
+    this.sort = this.random ? randomOrder : naturalOrder;
+
+    function naturalOrder(items) {
+      return items;
+    }
+
+    function randomOrder(items) {
+      var copy = items.slice();
+      copy.sort(function(a, b) {
+        return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id);
+      });
+      return copy;
+    }
+
+    function generateSeed() {
+      return String(Math.random()).slice(-5);
+    }
+
+    // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function
+    // used to get a different output when the key changes slighly.
+    // We use your return to sort the children randomly in a consistent way when
+    // used in conjunction with a seed
+
+    function jenkinsHash(key) {
+      var hash, i;
+      for(hash = i = 0; i < key.length; ++i) {
+        hash += key.charCodeAt(i);
+        hash += (hash << 10);
+        hash ^= (hash >> 6);
+      }
+      hash += (hash << 3);
+      hash ^= (hash >> 11);
+      hash += (hash << 15);
+      return hash;
+    }
+
+  }
+
+  return Order;
+};
+
+getJasmineRequireObj().Env = function(j$) {
+  function Env(options) {
+    options = options || {};
+
+    var self = this;
+    var global = options.global || j$.getGlobal();
+
+    var totalSpecsDefined = 0;
+
+    var catchExceptions = true;
+
+    var realSetTimeout = j$.getGlobal().setTimeout;
+    var realClearTimeout = j$.getGlobal().clearTimeout;
+    this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new 
j$.MockDate(global));
+
+    var runnableResources = {};
+
+    var currentSpec = null;
+    var currentlyExecutingSuites = [];
+    var currentDeclarationSuite = null;
+    var throwOnExpectationFailure = false;
+    var random = false;
+    var seed = null;
+
+    var currentSuite = function() {
+      return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
+    };
+
+    var currentRunnable = function() {
+      return currentSpec || currentSuite();
+    };
+
+    var reporter = new j$.ReportDispatcher([
+      'jasmineStarted',
+      'jasmineDone',
+      'suiteStarted',
+      'suiteDone',
+      'specStarted',
+      'specDone'
+    ]);
+
+    this.specFilter = function() {
+      return true;
+    };
+
+    this.addCustomEqualityTester = function(tester) {
+      if(!currentRunnable()) {
+        throw new Error('Custom Equalities must be added in a before function or a spec');
+      }
+      runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
+    };
+
+    this.addMatchers = function(matchersToAdd) {
+      if(!currentRunnable()) {
+        throw new Error('Matchers must be added in a before function or a spec');
+      }
+      var customMatchers = runnableResources[currentRunnable().id].customMatchers;
+      for (var matcherName in matchersToAdd) {
+        customMatchers[matcherName] = matchersToAdd[matcherName];
+      }
+    };
+
+    j$.Expectation.addCoreMatchers(j$.matchers);
+
+    var nextSpecId = 0;
+    var getNextSpecId = function() {
+      return 'spec' + nextSpecId++;
+    };
+
+    var nextSuiteId = 0;
+    var getNextSuiteId = function() {
+      return 'suite' + nextSuiteId++;
+    };
+
+    var expectationFactory = function(actual, spec) {
+      return j$.Expectation.Factory({
+        util: j$.matchersUtil,
+        customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+        customMatchers: runnableResources[spec.id].customMatchers,
+        actual: actual,
+        addExpectationResult: addExpectationResult
+      });
+
+      function addExpectationResult(passed, result) {
+        return spec.addExpectationResult(passed, result);
+      }
+    };
+
+    var defaultResourcesForRunnable = function(id, parentRunnableId) {
+      var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
+
+      if(runnableResources[parentRunnableId]){
+        resources.customEqualityTesters = 
j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
+        resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
+      }
+
+      runnableResources[id] = resources;
+    };
+
+    var clearResourcesForRunnable = function(id) {
+        spyRegistry.clearSpies();
+        delete runnableResources[id];
+    };
+
+    var beforeAndAfterFns = function(suite) {
+      return function() {
+        var befores = [],
+          afters = [];
+
+        while(suite) {
+          befores = befores.concat(suite.beforeFns);
+          afters = afters.concat(suite.afterFns);
+
+          suite = suite.parentSuite;
+        }
+
+        return {
+          befores: befores.reverse(),
+          afters: afters
+        };
+      };
+    };
+
+    var getSpecName = function(spec, suite) {
+      var fullName = [spec.description],
+          suiteFullName = suite.getFullName();
+
+      if (suiteFullName !== '') {
+        fullName.unshift(suiteFullName);
+      }
+      return fullName.join(' ');
+    };
+
+    // TODO: we may just be able to pass in the fn instead of wrapping here
+    var buildExpectationResult = j$.buildExpectationResult,
+        exceptionFormatter = new j$.ExceptionFormatter(),
+        expectationResultFactory = function(attrs) {
+          attrs.messageFormatter = exceptionFormatter.message;
+          attrs.stackFormatter = exceptionFormatter.stack;
+
+          return buildExpectationResult(attrs);
+        };
+
+    // TODO: fix this naming, and here's where the value comes in
+    this.catchExceptions = function(value) {
+      catchExceptions = !!value;
+      return catchExceptions;
+    };
+
+    this.catchingExceptions = function() {
+      return catchExceptions;
+    };
+
+    var maximumSpecCallbackDepth = 20;
+    var currentSpecCallbackDepth = 0;
+
+    function clearStack(fn) {
+      currentSpecCallbackDepth++;
+      if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) {
+        currentSpecCallbackDepth = 0;
+        realSetTimeout(fn, 0);
+      } else {
+        fn();
+      }
+    }
+
+    var catchException = function(e) {
+      return j$.Spec.isPendingSpecException(e) || catchExceptions;
+    };
+
+    this.throwOnExpectationFailure = function(value) {
+      throwOnExpectationFailure = !!value;
+    };
+
+    this.throwingExpectationFailures = function() {
+      return throwOnExpectationFailure;
+    };
+
+    this.randomizeTests = function(value) {
+      random = !!value;
+    };
+
+    this.randomTests = function() {
+      return random;
+    };
+
+    this.seed = function(value) {
+      if (value) {
+        seed = value;
+      }
+      return seed;
+    };
+
+    var queueRunnerFactory = function(options) {
+      options.catchException = catchException;
+      options.clearStack = options.clearStack || clearStack;
+      options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+      options.fail = self.fail;
+
+      new j$.QueueRunner(options).execute();
+    };
+
+    var topSuite = new j$.Suite({
+      env: this,
+      id: getNextSuiteId(),
+      description: 'Jasmine__TopLevel__Suite',
+      expectationFactory: expectationFactory,
+      expectationResultFactory: expectationResultFactory
+    });
+    defaultResourcesForRunnable(topSuite.id);
+    currentDeclarationSuite = topSuite;
+
+    this.topSuite = function() {
+      return topSuite;
+    };
+
+    this.execute = function(runnablesToRun) {
+      if(!runnablesToRun) {
+        if (focusedRunnables.length) {
+          runnablesToRun = focusedRunnables;
+        } else {
+          runnablesToRun = [topSuite.id];
+        }
+      }
+
+      var order = new j$.Order({
+        random: random,
+        seed: seed
+      });
+
+      var processor = new j$.TreeProcessor({
+        tree: topSuite,
+        runnableIds: runnablesToRun,
+        queueRunnerFactory: queueRunnerFactory,
+        nodeStart: function(suite) {
+          currentlyExecutingSuites.push(suite);
+          defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
+          reporter.suiteStarted(suite.result);
+        },
+        nodeComplete: function(suite, result) {
+          if (!suite.disabled) {
+            clearResourcesForRunnable(suite.id);
+          }
+          currentlyExecutingSuites.pop();
+          reporter.suiteDone(result);
+        },
+        orderChildren: function(node) {
+          return order.sort(node.children);
+        }
+      });
+
+      if(!processor.processTree().valid) {
+        throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times');
+      }
+
+      reporter.jasmineStarted({
+        totalSpecsDefined: totalSpecsDefined
+      });
+
+      currentlyExecutingSuites.push(topSuite);
+
+      processor.execute(function() {
+        clearResourcesForRunnable(topSuite.id);
+        currentlyExecutingSuites.pop();
+
+        reporter.jasmineDone({
+          order: order,
+          failedExpectations: topSuite.result.failedExpectations
+        });
+      });
+    };
+
+    this.addReporter = function(reporterToAdd) {
+      reporter.addReporter(reporterToAdd);
+    };
+
+    this.provideFallbackReporter = function(reporterToAdd) {
+      reporter.provideFallbackReporter(reporterToAdd);
+    };
+
+    this.clearReporters = function() {
+      reporter.clearReporters();
+    };
+
+    var spyRegistry = new j$.SpyRegistry({currentSpies: function() {
+      if(!currentRunnable()) {
+        throw new Error('Spies must be created in a before function or a spec');
+      }
+      return runnableResources[currentRunnable().id].spies;
+    }});
+
+    this.allowRespy = function(allow){
+      spyRegistry.allowRespy(allow);
+    };
+
+    this.spyOn = function() {
+      return spyRegistry.spyOn.apply(spyRegistry, arguments);
+    };
+
+    var suiteFactory = function(description) {
+      var suite = new j$.Suite({
+        env: self,
+        id: getNextSuiteId(),
+        description: description,
+        parentSuite: currentDeclarationSuite,
+        expectationFactory: expectationFactory,
+        expectationResultFactory: expectationResultFactory,
+        throwOnExpectationFailure: throwOnExpectationFailure
+      });
+
+      return suite;
+    };
+
+    this.describe = function(description, specDefinitions) {
+      var suite = suiteFactory(description);
+      if (specDefinitions.length > 0) {
+        throw new Error('describe does not expect any arguments');
+      }
+      if (currentDeclarationSuite.markedPending) {
+        suite.pend();
+      }
+      addSpecsToSuite(suite, specDefinitions);
+      return suite;
+    };
+
+    this.xdescribe = function(description, specDefinitions) {
+      var suite = suiteFactory(description);
+      suite.pend();
+      addSpecsToSuite(suite, specDefinitions);
+      return suite;
+    };
+
+    var focusedRunnables = [];
+
+    this.fdescribe = function(description, specDefinitions) {
+      var suite = suiteFactory(description);
+      suite.isFocused = true;
+
+      focusedRunnables.push(suite.id);
+      unfocusAncestor();
+      addSpecsToSuite(suite, specDefinitions);
+
+      return suite;
+    };
+
+    function addSpecsToSuite(suite, specDefinitions) {
+      var parentSuite = currentDeclarationSuite;
+      parentSuite.addChild(suite);
+      currentDeclarationSuite = suite;
+
+      var declarationError = null;
+      try {
+        specDefinitions.call(suite);
+      } catch (e) {
+        declarationError = e;
+      }
+
+      if (declarationError) {
+        self.it('encountered a declaration exception', function() {
+          throw declarationError;
+        });
+      }
+
+      currentDeclarationSuite = parentSuite;
+    }
+
+    function findFocusedAncestor(suite) {
+      while (suite) {
+        if (suite.isFocused) {
+          return suite.id;
+        }
+        suite = suite.parentSuite;
+      }
+
+      return null;
+    }
+
+    function unfocusAncestor() {
+      var focusedAncestor = findFocusedAncestor(currentDeclarationSuite);
+      if (focusedAncestor) {
+        for (var i = 0; i < focusedRunnables.length; i++) {
+          if (focusedRunnables[i] === focusedAncestor) {
+            focusedRunnables.splice(i, 1);
+            break;
+          }
+        }
+      }
+    }
+
+    var specFactory = function(description, fn, suite, timeout) {
+      totalSpecsDefined++;
+      var spec = new j$.Spec({
+        id: getNextSpecId(),
+        beforeAndAfterFns: beforeAndAfterFns(suite),
+        expectationFactory: expectationFactory,
+        resultCallback: specResultCallback,
+        getSpecName: function(spec) {
+          return getSpecName(spec, suite);
+        },
+        onStart: specStarted,
+        description: description,
+        expectationResultFactory: expectationResultFactory,
+        queueRunnerFactory: queueRunnerFactory,
+        userContext: function() { return suite.clonedSharedUserContext(); },
+        queueableFn: {
+          fn: fn,
+          timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+        },
+        throwOnExpectationFailure: throwOnExpectationFailure
+      });
+
+      if (!self.specFilter(spec)) {
+        spec.disable();
+      }
+
+      return spec;
+
+      function specResultCallback(result) {
+        clearResourcesForRunnable(spec.id);
+        currentSpec = null;
+        reporter.specDone(result);
+      }
+
+      function specStarted(spec) {
+        currentSpec = spec;
+        defaultResourcesForRunnable(spec.id, suite.id);
+        reporter.specStarted(spec.result);
+      }
+    };
+
+    this.it = function(description, fn, timeout) {
+      var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+      if (currentDeclarationSuite.markedPending) {
+        spec.pend();
+      }
+      currentDeclarationSuite.addChild(spec);
+      return spec;
+    };
+
+    this.xit = function() {
+      var spec = this.it.apply(this, arguments);
+      spec.pend('Temporarily disabled with xit');
+      return spec;
+    };
+
+    this.fit = function(description, fn, timeout){
+      var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+      currentDeclarationSuite.addChild(spec);
+      focusedRunnables.push(spec.id);
+      unfocusAncestor();
+      return spec;
+    };
+
+    this.expect = function(actual) {
+      if (!currentRunnable()) {
+        throw new Error('\'expect\' was used when there was no current spec, this could be because an 
asynchronous test timed out');
+      }
+
+      return currentRunnable().expect(actual);
+    };
+
+    this.beforeEach = function(beforeEachFunction, timeout) {
+      currentDeclarationSuite.beforeEach({
+        fn: beforeEachFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.beforeAll = function(beforeAllFunction, timeout) {
+      currentDeclarationSuite.beforeAll({
+        fn: beforeAllFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.afterEach = function(afterEachFunction, timeout) {
+      currentDeclarationSuite.afterEach({
+        fn: afterEachFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.afterAll = function(afterAllFunction, timeout) {
+      currentDeclarationSuite.afterAll({
+        fn: afterAllFunction,
+        timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+      });
+    };
+
+    this.pending = function(message) {
+      var fullMessage = j$.Spec.pendingSpecExceptionMessage;
+      if(message) {
+        fullMessage += message;
+      }
+      throw fullMessage;
+    };
+
+    this.fail = function(error) {
+      var message = 'Failed';
+      if (error) {
+        message += ': ';
+        message += error.message || error;
+      }
+
+      currentRunnable().addExpectationResult(false, {
+        matcherName: '',
+        passed: false,
+        expected: '',
+        actual: '',
+        message: message,
+        error: error && error.message ? error : null
+      });
+    };
+  }
+
+  return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function() {
+
+  var noopTimer = {
+    start: function(){},
+    elapsed: function(){ return 0; }
+  };
+
+  function JsApiReporter(options) {
+    var timer = options.timer || noopTimer,
+        status = 'loaded';
+
+    this.started = false;
+    this.finished = false;
+    this.runDetails = {};
+
+    this.jasmineStarted = function() {
+      this.started = true;
+      status = 'started';
+      timer.start();
+    };
+
+    var executionTime;
+
+    this.jasmineDone = function(runDetails) {
+      this.finished = true;
+      this.runDetails = runDetails;
+      executionTime = timer.elapsed();
+      status = 'done';
+    };
+
+    this.status = function() {
+      return status;
+    };
+
+    var suites = [],
+      suites_hash = {};
+
+    this.suiteStarted = function(result) {
+      suites_hash[result.id] = result;
+    };
+
+    this.suiteDone = function(result) {
+      storeSuite(result);
+    };
+
+    this.suiteResults = function(index, length) {
+      return suites.slice(index, index + length);
+    };
+
+    function storeSuite(result) {
+      suites.push(result);
+      suites_hash[result.id] = result;
+    }
+
+    this.suites = function() {
+      return suites_hash;
+    };
+
+    var specs = [];
+
+    this.specDone = function(result) {
+      specs.push(result);
+    };
+
+    this.specResults = function(index, length) {
+      return specs.slice(index, index + length);
+    };
+
+    this.specs = function() {
+      return specs;
+    };
+
+    this.executionTime = function() {
+      return executionTime;
+    };
+
+  }
+
+  return JsApiReporter;
+};
+
+getJasmineRequireObj().CallTracker = function(j$) {
+
+  function CallTracker() {
+    var calls = [];
+    var opts = {};
+
+    function argCloner(context) {
+      var clonedArgs = [];
+      var argsAsArray = j$.util.argsToArray(context.args);
+      for(var i = 0; i < argsAsArray.length; i++) {
+        if(Object.prototype.toString.apply(argsAsArray[i]).match(/^\[object/)) {
+          clonedArgs.push(j$.util.clone(argsAsArray[i]));
+        } else {
+          clonedArgs.push(argsAsArray[i]);
+        }
+      }
+      context.args = clonedArgs;
+    }
+
+    this.track = function(context) {
+      if(opts.cloneArgs) {
+        argCloner(context);
+      }
+      calls.push(context);
+    };
+
+    this.any = function() {
+      return !!calls.length;
+    };
+
+    this.count = function() {
+      return calls.length;
+    };
+
+    this.argsFor = function(index) {
+      var call = calls[index];
+      return call ? call.args : [];
+    };
+
+    this.all = function() {
+      return calls;
+    };
+
+    this.allArgs = function() {
+      var callArgs = [];
+      for(var i = 0; i < calls.length; i++){
+        callArgs.push(calls[i].args);
+      }
+
+      return callArgs;
+    };
+
+    this.first = function() {
+      return calls[0];
+    };
+
+    this.mostRecent = function() {
+      return calls[calls.length - 1];
+    };
+
+    this.reset = function() {
+      calls = [];
+    };
+
+    this.saveArgumentsByValue = function() {
+      opts.cloneArgs = true;
+    };
+
+  }
+
+  return CallTracker;
+};
+
+getJasmineRequireObj().Clock = function() {
+  function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
+    var self = this,
+      realTimingFunctions = {
+        setTimeout: global.setTimeout,
+        clearTimeout: global.clearTimeout,
+        setInterval: global.setInterval,
+        clearInterval: global.clearInterval
+      },
+      fakeTimingFunctions = {
+        setTimeout: setTimeout,
+        clearTimeout: clearTimeout,
+        setInterval: setInterval,
+        clearInterval: clearInterval
+      },
+      installed = false,
+      delayedFunctionScheduler,
+      timer;
+
+
+    self.install = function() {
+      if(!originalTimingFunctionsIntact()) {
+        throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the 
clock already installed?');
+      }
+      replace(global, fakeTimingFunctions);
+      timer = fakeTimingFunctions;
+      delayedFunctionScheduler = delayedFunctionSchedulerFactory();
+      installed = true;
+
+      return self;
+    };
+
+    self.uninstall = function() {
+      delayedFunctionScheduler = null;
+      mockDate.uninstall();
+      replace(global, realTimingFunctions);
+
+      timer = realTimingFunctions;
+      installed = false;
+    };
+
+    self.withMock = function(closure) {
+      this.install();
+      try {
+        closure();
+      } finally {
+        this.uninstall();
+      }
+    };
+
+    self.mockDate = function(initialDate) {
+      mockDate.install(initialDate);
+    };
+
+    self.setTimeout = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
+        }
+        return timer.setTimeout(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
+    };
+
+    self.setInterval = function(fn, delay, params) {
+      if (legacyIE()) {
+        if (arguments.length > 2) {
+          throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
+        }
+        return timer.setInterval(fn, delay);
+      }
+      return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
+    };
+
+    self.clearTimeout = function(id) {
+      return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+    };
+
+    self.clearInterval = function(id) {
+      return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+    };
+
+    self.tick = function(millis) {
+      if (installed) {
+        delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); });
+      } else {
+        throw new Error('Mock clock is not installed, use jasmine.clock().install()');
+      }
+    };
+
+    return self;
+
+    function originalTimingFunctionsIntact() {
+      return global.setTimeout === realTimingFunctions.setTimeout &&
+        global.clearTimeout === realTimingFunctions.clearTimeout &&
+        global.setInterval === realTimingFunctions.setInterval &&
+        global.clearInterval === realTimingFunctions.clearInterval;
+    }
+
+    function legacyIE() {
+      //if these methods are polyfilled, apply will be present
+      return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
+    }
+
+    function replace(dest, source) {
+      for (var prop in source) {
+        dest[prop] = source[prop];
+      }
+    }
+
+    function setTimeout(fn, delay) {
+      return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
+    }
+
+    function clearTimeout(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function setInterval(fn, interval) {
+      return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+    }
+
+    function clearInterval(id) {
+      return delayedFunctionScheduler.removeFunctionWithId(id);
+    }
+
+    function argSlice(argsObj, n) {
+      return Array.prototype.slice.call(argsObj, n);
+    }
+  }
+
+  return Clock;
+};
+
+getJasmineRequireObj().DelayedFunctionScheduler = function() {
+  function DelayedFunctionScheduler() {
+    var self = this;
+    var scheduledLookup = [];
+    var scheduledFunctions = {};
+    var currentTime = 0;
+    var delayedFnCount = 0;
+
+    self.tick = function(millis, tickDate) {
+      millis = millis || 0;
+      var endTime = currentTime + millis;
+
+      runScheduledFunctions(endTime, tickDate);
+      currentTime = endTime;
+    };
+
+    self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
+      var f;
+      if (typeof(funcToCall) === 'string') {
+        /* jshint evil: true */
+        f = function() { return eval(funcToCall); };
+        /* jshint evil: false */
+      } else {
+        f = funcToCall;
+      }
+
+      millis = millis || 0;
+      timeoutKey = timeoutKey || ++delayedFnCount;
+      runAtMillis = runAtMillis || (currentTime + millis);
+
+      var funcToSchedule = {
+        runAtMillis: runAtMillis,
+        funcToCall: f,
+        recurring: recurring,
+        params: params,
+        timeoutKey: timeoutKey,
+        millis: millis
+      };
+
+      if (runAtMillis in scheduledFunctions) {
+        scheduledFunctions[runAtMillis].push(funcToSchedule);
+      } else {
+        scheduledFunctions[runAtMillis] = [funcToSchedule];
+        scheduledLookup.push(runAtMillis);
+        scheduledLookup.sort(function (a, b) {
+          return a - b;
+        });
+      }
+
+      return timeoutKey;
+    };
+
+    self.removeFunctionWithId = function(timeoutKey) {
+      for (var runAtMillis in scheduledFunctions) {
+        var funcs = scheduledFunctions[runAtMillis];
+        var i = indexOfFirstToPass(funcs, function (func) {
+          return func.timeoutKey === timeoutKey;
+        });
+
+        if (i > -1) {
+          if (funcs.length === 1) {
+            delete scheduledFunctions[runAtMillis];
+            deleteFromLookup(runAtMillis);
+          } else {
+            funcs.splice(i, 1);
+          }
+
+          // intervals get rescheduled when executed, so there's never more
+          // than a single scheduled function with a given timeoutKey
+          break;
+        }
+      }
+    };
+
+    return self;
+
+    function indexOfFirstToPass(array, testFn) {
+      var index = -1;
+
+      for (var i = 0; i < array.length; ++i) {
+        if (testFn(array[i])) {
+          index = i;
+          break;
+        }
+      }
+
+      return index;
+    }
+
+    function deleteFromLookup(key) {
+      var value = Number(key);
+      var i = indexOfFirstToPass(scheduledLookup, function (millis) {
+        return millis === value;
+      });
+
+      if (i > -1) {
+        scheduledLookup.splice(i, 1);
+      }
+    }
+
+    function reschedule(scheduledFn) {
+      self.scheduleFunction(scheduledFn.funcToCall,
+        scheduledFn.millis,
+        scheduledFn.params,
+        true,
+        scheduledFn.timeoutKey,
+        scheduledFn.runAtMillis + scheduledFn.millis);
+    }
+
+    function forEachFunction(funcsToRun, callback) {
+      for (var i = 0; i < funcsToRun.length; ++i) {
+        callback(funcsToRun[i]);
+      }
+    }
+
+    function runScheduledFunctions(endTime, tickDate) {
+      tickDate = tickDate || function() {};
+      if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+        tickDate(endTime - currentTime);
+        return;
+      }
+
+      do {
+        var newCurrentTime = scheduledLookup.shift();
+        tickDate(newCurrentTime - currentTime);
+
+        currentTime = newCurrentTime;
+
+        var funcsToRun = scheduledFunctions[currentTime];
+        delete scheduledFunctions[currentTime];
+
+        forEachFunction(funcsToRun, function(funcToRun) {
+          if (funcToRun.recurring) {
+            reschedule(funcToRun);
+          }
+        });
+
+        forEachFunction(funcsToRun, function(funcToRun) {
+          funcToRun.funcToCall.apply(null, funcToRun.params || []);
+        });
+      } while (scheduledLookup.length > 0 &&
+              // checking first if we're out of time prevents setTimeout(0)
+              // scheduled in a funcToRun from forcing an extra iteration
+                 currentTime !== endTime  &&
+                 scheduledLookup[0] <= endTime);
+
+      // ran out of functions to call, but still time left on the clock
+      if (currentTime !== endTime) {
+        tickDate(endTime - currentTime);
+      }
+    }
+  }
+
+  return DelayedFunctionScheduler;
+};
+
+getJasmineRequireObj().ExceptionFormatter = function() {
+  function ExceptionFormatter() {
+    this.message = function(error) {
+      var message = '';
+
+      if (error.name && error.message) {
+        message += error.name + ': ' + error.message;
+      } else {
+        message += error.toString() + ' thrown';
+      }
+
+      if (error.fileName || error.sourceURL) {
+        message += ' in ' + (error.fileName || error.sourceURL);
+      }
+
+      if (error.line || error.lineNumber) {
+        message += ' (line ' + (error.line || error.lineNumber) + ')';
+      }
+
+      return message;
+    };
+
+    this.stack = function(error) {
+      return error ? error.stack : null;
+    };
+  }
+
+  return ExceptionFormatter;
+};
+
+getJasmineRequireObj().Expectation = function() {
+
+  function Expectation(options) {
+    this.util = options.util || { buildFailureMessage: function() {} };
+    this.customEqualityTesters = options.customEqualityTesters || [];
+    this.actual = options.actual;
+    this.addExpectationResult = options.addExpectationResult || function(){};
+    this.isNot = options.isNot;
+
+    var customMatchers = options.customMatchers || {};
+    for (var matcherName in customMatchers) {
+      this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]);
+    }
+  }
+
+  Expectation.prototype.wrapCompare = function(name, matcherFactory) {
+    return function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+        expected = args.slice(0),
+        message = '';
+
+      args.unshift(this.actual);
+
+      var matcher = matcherFactory(this.util, this.customEqualityTesters),
+          matcherCompare = matcher.compare;
+
+      function defaultNegativeCompare() {
+        var result = matcher.compare.apply(null, args);
+        result.pass = !result.pass;
+        return result;
+      }
+
+      if (this.isNot) {
+        matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
+      }
+
+      var result = matcherCompare.apply(null, args);
+
+      if (!result.pass) {
+        if (!result.message) {
+          args.unshift(this.isNot);
+          args.unshift(name);
+          message = this.util.buildFailureMessage.apply(null, args);
+        } else {
+          if (Object.prototype.toString.apply(result.message) === '[object Function]') {
+            message = result.message();
+          } else {
+            message = result.message;
+          }
+        }
+      }
+
+      if (expected.length == 1) {
+        expected = expected[0];
+      }
+
+      // TODO: how many of these params are needed?
+      this.addExpectationResult(
+        result.pass,
+        {
+          matcherName: name,
+          passed: result.pass,
+          message: message,
+          actual: this.actual,
+          expected: expected // TODO: this may need to be arrayified/sliced
+        }
+      );
+    };
+  };
+
+  Expectation.addCoreMatchers = function(matchers) {
+    var prototype = Expectation.prototype;
+    for (var matcherName in matchers) {
+      var matcher = matchers[matcherName];
+      prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
+    }
+  };
+
+  Expectation.Factory = function(options) {
+    options = options || {};
+
+    var expect = new Expectation(options);
+
+    // TODO: this would be nice as its own Object - NegativeExpectation
+    // TODO: copy instead of mutate options
+    options.isNot = true;
+    expect.not = new Expectation(options);
+
+    return expect;
+  };
+
+  return Expectation;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function() {
+  function buildExpectationResult(options) {
+    var messageFormatter = options.messageFormatter || function() {},
+      stackFormatter = options.stackFormatter || function() {};
+
+    var result = {
+      matcherName: options.matcherName,
+      message: message(),
+      stack: stack(),
+      passed: options.passed
+    };
+
+    if(!result.passed) {
+      result.expected = options.expected;
+      result.actual = options.actual;
+    }
+
+    return result;
+
+    function message() {
+      if (options.passed) {
+        return 'Passed.';
+      } else if (options.message) {
+        return options.message;
+      } else if (options.error) {
+        return messageFormatter(options.error);
+      }
+      return '';
+    }
+
+    function stack() {
+      if (options.passed) {
+        return '';
+      }
+
+      var error = options.error;
+      if (!error) {
+        try {
+          throw new Error(message());
+        } catch (e) {
+          error = e;
+        }
+      }
+      return stackFormatter(error);
+    }
+  }
+
+  return buildExpectationResult;
+};
+
+getJasmineRequireObj().MockDate = function() {
+  function MockDate(global) {
+    var self = this;
+    var currentTime = 0;
+
+    if (!global || !global.Date) {
+      self.install = function() {};
+      self.tick = function() {};
+      self.uninstall = function() {};
+      return self;
+    }
+
+    var GlobalDate = global.Date;
+
+    self.install = function(mockDate) {
+      if (mockDate instanceof GlobalDate) {
+        currentTime = mockDate.getTime();
+      } else {
+        currentTime = new GlobalDate().getTime();
+      }
+
+      global.Date = FakeDate;
+    };
+
+    self.tick = function(millis) {
+      millis = millis || 0;
+      currentTime = currentTime + millis;
+    };
+
+    self.uninstall = function() {
+      currentTime = 0;
+      global.Date = GlobalDate;
+    };
+
+    createDateProperties();
+
+    return self;
+
+    function FakeDate() {
+      switch(arguments.length) {
+        case 0:
+          return new GlobalDate(currentTime);
+        case 1:
+          return new GlobalDate(arguments[0]);
+        case 2:
+          return new GlobalDate(arguments[0], arguments[1]);
+        case 3:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2]);
+        case 4:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
+        case 5:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+                                arguments[4]);
+        case 6:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+                                arguments[4], arguments[5]);
+        default:
+          return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+                                arguments[4], arguments[5], arguments[6]);
+      }
+    }
+
+    function createDateProperties() {
+      FakeDate.prototype = GlobalDate.prototype;
+
+      FakeDate.now = function() {
+        if (GlobalDate.now) {
+          return currentTime;
+        } else {
+          throw new Error('Browser does not support Date.now()');
+        }
+      };
+
+      FakeDate.toSource = GlobalDate.toSource;
+      FakeDate.toString = GlobalDate.toString;
+      FakeDate.parse = GlobalDate.parse;
+      FakeDate.UTC = GlobalDate.UTC;
+    }
+       }
+
+  return MockDate;
+};
+
+getJasmineRequireObj().pp = function(j$) {
+
+  function PrettyPrinter() {
+    this.ppNestLevel_ = 0;
+    this.seen = [];
+  }
+
+  PrettyPrinter.prototype.format = function(value) {
+    this.ppNestLevel_++;
+    try {
+      if (j$.util.isUndefined(value)) {
+        this.emitScalar('undefined');
+      } else if (value === null) {
+        this.emitScalar('null');
+      } else if (value === 0 && 1/value === -Infinity) {
+        this.emitScalar('-0');
+      } else if (value === j$.getGlobal()) {
+        this.emitScalar('<global>');
+      } else if (value.jasmineToString) {
+        this.emitScalar(value.jasmineToString());
+      } else if (typeof value === 'string') {
+        this.emitString(value);
+      } else if (j$.isSpy(value)) {
+        this.emitScalar('spy on ' + value.and.identity());
+      } else if (value instanceof RegExp) {
+        this.emitScalar(value.toString());
+      } else if (typeof value === 'function') {
+        this.emitScalar('Function');
+      } else if (typeof value.nodeType === 'number') {
+        this.emitScalar('HTMLNode');
+      } else if (value instanceof Date) {
+        this.emitScalar('Date(' + value + ')');
+      } else if (value.toString && typeof value === 'object' && !(value instanceof Array) && value.toString 
!== Object.prototype.toString) {
+        this.emitScalar(value.toString());
+      } else if (j$.util.arrayContains(this.seen, value)) {
+        this.emitScalar('<circular reference: ' + (j$.isArray_(value) ? 'Array' : 'Object') + '>');
+      } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
+        this.seen.push(value);
+        if (j$.isArray_(value)) {
+          this.emitArray(value);
+        } else {
+          this.emitObject(value);
+        }
+        this.seen.pop();
+      } else {
+        this.emitScalar(value.toString());
+      }
+    } finally {
+      this.ppNestLevel_--;
+    }
+  };
+
+  PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+    for (var property in obj) {
+      if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; }
+      fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
+          obj.__lookupGetter__(property) !== null) : false);
+    }
+  };
+
+  PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
+  PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
+
+  function StringPrettyPrinter() {
+    PrettyPrinter.call(this);
+
+    this.string = '';
+  }
+
+  j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+
+  StringPrettyPrinter.prototype.emitScalar = function(value) {
+    this.append(value);
+  };
+
+  StringPrettyPrinter.prototype.emitString = function(value) {
+    this.append('\'' + value + '\'');
+  };
+
+  StringPrettyPrinter.prototype.emitArray = function(array) {
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      this.append('Array');
+      return;
+    }
+    var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+    this.append('[ ');
+    for (var i = 0; i < length; i++) {
+      if (i > 0) {
+        this.append(', ');
+      }
+      this.format(array[i]);
+    }
+    if(array.length > length){
+      this.append(', ...');
+    }
+
+    var self = this;
+    var first = array.length === 0;
+    this.iterateObject(array, function(property, isGetter) {
+      if (property.match(/^\d+$/)) {
+        return;
+      }
+
+      if (first) {
+        first = false;
+      } else {
+        self.append(', ');
+      }
+
+      self.formatProperty(array, property, isGetter);
+    });
+
+    this.append(' ]');
+  };
+
+  StringPrettyPrinter.prototype.emitObject = function(obj) {
+    var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null';
+    this.append(constructorName);
+
+    if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+      return;
+    }
+
+    var self = this;
+    this.append('({ ');
+    var first = true;
+
+    this.iterateObject(obj, function(property, isGetter) {
+      if (first) {
+        first = false;
+      } else {
+        self.append(', ');
+      }
+
+      self.formatProperty(obj, property, isGetter);
+    });
+
+    this.append(' })');
+  };
+
+  StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) {
+      this.append(property);
+      this.append(': ');
+      if (isGetter) {
+        this.append('<getter>');
+      } else {
+        this.format(obj[property]);
+      }
+  };
+
+  StringPrettyPrinter.prototype.append = function(value) {
+    this.string += value;
+  };
+
+  return function(value) {
+    var stringPrettyPrinter = new StringPrettyPrinter();
+    stringPrettyPrinter.format(value);
+    return stringPrettyPrinter.string;
+  };
+};
+
+getJasmineRequireObj().QueueRunner = function(j$) {
+
+  function once(fn) {
+    var called = false;
+    return function() {
+      if (!called) {
+        called = true;
+        fn();
+      }
+      return null;
+    };
+  }
+
+  function QueueRunner(attrs) {
+    this.queueableFns = attrs.queueableFns || [];
+    this.onComplete = attrs.onComplete || function() {};
+    this.clearStack = attrs.clearStack || function(fn) {fn();};
+    this.onException = attrs.onException || function() {};
+    this.catchException = attrs.catchException || function() { return true; };
+    this.userContext = attrs.userContext || {};
+    this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+    this.fail = attrs.fail || function() {};
+  }
+
+  QueueRunner.prototype.execute = function() {
+    this.run(this.queueableFns, 0);
+  };
+
+  QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
+    var length = queueableFns.length,
+      self = this,
+      iterativeIndex;
+
+
+    for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
+      var queueableFn = queueableFns[iterativeIndex];
+      if (queueableFn.fn.length > 0) {
+        attemptAsync(queueableFn);
+        return;
+      } else {
+        attemptSync(queueableFn);
+      }
+    }
+
+    var runnerDone = iterativeIndex >= length;
+
+    if (runnerDone) {
+      this.clearStack(this.onComplete);
+    }
+
+    function attemptSync(queueableFn) {
+      try {
+        queueableFn.fn.call(self.userContext);
+      } catch (e) {
+        handleException(e, queueableFn);
+      }
+    }
+
+    function attemptAsync(queueableFn) {
+      var clearTimeout = function () {
+          Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
+        },
+        next = once(function () {
+          clearTimeout(timeoutId);
+          self.run(queueableFns, iterativeIndex + 1);
+        }),
+        timeoutId;
+
+      next.fail = function() {
+        self.fail.apply(null, arguments);
+        next();
+      };
+
+      if (queueableFn.timeout) {
+        timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() {
+          var error = new Error('Timeout - Async callback was not invoked within timeout specified by 
jasmine.DEFAULT_TIMEOUT_INTERVAL.');
+          onException(error);
+          next();
+        }, queueableFn.timeout()]]);
+      }
+
+      try {
+        queueableFn.fn.call(self.userContext, next);
+      } catch (e) {
+        handleException(e, queueableFn);
+        next();
+      }
+    }
+
+    function onException(e) {
+      self.onException(e);
+    }
+
+    function handleException(e, queueableFn) {
+      onException(e);
+      if (!self.catchException(e)) {
+        //TODO: set a var when we catch an exception and
+        //use a finally block to close the loop in a nice way..
+        throw e;
+      }
+    }
+  };
+
+  return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function() {
+  function ReportDispatcher(methods) {
+
+    var dispatchedMethods = methods || [];
+
+    for (var i = 0; i < dispatchedMethods.length; i++) {
+      var method = dispatchedMethods[i];
+      this[method] = (function(m) {
+        return function() {
+          dispatch(m, arguments);
+        };
+      }(method));
+    }
+
+    var reporters = [];
+    var fallbackReporter = null;
+
+    this.addReporter = function(reporter) {
+      reporters.push(reporter);
+    };
+
+    this.provideFallbackReporter = function(reporter) {
+      fallbackReporter = reporter;
+    };
+
+    this.clearReporters = function() {
+      reporters = [];
+    };
+
+    return this;
+
+    function dispatch(method, args) {
+      if (reporters.length === 0 && fallbackReporter !== null) {
+          reporters.push(fallbackReporter);
+      }
+      for (var i = 0; i < reporters.length; i++) {
+        var reporter = reporters[i];
+        if (reporter[method]) {
+          reporter[method].apply(reporter, args);
+        }
+      }
+    }
+  }
+
+  return ReportDispatcher;
+};
+
+
+getJasmineRequireObj().SpyRegistry = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<spyOn>', 'spyOn(<object>, <methodName>)');
+
+  function SpyRegistry(options) {
+    options = options || {};
+    var currentSpies = options.currentSpies || function() { return []; };
+
+    this.allowRespy = function(allow){
+      this.respy = allow;
+    };
+
+    this.spyOn = function(obj, methodName) {
+
+      if (j$.util.isUndefined(obj)) {
+        throw new Error(getErrorMsg('could not find an object to spy upon for ' + methodName + '()'));
+      }
+
+      if (j$.util.isUndefined(methodName)) {
+        throw new Error(getErrorMsg('No method name supplied'));
+      }
+
+      if (j$.util.isUndefined(obj[methodName])) {
+        throw new Error(getErrorMsg(methodName + '() method does not exist'));
+      }
+
+      if (obj[methodName] && j$.isSpy(obj[methodName])  ) {
+        if ( !!this.respy ){
+          return obj[methodName];
+        }else {
+          throw new Error(getErrorMsg(methodName + ' has already been spied upon'));
+        }
+      }
+
+      var descriptor;
+      try {
+        descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
+      } catch(e) {
+        // IE 8 doesn't support `definePropery` on non-DOM nodes
+      }
+
+      if (descriptor && !(descriptor.writable || descriptor.set)) {
+        throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter'));
+      }
+
+      var originalMethod = obj[methodName],
+        spiedMethod = j$.createSpy(methodName, originalMethod),
+        restoreStrategy;
+
+      if (Object.prototype.hasOwnProperty.call(obj, methodName)) {
+        restoreStrategy = function() {
+          obj[methodName] = originalMethod;
+        };
+      } else {
+        restoreStrategy = function() {
+          if (!delete obj[methodName]) {
+            obj[methodName] = originalMethod;
+          }
+        };
+      }
+
+      currentSpies().push({
+        restoreObjectToOriginalState: restoreStrategy
+      });
+
+      obj[methodName] = spiedMethod;
+
+      return spiedMethod;
+    };
+
+    this.clearSpies = function() {
+      var spies = currentSpies();
+      for (var i = spies.length - 1; i >= 0; i--) {
+        var spyEntry = spies[i];
+        spyEntry.restoreObjectToOriginalState();
+      }
+    };
+  }
+
+  return SpyRegistry;
+};
+
+getJasmineRequireObj().SpyStrategy = function(j$) {
+
+  function SpyStrategy(options) {
+    options = options || {};
+
+    var identity = options.name || 'unknown',
+        originalFn = options.fn || function() {},
+        getSpy = options.getSpy || function() {},
+        plan = function() {};
+
+    this.identity = function() {
+      return identity;
+    };
+
+    this.exec = function() {
+      return plan.apply(this, arguments);
+    };
+
+    this.callThrough = function() {
+      plan = originalFn;
+      return getSpy();
+    };
+
+    this.returnValue = function(value) {
+      plan = function() {
+        return value;
+      };
+      return getSpy();
+    };
+
+    this.returnValues = function() {
+      var values = Array.prototype.slice.call(arguments);
+      plan = function () {
+        return values.shift();
+      };
+      return getSpy();
+    };
+
+    this.throwError = function(something) {
+      var error = (something instanceof Error) ? something : new Error(something);
+      plan = function() {
+        throw error;
+      };
+      return getSpy();
+    };
+
+    this.callFake = function(fn) {
+      if(!j$.isFunction_(fn)) {
+        throw new Error('Argument passed to callFake should be a function, got ' + fn);
+      }
+      plan = fn;
+      return getSpy();
+    };
+
+    this.stub = function(fn) {
+      plan = function() {};
+      return getSpy();
+    };
+  }
+
+  return SpyStrategy;
+};
+
+getJasmineRequireObj().Suite = function(j$) {
+  function Suite(attrs) {
+    this.env = attrs.env;
+    this.id = attrs.id;
+    this.parentSuite = attrs.parentSuite;
+    this.description = attrs.description;
+    this.expectationFactory = attrs.expectationFactory;
+    this.expectationResultFactory = attrs.expectationResultFactory;
+    this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+    this.beforeFns = [];
+    this.afterFns = [];
+    this.beforeAllFns = [];
+    this.afterAllFns = [];
+    this.disabled = false;
+
+    this.children = [];
+
+    this.result = {
+      id: this.id,
+      description: this.description,
+      fullName: this.getFullName(),
+      failedExpectations: []
+    };
+  }
+
+  Suite.prototype.expect = function(actual) {
+    return this.expectationFactory(actual, this);
+  };
+
+  Suite.prototype.getFullName = function() {
+    var fullName = [];
+    for (var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite) {
+      if (parentSuite.parentSuite) {
+        fullName.unshift(parentSuite.description);
+      }
+    }
+    return fullName.join(' ');
+  };
+
+  Suite.prototype.disable = function() {
+    this.disabled = true;
+  };
+
+  Suite.prototype.pend = function(message) {
+    this.markedPending = true;
+  };
+
+  Suite.prototype.beforeEach = function(fn) {
+    this.beforeFns.unshift(fn);
+  };
+
+  Suite.prototype.beforeAll = function(fn) {
+    this.beforeAllFns.push(fn);
+  };
+
+  Suite.prototype.afterEach = function(fn) {
+    this.afterFns.unshift(fn);
+  };
+
+  Suite.prototype.afterAll = function(fn) {
+    this.afterAllFns.push(fn);
+  };
+
+  Suite.prototype.addChild = function(child) {
+    this.children.push(child);
+  };
+
+  Suite.prototype.status = function() {
+    if (this.disabled) {
+      return 'disabled';
+    }
+
+    if (this.markedPending) {
+      return 'pending';
+    }
+
+    if (this.result.failedExpectations.length > 0) {
+      return 'failed';
+    } else {
+      return 'finished';
+    }
+  };
+
+  Suite.prototype.isExecutable = function() {
+    return !this.disabled;
+  };
+
+  Suite.prototype.canBeReentered = function() {
+    return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
+  };
+
+  Suite.prototype.getResult = function() {
+    this.result.status = this.status();
+    return this.result;
+  };
+
+  Suite.prototype.sharedUserContext = function() {
+    if (!this.sharedContext) {
+      this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
+    }
+
+    return this.sharedContext;
+  };
+
+  Suite.prototype.clonedSharedUserContext = function() {
+    return clone(this.sharedUserContext());
+  };
+
+  Suite.prototype.onException = function() {
+    if (arguments[0] instanceof j$.errors.ExpectationFailed) {
+      return;
+    }
+
+    if(isAfterAll(this.children)) {
+      var data = {
+        matcherName: '',
+        passed: false,
+        expected: '',
+        actual: '',
+        error: arguments[0]
+      };
+      this.result.failedExpectations.push(this.expectationResultFactory(data));
+    } else {
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        child.onException.apply(child, arguments);
+      }
+    }
+  };
+
+  Suite.prototype.addExpectationResult = function () {
+    if(isAfterAll(this.children) && isFailure(arguments)){
+      var data = arguments[1];
+      this.result.failedExpectations.push(this.expectationResultFactory(data));
+      if(this.throwOnExpectationFailure) {
+        throw new j$.errors.ExpectationFailed();
+      }
+    } else {
+      for (var i = 0; i < this.children.length; i++) {
+        var child = this.children[i];
+        try {
+          child.addExpectationResult.apply(child, arguments);
+        } catch(e) {
+          // keep going
+        }
+      }
+    }
+  };
+
+  function isAfterAll(children) {
+    return children && children[0].result.status;
+  }
+
+  function isFailure(args) {
+    return !args[0];
+  }
+
+  function clone(obj) {
+    var clonedObj = {};
+    for (var prop in obj) {
+      if (obj.hasOwnProperty(prop)) {
+        clonedObj[prop] = obj[prop];
+      }
+    }
+
+    return clonedObj;
+  }
+
+  return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+  exports.Suite = jasmineRequire.Suite;
+}
+
+getJasmineRequireObj().Timer = function() {
+  var defaultNow = (function(Date) {
+    return function() { return new Date().getTime(); };
+  })(Date);
+
+  function Timer(options) {
+    options = options || {};
+
+    var now = options.now || defaultNow,
+      startTime;
+
+    this.start = function() {
+      startTime = now();
+    };
+
+    this.elapsed = function() {
+      return now() - startTime;
+    };
+  }
+
+  return Timer;
+};
+
+getJasmineRequireObj().TreeProcessor = function() {
+  function TreeProcessor(attrs) {
+    var tree = attrs.tree,
+        runnableIds = attrs.runnableIds,
+        queueRunnerFactory = attrs.queueRunnerFactory,
+        nodeStart = attrs.nodeStart || function() {},
+        nodeComplete = attrs.nodeComplete || function() {},
+        orderChildren = attrs.orderChildren || function(node) { return node.children; },
+        stats = { valid: true },
+        processed = false,
+        defaultMin = Infinity,
+        defaultMax = 1 - Infinity;
+
+    this.processTree = function() {
+      processNode(tree, false);
+      processed = true;
+      return stats;
+    };
+
+    this.execute = function(done) {
+      if (!processed) {
+        this.processTree();
+      }
+
+      if (!stats.valid) {
+        throw 'invalid order';
+      }
+
+      var childFns = wrapChildren(tree, 0);
+
+      queueRunnerFactory({
+        queueableFns: childFns,
+        userContext: tree.sharedUserContext(),
+        onException: function() {
+          tree.onException.apply(tree, arguments);
+        },
+        onComplete: done
+      });
+    };
+
+    function runnableIndex(id) {
+      for (var i = 0; i < runnableIds.length; i++) {
+        if (runnableIds[i] === id) {
+          return i;
+        }
+      }
+    }
+
+    function processNode(node, parentEnabled) {
+      var executableIndex = runnableIndex(node.id);
+
+      if (executableIndex !== undefined) {
+        parentEnabled = true;
+      }
+
+      parentEnabled = parentEnabled && node.isExecutable();
+
+      if (!node.children) {
+        stats[node.id] = {
+          executable: parentEnabled && node.isExecutable(),
+          segments: [{
+            index: 0,
+            owner: node,
+            nodes: [node],
+            min: startingMin(executableIndex),
+            max: startingMax(executableIndex)
+          }]
+        };
+      } else {
+        var hasExecutableChild = false;
+
+        var orderedChildren = orderChildren(node);
+
+        for (var i = 0; i < orderedChildren.length; i++) {
+          var child = orderedChildren[i];
+
+          processNode(child, parentEnabled);
+
+          if (!stats.valid) {
+            return;
+          }
+
+          var childStats = stats[child.id];
+
+          hasExecutableChild = hasExecutableChild || childStats.executable;
+        }
+
+        stats[node.id] = {
+          executable: hasExecutableChild
+        };
+
+        segmentChildren(node, orderedChildren, stats[node.id], executableIndex);
+
+        if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
+          stats = { valid: false };
+        }
+      }
+    }
+
+    function startingMin(executableIndex) {
+      return executableIndex === undefined ? defaultMin : executableIndex;
+    }
+
+    function startingMax(executableIndex) {
+      return executableIndex === undefined ? defaultMax : executableIndex;
+    }
+
+    function segmentChildren(node, orderedChildren, nodeStats, executableIndex) {
+      var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: 
startingMax(executableIndex) },
+          result = [currentSegment],
+          lastMax = defaultMax,
+          orderedChildSegments = orderChildSegments(orderedChildren);
+
+      function isSegmentBoundary(minIndex) {
+        return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
+      }
+
+      for (var i = 0; i < orderedChildSegments.length; i++) {
+        var childSegment = orderedChildSegments[i],
+          maxIndex = childSegment.max,
+          minIndex = childSegment.min;
+
+        if (isSegmentBoundary(minIndex)) {
+          currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax};
+          result.push(currentSegment);
+        }
+
+        currentSegment.nodes.push(childSegment);
+        currentSegment.min = Math.min(currentSegment.min, minIndex);
+        currentSegment.max = Math.max(currentSegment.max, maxIndex);
+        lastMax = maxIndex;
+      }
+
+      nodeStats.segments = result;
+    }
+
+    function orderChildSegments(children) {
+      var specifiedOrder = [],
+          unspecifiedOrder = [];
+
+      for (var i = 0; i < children.length; i++) {
+        var child = children[i],
+            segments = stats[child.id].segments;
+
+        for (var j = 0; j < segments.length; j++) {
+          var seg = segments[j];
+
+          if (seg.min === defaultMin) {
+            unspecifiedOrder.push(seg);
+          } else {
+            specifiedOrder.push(seg);
+          }
+        }
+      }
+
+      specifiedOrder.sort(function(a, b) {
+        return a.min - b.min;
+      });
+
+      return specifiedOrder.concat(unspecifiedOrder);
+    }
+
+    function executeNode(node, segmentNumber) {
+      if (node.children) {
+        return {
+          fn: function(done) {
+            nodeStart(node);
+
+            queueRunnerFactory({
+              onComplete: function() {
+                nodeComplete(node, node.getResult());
+                done();
+              },
+              queueableFns: wrapChildren(node, segmentNumber),
+              userContext: node.sharedUserContext(),
+              onException: function() {
+                node.onException.apply(node, arguments);
+              }
+            });
+          }
+        };
+      } else {
+        return {
+          fn: function(done) { node.execute(done, stats[node.id].executable); }
+        };
+      }
+    }
+
+    function wrapChildren(node, segmentNumber) {
+      var result = [],
+          segmentChildren = stats[node.id].segments[segmentNumber].nodes;
+
+      for (var i = 0; i < segmentChildren.length; i++) {
+        result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index));
+      }
+
+      if (!stats[node.id].executable) {
+        return result;
+      }
+
+      return node.beforeAllFns.concat(result).concat(node.afterAllFns);
+    }
+  }
+
+  return TreeProcessor;
+};
+
+getJasmineRequireObj().Any = function(j$) {
+
+  function Any(expectedObject) {
+    if (typeof expectedObject === 'undefined') {
+      throw new TypeError(
+        'jasmine.any() expects to be passed a constructor function. ' +
+        'Please pass one or use jasmine.anything() to match any object.'
+      );
+    }
+    this.expectedObject = expectedObject;
+  }
+
+  Any.prototype.asymmetricMatch = function(other) {
+    if (this.expectedObject == String) {
+      return typeof other == 'string' || other instanceof String;
+    }
+
+    if (this.expectedObject == Number) {
+      return typeof other == 'number' || other instanceof Number;
+    }
+
+    if (this.expectedObject == Function) {
+      return typeof other == 'function' || other instanceof Function;
+    }
+
+    if (this.expectedObject == Object) {
+      return typeof other == 'object';
+    }
+
+    if (this.expectedObject == Boolean) {
+      return typeof other == 'boolean';
+    }
+
+    return other instanceof this.expectedObject;
+  };
+
+  Any.prototype.jasmineToString = function() {
+    return '<jasmine.any(' + j$.fnNameFor(this.expectedObject) + ')>';
+  };
+
+  return Any;
+};
+
+getJasmineRequireObj().Anything = function(j$) {
+
+  function Anything() {}
+
+  Anything.prototype.asymmetricMatch = function(other) {
+    return !j$.util.isUndefined(other) && other !== null;
+  };
+
+  Anything.prototype.jasmineToString = function() {
+    return '<jasmine.anything>';
+  };
+
+  return Anything;
+};
+
+getJasmineRequireObj().ArrayContaining = function(j$) {
+  function ArrayContaining(sample) {
+    this.sample = sample;
+  }
+
+  ArrayContaining.prototype.asymmetricMatch = function(other) {
+    var className = Object.prototype.toString.call(this.sample);
+    if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not 
\'' + this.sample + '\'.'); }
+
+    for (var i = 0; i < this.sample.length; i++) {
+      var item = this.sample[i];
+      if (!j$.matchersUtil.contains(other, item)) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+  ArrayContaining.prototype.jasmineToString = function () {
+    return '<jasmine.arrayContaining(' + jasmine.pp(this.sample) +')>';
+  };
+
+  return ArrayContaining;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+  function ObjectContaining(sample) {
+    this.sample = sample;
+  }
+
+  function getPrototype(obj) {
+    if (Object.getPrototypeOf) {
+      return Object.getPrototypeOf(obj);
+    }
+
+    if (obj.constructor.prototype == obj) {
+      return null;
+    }
+
+    return obj.constructor.prototype;
+  }
+
+  function hasProperty(obj, property) {
+    if (!obj) {
+      return false;
+    }
+
+    if (Object.prototype.hasOwnProperty.call(obj, property)) {
+      return true;
+    }
+
+    return hasProperty(getPrototype(obj), property);
+  }
+
+  ObjectContaining.prototype.asymmetricMatch = function(other) {
+    if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, 
not \''+this.sample+'\'.'); }
+
+    for (var property in this.sample) {
+      if (!hasProperty(other, property) ||
+          !j$.matchersUtil.equals(this.sample[property], other[property])) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+  ObjectContaining.prototype.jasmineToString = function() {
+    return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
+  };
+
+  return ObjectContaining;
+};
+
+getJasmineRequireObj().StringMatching = function(j$) {
+
+  function StringMatching(expected) {
+    if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+      throw new Error('Expected is not a String or a RegExp');
+    }
+
+    this.regexp = new RegExp(expected);
+  }
+
+  StringMatching.prototype.asymmetricMatch = function(other) {
+    return this.regexp.test(other);
+  };
+
+  StringMatching.prototype.jasmineToString = function() {
+    return '<jasmine.stringMatching(' + this.regexp + ')>';
+  };
+
+  return StringMatching;
+};
+
+getJasmineRequireObj().errors = function() {
+  function ExpectationFailed() {}
+
+  ExpectationFailed.prototype = new Error();
+  ExpectationFailed.prototype.constructor = ExpectationFailed;
+
+  return {
+    ExpectationFailed: ExpectationFailed
+  };
+};
+getJasmineRequireObj().formatErrorMsg = function() {
+  function generateErrorMsg(domain, usage) {
+    var usageDefinition = usage ? '\nUsage: ' + usage : '';
+
+    return function errorMsg(msg) {
+      return domain + ' : ' + msg + usageDefinition;
+    };
+  }
+
+  return generateErrorMsg;
+};
+
+getJasmineRequireObj().matchersUtil = function(j$) {
+  // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+
+  return {
+    equals: function(a, b, customTesters) {
+      customTesters = customTesters || [];
+
+      return eq(a, b, [], [], customTesters);
+    },
+
+    contains: function(haystack, needle, customTesters) {
+      customTesters = customTesters || [];
+
+      if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
+        (!!haystack && !haystack.indexOf))
+      {
+        for (var i = 0; i < haystack.length; i++) {
+          if (eq(haystack[i], needle, [], [], customTesters)) {
+            return true;
+          }
+        }
+        return false;
+      }
+
+      return !!haystack && haystack.indexOf(needle) >= 0;
+    },
+
+    buildFailureMessage: function() {
+      var args = Array.prototype.slice.call(arguments, 0),
+        matcherName = args[0],
+        isNot = args[1],
+        actual = args[2],
+        expected = args.slice(3),
+        englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+
+      var message = 'Expected ' +
+        j$.pp(actual) +
+        (isNot ? ' not ' : ' ') +
+        englishyPredicate;
+
+      if (expected.length > 0) {
+        for (var i = 0; i < expected.length; i++) {
+          if (i > 0) {
+            message += ',';
+          }
+          message += ' ' + j$.pp(expected[i]);
+        }
+      }
+
+      return message + '.';
+    }
+  };
+
+  function isAsymmetric(obj) {
+    return obj && j$.isA_('Function', obj.asymmetricMatch);
+  }
+
+  function asymmetricMatch(a, b) {
+    var asymmetricA = isAsymmetric(a),
+        asymmetricB = isAsymmetric(b);
+
+    if (asymmetricA && asymmetricB) {
+      return undefined;
+    }
+
+    if (asymmetricA) {
+      return a.asymmetricMatch(b);
+    }
+
+    if (asymmetricB) {
+      return b.asymmetricMatch(a);
+    }
+  }
+
+  // Equality function lovingly adapted from isEqual in
+  //   [Underscore](http://underscorejs.org)
+  function eq(a, b, aStack, bStack, customTesters) {
+    var result = true;
+
+    var asymmetricResult = asymmetricMatch(a, b);
+    if (!j$.util.isUndefined(asymmetricResult)) {
+      return asymmetricResult;
+    }
+
+    for (var i = 0; i < customTesters.length; i++) {
+      var customTesterResult = customTesters[i](a, b);
+      if (!j$.util.isUndefined(customTesterResult)) {
+        return customTesterResult;
+      }
+    }
+
+    if (a instanceof Error && b instanceof Error) {
+      return a.message == b.message;
+    }
+
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) { return a !== 0 || 1 / a == 1 / b; }
+    // A strict comparison is necessary because `null == undefined`.
+    if (a === null || b === null) { return a === b; }
+    var className = Object.prototype.toString.call(a);
+    if (className != Object.prototype.toString.call(b)) { return false; }
+    switch (className) {
+      // Strings, numbers, dates, and booleans are compared by value.
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return a == String(b);
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+        // other numeric values.
+        return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a == +b;
+      // RegExps are compared by their source patterns and flags.
+      case '[object RegExp]':
+        return a.source == b.source &&
+          a.global == b.global &&
+          a.multiline == b.multiline &&
+          a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') { return false; }
+
+    var aIsDomNode = j$.isDomNode(a);
+    var bIsDomNode = j$.isDomNode(b);
+    if (aIsDomNode && bIsDomNode) {
+      // At first try to use DOM3 method isEqualNode
+      if (a.isEqualNode) {
+        return a.isEqualNode(b);
+      }
+      // IE8 doesn't support isEqualNode, try to use outerHTML && innerText
+      var aIsElement = a instanceof Element;
+      var bIsElement = b instanceof Element;
+      if (aIsElement && bIsElement) {
+        return a.outerHTML == b.outerHTML;
+      }
+      if (aIsElement || bIsElement) {
+        return false;
+      }
+      return a.innerText == b.innerText && a.textContent == b.textContent;
+    }
+    if (aIsDomNode || bIsDomNode) {
+      return false;
+    }
+
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] == a) { return bStack[length] == b; }
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size = 0;
+    // Recursively compare objects and arrays.
+    // Compare array lengths to determine if a deep comparison is necessary.
+    if (className == '[object Array]') {
+      size = a.length;
+      if (size !== b.length) {
+        return false;
+      }
+
+      while (size--) {
+        result = eq(a[size], b[size], aStack, bStack, customTesters);
+        if (!result) {
+          return false;
+        }
+      }
+    } else {
+
+      // Objects with different constructors are not equivalent, but `Object`s
+      // or `Array`s from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor && !(isObjectConstructor(aCtor) &&
+                               isObjectConstructor(bCtor))) {
+        return false;
+      }
+    }
+
+    // Deep compare objects.
+    var aKeys = keys(a, className == '[object Array]'), key;
+    size = aKeys.length;
+
+    // Ensure that both objects contain the same number of properties before comparing deep equality.
+    if (keys(b, className == '[object Array]').length !== size) { return false; }
+
+    while (size--) {
+      key = aKeys[size];
+      // Deep compare each member
+      result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters);
+
+      if (!result) {
+        return false;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+
+    return result;
+
+    function keys(obj, isArray) {
+      var allKeys = Object.keys ? Object.keys(obj) :
+        (function(o) {
+            var keys = [];
+            for (var key in o) {
+                if (has(o, key)) {
+                    keys.push(key);
+                }
+            }
+            return keys;
+        })(obj);
+
+      if (!isArray) {
+        return allKeys;
+      }
+
+      var extraKeys = [];
+      if (allKeys.length === 0) {
+          return allKeys;
+      }
+
+      for (var x = 0; x < allKeys.length; x++) {
+          if (!allKeys[x].match(/^[0-9]+$/)) {
+              extraKeys.push(allKeys[x]);
+          }
+      }
+
+      return extraKeys;
+    }
+  }
+
+  function has(obj, key) {
+    return Object.prototype.hasOwnProperty.call(obj, key);
+  }
+
+  function isFunction(obj) {
+    return typeof obj === 'function';
+  }
+
+  function isObjectConstructor(ctor) {
+    // aCtor instanceof aCtor is true for the Object and Function
+    // constructors (since a constructor is-a Function and a function is-a
+    // Object). We don't just compare ctor === Object because the constructor
+    // might come from a different frame with different globals.
+    return isFunction(ctor) && ctor instanceof ctor;
+  }
+};
+
+getJasmineRequireObj().toBe = function() {
+  function toBe() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual === expected
+        };
+      }
+    };
+  }
+
+  return toBe;
+};
+
+getJasmineRequireObj().toBeCloseTo = function() {
+
+  function toBeCloseTo() {
+    return {
+      compare: function(actual, expected, precision) {
+        if (precision !== 0) {
+          precision = precision || 2;
+        }
+
+        return {
+          pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+        };
+      }
+    };
+  }
+
+  return toBeCloseTo;
+};
+
+getJasmineRequireObj().toBeDefined = function() {
+  function toBeDefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: (void 0 !== actual)
+        };
+      }
+    };
+  }
+
+  return toBeDefined;
+};
+
+getJasmineRequireObj().toBeFalsy = function() {
+  function toBeFalsy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!!actual
+        };
+      }
+    };
+  }
+
+  return toBeFalsy;
+};
+
+getJasmineRequireObj().toBeGreaterThan = function() {
+
+  function toBeGreaterThan() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual > expected
+        };
+      }
+    };
+  }
+
+  return toBeGreaterThan;
+};
+
+
+getJasmineRequireObj().toBeGreaterThanOrEqual = function() {
+
+  function toBeGreaterThanOrEqual() {
+    return {
+      compare: function(actual, expected) {
+        return {
+          pass: actual >= expected
+        };
+      }
+    };
+  }
+
+  return toBeGreaterThanOrEqual;
+};
+
+getJasmineRequireObj().toBeLessThan = function() {
+  function toBeLessThan() {
+    return {
+
+      compare: function(actual, expected) {
+        return {
+          pass: actual < expected
+        };
+      }
+    };
+  }
+
+  return toBeLessThan;
+};
+getJasmineRequireObj().toBeLessThanOrEqual = function() {
+  function toBeLessThanOrEqual() {
+    return {
+
+      compare: function(actual, expected) {
+        return {
+          pass: actual <= expected
+        };
+      }
+    };
+  }
+
+  return toBeLessThanOrEqual;
+};
+
+getJasmineRequireObj().toBeNaN = function(j$) {
+
+  function toBeNaN() {
+    return {
+      compare: function(actual) {
+        var result = {
+          pass: (actual !== actual)
+        };
+
+        if (result.pass) {
+          result.message = 'Expected actual not to be NaN.';
+        } else {
+          result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toBeNaN;
+};
+
+getJasmineRequireObj().toBeNull = function() {
+
+  function toBeNull() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: actual === null
+        };
+      }
+    };
+  }
+
+  return toBeNull;
+};
+
+getJasmineRequireObj().toBeTruthy = function() {
+
+  function toBeTruthy() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: !!actual
+        };
+      }
+    };
+  }
+
+  return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+
+  function toBeUndefined() {
+    return {
+      compare: function(actual) {
+        return {
+          pass: void 0 === actual
+        };
+      }
+    };
+  }
+
+  return toBeUndefined;
+};
+
+getJasmineRequireObj().toContain = function() {
+  function toContain(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+
+        return {
+          pass: util.contains(actual, expected, customEqualityTesters)
+        };
+      }
+    };
+  }
+
+  return toContain;
+};
+
+getJasmineRequireObj().toEqual = function() {
+
+  function toEqual(util, customEqualityTesters) {
+    customEqualityTesters = customEqualityTesters || [];
+
+    return {
+      compare: function(actual, expected) {
+        var result = {
+          pass: false
+        };
+
+        result.pass = util.equals(actual, expected, customEqualityTesters);
+
+        return result;
+      }
+    };
+  }
+
+  return toEqual;
+};
+
+getJasmineRequireObj().toHaveBeenCalled = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalled>', 'expect(<spyObj>).toHaveBeenCalled()');
+
+  function toHaveBeenCalled() {
+    return {
+      compare: function(actual) {
+        var result = {};
+
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        if (arguments.length > 1) {
+          throw new Error(getErrorMsg('Does not take arguments, use toHaveBeenCalledWith'));
+        }
+
+        result.pass = actual.calls.any();
+
+        result.message = result.pass ?
+          'Expected spy ' + actual.and.identity() + ' not to have been called.' :
+          'Expected spy ' + actual.and.identity() + ' to have been called.';
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalled;
+};
+
+getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledTimes>', 
'expect(<spyObj>).toHaveBeenCalledTimes(<Number>)');
+
+  function toHaveBeenCalledTimes() {
+    return {
+      compare: function(actual, expected) {
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        var args = Array.prototype.slice.call(arguments, 0),
+          result = { pass: false };
+
+        if (!j$.isNumber_(expected)){
+          throw new Error(getErrorMsg('The expected times failed is a required argument and must be a 
number.'));
+        }
+
+        actual = args[0];
+        var calls = actual.calls.count();
+        var timesMessage = expected === 1 ? 'once' : expected + ' times';
+        result.pass = calls === expected;
+        result.message = result.pass ?
+          'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was 
called ' +  calls + ' times.' :
+          'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was 
called ' +  calls + ' times.';
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledTimes;
+};
+
+getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledWith>', 
'expect(<spyObj>).toHaveBeenCalledWith(...arguments)');
+
+  function toHaveBeenCalledWith(util, customEqualityTesters) {
+    return {
+      compare: function() {
+        var args = Array.prototype.slice.call(arguments, 0),
+          actual = args[0],
+          expectedArgs = args.slice(1),
+          result = { pass: false };
+
+        if (!j$.isSpy(actual)) {
+          throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+        }
+
+        if (!actual.calls.any()) {
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been 
called with ' + j$.pp(expectedArgs) + ' but it was never called.'; };
+          return result;
+        }
+
+        if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
+          result.pass = true;
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been 
called with ' + j$.pp(expectedArgs) + ' but it was.'; };
+        } else {
+          result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been 
called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ 
| \]$/g, '') + '.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toHaveBeenCalledWith;
+};
+
+getJasmineRequireObj().toMatch = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toMatch>', 'expect(<expectation>).toMatch(<string> || <regexp>)');
+
+  function toMatch() {
+    return {
+      compare: function(actual, expected) {
+        if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+          throw new Error(getErrorMsg('Expected is not a String or a RegExp'));
+        }
+
+        var regexp = new RegExp(expected);
+
+        return {
+          pass: regexp.test(actual)
+        };
+      }
+    };
+  }
+
+  return toMatch;
+};
+
+getJasmineRequireObj().toThrow = function(j$) {
+
+  var getErrorMsg = j$.formatErrorMsg('<toThrow>', 'expect(function() {<expectation>}).toThrow()');
+
+  function toThrow(util) {
+    return {
+      compare: function(actual, expected) {
+        var result = { pass: false },
+          threw = false,
+          thrown;
+
+        if (typeof actual != 'function') {
+          throw new Error(getErrorMsg('Actual is not a Function'));
+        }
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          result.message = 'Expected function to throw an exception.';
+          return result;
+        }
+
+        if (arguments.length == 1) {
+          result.pass = true;
+          result.message = function() { return 'Expected function not to throw, but it threw ' + 
j$.pp(thrown) + '.'; };
+
+          return result;
+        }
+
+        if (util.equals(thrown, expected)) {
+          result.pass = true;
+          result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; };
+        } else {
+          result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it 
threw ' +  j$.pp(thrown) + '.'; };
+        }
+
+        return result;
+      }
+    };
+  }
+
+  return toThrow;
+};
+
+getJasmineRequireObj().toThrowError = function(j$) {
+
+  var getErrorMsg =  j$.formatErrorMsg('<toThrowError>', 'expect(function() 
{<expectation>}).toThrowError(<ErrorConstructor>, <message>)');
+
+  function toThrowError () {
+    return {
+      compare: function(actual) {
+        var threw = false,
+          pass = {pass: true},
+          fail = {pass: false},
+          thrown;
+
+        if (typeof actual != 'function') {
+          throw new Error(getErrorMsg('Actual is not a Function'));
+        }
+
+        var errorMatcher = getMatcher.apply(null, arguments);
+
+        try {
+          actual();
+        } catch (e) {
+          threw = true;
+          thrown = e;
+        }
+
+        if (!threw) {
+          fail.message = 'Expected function to throw an Error.';
+          return fail;
+        }
+
+        if (!(thrown instanceof Error)) {
+          fail.message = function() { return 'Expected function to throw an Error, but it threw ' + 
j$.pp(thrown) + '.'; };
+          return fail;
+        }
+
+        if (errorMatcher.hasNoSpecifics()) {
+          pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + 
'.';
+          return pass;
+        }
+
+        if (errorMatcher.matches(thrown)) {
+          pass.message = function() {
+            return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + 
errorMatcher.messageDescription() + '.';
+          };
+          return pass;
+        } else {
+          fail.message = function() {
+            return 'Expected function to throw ' + errorMatcher.errorTypeDescription + 
errorMatcher.messageDescription() +
+              ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.';
+          };
+          return fail;
+        }
+      }
+    };
+
+    function getMatcher() {
+      var expected = null,
+          errorType = null;
+
+      if (arguments.length == 2) {
+        expected = arguments[1];
+        if (isAnErrorType(expected)) {
+          errorType = expected;
+          expected = null;
+        }
+      } else if (arguments.length > 2) {
+        errorType = arguments[1];
+        expected = arguments[2];
+        if (!isAnErrorType(errorType)) {
+          throw new Error(getErrorMsg('Expected error type is not an Error.'));
+        }
+      }
+
+      if (expected && !isStringOrRegExp(expected)) {
+        if (errorType) {
+          throw new Error(getErrorMsg('Expected error message is not a string or RegExp.'));
+        } else {
+          throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.'));
+        }
+      }
+
+      function messageMatch(message) {
+        if (typeof expected == 'string') {
+          return expected == message;
+        } else {
+          return expected.test(message);
+        }
+      }
+
+      return {
+        errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception',
+        thrownDescription: function(thrown) {
+          var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
+              thrownMessage = '';
+
+          if (expected) {
+            thrownMessage = ' with message ' + j$.pp(thrown.message);
+          }
+
+          return thrownName + thrownMessage;
+        },
+        messageDescription: function() {
+          if (expected === null) {
+            return '';
+          } else if (expected instanceof RegExp) {
+            return ' with a message matching ' + j$.pp(expected);
+          } else {
+            return ' with message ' + j$.pp(expected);
+          }
+        },
+        hasNoSpecifics: function() {
+          return expected === null && errorType === null;
+        },
+        matches: function(error) {
+          return (errorType === null || error instanceof errorType) &&
+            (expected === null || messageMatch(error.message));
+        }
+      };
+    }
+
+    function isStringOrRegExp(potential) {
+      return potential instanceof RegExp || (typeof potential == 'string');
+    }
+
+    function isAnErrorType(type) {
+      if (typeof type !== 'function') {
+        return false;
+      }
+
+      var Surrogate = function() {};
+      Surrogate.prototype = type.prototype;
+      return (new Surrogate()) instanceof Error;
+    }
+  }
+
+  return toThrowError;
+};
+
+getJasmineRequireObj().interface = function(jasmine, env) {
+  var jasmineInterface = {
+    describe: function(description, specDefinitions) {
+      return env.describe(description, specDefinitions);
+    },
+
+    xdescribe: function(description, specDefinitions) {
+      return env.xdescribe(description, specDefinitions);
+    },
+
+    fdescribe: function(description, specDefinitions) {
+      return env.fdescribe(description, specDefinitions);
+    },
+
+    it: function() {
+      return env.it.apply(env, arguments);
+    },
+
+    xit: function() {
+      return env.xit.apply(env, arguments);
+    },
+
+    fit: function() {
+      return env.fit.apply(env, arguments);
+    },
+
+    beforeEach: function() {
+      return env.beforeEach.apply(env, arguments);
+    },
+
+    afterEach: function() {
+      return env.afterEach.apply(env, arguments);
+    },
+
+    beforeAll: function() {
+      return env.beforeAll.apply(env, arguments);
+    },
+
+    afterAll: function() {
+      return env.afterAll.apply(env, arguments);
+    },
+
+    expect: function(actual) {
+      return env.expect(actual);
+    },
+
+    pending: function() {
+      return env.pending.apply(env, arguments);
+    },
+
+    fail: function() {
+      return env.fail.apply(env, arguments);
+    },
+
+    spyOn: function(obj, methodName) {
+      return env.spyOn(obj, methodName);
+    },
+
+    jsApiReporter: new jasmine.JsApiReporter({
+      timer: new jasmine.Timer()
+    }),
+
+    jasmine: jasmine
+  };
+
+  jasmine.addCustomEqualityTester = function(tester) {
+    env.addCustomEqualityTester(tester);
+  };
+
+  jasmine.addMatchers = function(matchers) {
+    return env.addMatchers(matchers);
+  };
+
+  jasmine.clock = function() {
+    return env.clock;
+  };
+
+  return jasmineInterface;
+};
+
+getJasmineRequireObj().version = function() {
+  return '2.5.2';
+};
diff --git a/installed-tests/js/jsunit.gresources.xml b/installed-tests/js/jsunit.gresources.xml
index 944626b..da983d4 100644
--- a/installed-tests/js/jsunit.gresources.xml
+++ b/installed-tests/js/jsunit.gresources.xml
@@ -2,6 +2,8 @@
 <gresources>
   <gresource prefix="/org/gjs/jsunit">
     <file preprocess="xml-stripblanks">complex.ui</file>
+    <file>jasmine.js</file>
+    <file>minijasmine.js</file>
     <file>modules/alwaysThrows.js</file>
     <file>modules/foobar.js</file>
     <file>modules/modunicode.js</file>
diff --git a/installed-tests/js/minijasmine.js b/installed-tests/js/minijasmine.js
new file mode 100644
index 0000000..0345ec3
--- /dev/null
+++ b/installed-tests/js/minijasmine.js
@@ -0,0 +1,113 @@
+#!/usr/bin/env gjs
+
+const GLib = imports.gi.GLib;
+const Lang = imports.lang;
+
+function _removeNewlines(str) {
+    let allNewlines = /\n/g;
+    return str.replace(allNewlines, '\\n');
+}
+
+function _filterStack(stack) {
+    return stack.split('\n')
+        .filter(stackLine => stackLine.indexOf('resource:///org/gjs/jsunit') === -1)
+        .filter(stackLine => stackLine.indexOf('<jasmine-start>') === -1)
+        .join('\n');
+}
+
+function _setTimeoutInternal(continueTimeout, func, time) {
+    return GLib.timeout_add(GLib.PRIORITY_DEFAULT, time, function () {
+        func();
+        return continueTimeout;
+    });
+}
+
+function _clearTimeoutInternal(id) {
+    if (id > 0)
+        GLib.source_remove(id);
+}
+
+// Install the browser setTimeout/setInterval API on the global object
+window.setTimeout = _setTimeoutInternal.bind(undefined, GLib.SOURCE_REMOVE);
+window.setInterval = _setTimeoutInternal.bind(undefined, GLib.SOURCE_CONTINUE);
+window.clearTimeout = window.clearInterval = _clearTimeoutInternal;
+
+let jasmineRequire = imports.jasmine.getJasmineRequireObj();
+let jasmineCore = jasmineRequire.core(jasmineRequire);
+window._jasmineEnv = jasmineCore.getEnv();
+
+window._jasmineMain = GLib.MainLoop.new(null, false);
+window._jasmineRetval = 0;
+
+// Install Jasmine API on the global object
+let jasmineInterface = jasmineRequire.interface(jasmineCore, window._jasmineEnv);
+Lang.copyProperties(jasmineInterface, window);
+
+// Reporter that outputs according to the Test Anything Protocol
+// See http://testanything.org/tap-specification.html
+const TapReporter = new Lang.Class({
+    Name: 'TapReporter',
+
+    _init: function () {
+        this._failedSuites = [];
+        this._specCount = 0;
+    },
+
+    jasmineStarted: function (info) {
+        print('1..' + info.totalSpecsDefined);
+    },
+
+    jasmineDone: function () {
+        this._failedSuites.forEach(failure => {
+            failure.failedExpectations.forEach(result => {
+                print('not ok - An error was thrown outside a test');
+                print('# ' + result.message);
+            });
+        });
+
+        window._jasmineMain.quit();
+    },
+
+    suiteDone: function (result) {
+        if (result.failedExpectations && result.failedExpectations.length > 0) {
+            window._jasmineRetval = 1;
+            this._failedSuites.push(result);
+        }
+
+        if (result.status === 'disabled') {
+            print('# Suite was disabled:', result.fullName);
+        }
+    },
+
+    specStarted: function () {
+        this._specCount++;
+    },
+
+    specDone: function (result) {
+        let tap_report;
+        if (result.status === 'failed') {
+            window._jasmineRetval = 1;
+            tap_report = 'not ok';
+        } else {
+            tap_report = 'ok';
+        }
+        tap_report += ' ' + this._specCount + ' ' + result.fullName;
+        if (result.status === 'pending' || result.status === 'disabled') {
+            let reason = result.pendingReason || result.status;
+            tap_report += ' # SKIP ' + reason;
+        }
+        print(tap_report);
+
+        // Print additional diagnostic info on failure
+        if (result.status === 'failed' && result.failedExpectations) {
+            result.failedExpectations.forEach((failedExpectation) => {
+                print('# Message:', _removeNewlines(failedExpectation.message));
+                print('# Stack:');
+                let stackTrace = _filterStack(failedExpectation.stack).trim();
+                print(stackTrace.split('\n').map((str) => '#   ' + str).join('\n'));
+            });
+        }
+    },
+});
+
+window._jasmineEnv.addReporter(new TapReporter());
diff --git a/installed-tests/js/test0020importer.js b/installed-tests/js/test0020importer.js
index 33586bd..a65241c 100644
--- a/installed-tests/js/test0020importer.js
+++ b/installed-tests/js/test0020importer.js
@@ -1,8 +1,6 @@
-const JSUnit = imports.jsUnit;
-
-function testImporter1() {
-    var GLib = imports.gi.GLib;
-    JSUnit.assertEquals(GLib.MAJOR_VERSION, 2);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+describe('Importer', function () {
+    it('can import GI modules', function () {
+        var GLib = imports.gi.GLib;
+        expect(GLib.MAJOR_VERSION).toEqual(2);
+    });
+});
diff --git a/installed-tests/js/test0030basicBoxed.js b/installed-tests/js/test0030basicBoxed.js
index f736c8a..bf4ad9c 100644
--- a/installed-tests/js/test0030basicBoxed.js
+++ b/installed-tests/js/test0030basicBoxed.js
@@ -1,10 +1,9 @@
-const JSUnit = imports.jsUnit;
 const Regress = imports.gi.Regress;
 
-function testBasicBoxed() {
-    var a = new Regress.TestSimpleBoxedA();
-    a.some_int = 42;
-    JSUnit.assertEquals(a.some_int, 42);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+describe('GI boxed type', function () {
+    it('has readable and writable fields', function () {
+        var a = new Regress.TestSimpleBoxedA();
+        a.some_int = 42;
+        expect(a.some_int).toEqual(42);
+    });
+});
diff --git a/installed-tests/js/test0040mainloop.js b/installed-tests/js/test0040mainloop.js
index 638688d..3a671e7 100644
--- a/installed-tests/js/test0040mainloop.js
+++ b/installed-tests/js/test0040mainloop.js
@@ -1,17 +1,15 @@
-const JSUnit = imports.jsUnit;
 var Mainloop = imports.mainloop;
 
-function testBasicMainloop() {
-    log('running mainloop test');
-    Mainloop.idle_add(function() { Mainloop.quit('testMainloop'); });
-    Mainloop.run('testMainloop');
-    log('mainloop test done');
-}
-
-/* A dangling mainloop idle should get removed and not leaked */
-function testDanglingIdle() {
-    Mainloop.idle_add(function() { return true; });
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
-
+describe('Mainloop module', function () {
+    it('runs a main loop', function (done) {
+        Mainloop.idle_add(function () {
+            Mainloop.quit('testMainloop');
+            done();
+        });
+        Mainloop.run('testMainloop');
+    });
+
+    it('removes a dangling idle and does not leak it', function () {
+        Mainloop.idle_add(function() { return true; });
+    });
+});
diff --git a/installed-tests/js/testByteArray.js b/installed-tests/js/testByteArray.js
index 7a357dc..97f93e3 100644
--- a/installed-tests/js/testByteArray.js
+++ b/installed-tests/js/testByteArray.js
@@ -1,121 +1,113 @@
-// tests for imports.lang module
-
-const JSUnit = imports.jsUnit;
 const ByteArray = imports.byteArray;
-const Gio = imports.gi.Gio;
-
-function testEmptyByteArray() {
-    let a = new ByteArray.ByteArray();
-    JSUnit.assertEquals("length is 0 for empty array", 0, a.length);
-}
-
-function testInitialSizeByteArray() {
-    let a = new ByteArray.ByteArray(10);
-    JSUnit.assertEquals("length is 10 for initially-sized-10 array", 10, a.length);
-
-    let i;
-
-    for (i = 0; i < a.length; ++i) {
-        JSUnit.assertEquals("new array initialized to zeroes", 0, a[i]);
-    }
-
-    JSUnit.assertEquals("array had proper number of elements post-construct (counting for)",
-                 10, i);
-}
-
-function testAssignment() {
-    let a = new ByteArray.ByteArray(256);
-    JSUnit.assertEquals("length is 256 for initially-sized-256 array", 256, a.length);
-
-    let i;
-    let count;
-
-    count = 0;
-    for (i = 0; i < a.length; ++i) {
-        JSUnit.assertEquals("new array initialized to zeroes", 0, a[i]);
-        a[i] = 255 - i;
-        count += 1;
-    }
-
-    JSUnit.assertEquals("set proper number of values", 256, count);
-
-    count = 0;
-    for (i = 0; i < a.length; ++i) {
-        JSUnit.assertEquals("assignment set expected value", 255 - i, a[i]);
-        count += 1;
-    }
-
-    JSUnit.assertEquals("checked proper number of values", 256, count);
-}
-
-function testAssignmentPastEnd() {
-    let a = new ByteArray.ByteArray();
-    JSUnit.assertEquals("length is 0 for empty array", 0, a.length);
-
-    a[2] = 5;
-    JSUnit.assertEquals("implicitly made length 3", 3, a.length);
-    JSUnit.assertEquals("implicitly-created zero byte", 0, a[0]);
-    JSUnit.assertEquals("implicitly-created zero byte", 0, a[1]);
-    JSUnit.assertEquals("stored 5 in autocreated position", 5, a[2]);
-}
-
-function testAssignmentToLength() {
-    let a = new ByteArray.ByteArray(20);
-    JSUnit.assertEquals("length is 20 for new array", 20, a.length);
-
-    a.length = 5;
-
-    JSUnit.assertEquals("length is 5 after setting it to 5", 5, a.length);
-}
-
-function testNonIntegerAssignment() {
-    let a = new ByteArray.ByteArray();
-
-    a[0] = 5;
-    JSUnit.assertEquals("assigning 5 gives a byte 5", 5, a[0]);
-
-    a[0] = null;
-    JSUnit.assertEquals("assigning null gives a zero byte", 0, a[0]);
-
-    a[0] = 5;
-    JSUnit.assertEquals("assigning 5 gives a byte 5", 5, a[0]);
-
-    a[0] = undefined;
-    JSUnit.assertEquals("assigning undefined gives a zero byte", 0, a[0]);
-
-    a[0] = 3.14;
-    JSUnit.assertEquals("assigning a double rounds off", 3, a[0]);
-}
-
-function testFromString() {
-    let a = ByteArray.fromString('abcd');
-    JSUnit.assertEquals("from string 'abcd' gives length 4", 4, a.length);
-    JSUnit.assertEquals("'a' results in 97", 97, a[0]);
-    JSUnit.assertEquals("'b' results in 98", 98, a[1]);
-    JSUnit.assertEquals("'c' results in 99", 99, a[2]);
-    JSUnit.assertEquals("'d' results in 100", 100, a[3]);
-}
-
-function testFromArray() {
-    let a = ByteArray.fromArray([ 1, 2, 3, 4 ]);
-    JSUnit.assertEquals("from array [1,2,3,4] gives length 4", 4, a.length);
-    JSUnit.assertEquals("a[0] == 1", 1, a[0]);
-    JSUnit.assertEquals("a[1] == 2", 2, a[1]);
-    JSUnit.assertEquals("a[2] == 3", 3, a[2]);
-    JSUnit.assertEquals("a[3] == 4", 4, a[3]);
-}
-
-function testToString() {
-    let a = new ByteArray.ByteArray();
-    a[0] = 97;
-    a[1] = 98;
-    a[2] = 99;
-    a[3] = 100;
-
-    let s = a.toString();
-    JSUnit.assertEquals("toString() on 4 ascii bytes gives length 4", 4, s.length);
-    JSUnit.assertEquals("toString() gives 'abcd'", "abcd", s);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
 
+describe('Byte array', function () {
+    it('has length 0 for empty array', function () {
+        let a = new ByteArray.ByteArray();
+        expect(a.length).toEqual(0);
+    });
+
+    describe('initially sized to 10', function () {
+        let a;
+        beforeEach(function () {
+            a = new ByteArray.ByteArray(10);
+        });
+
+        it('has length 10', function () {
+            expect(a.length).toEqual(10);
+        });
+
+        it('is initialized to zeroes', function () {
+            for (let i = 0; i < a.length; ++i) {
+                expect(a[i]).toEqual(0);
+            }
+        });
+    });
+
+    it('assigns values correctly', function () {
+        let a = new ByteArray.ByteArray(256);
+
+        for (let i = 0; i < a.length; ++i) {
+            a[i] = 255 - i;
+        }
+
+        for (let i = 0; i < a.length; ++i) {
+            expect(a[i]).toEqual(255 - i);
+        }
+    });
+
+    describe('assignment past end', function () {
+        let a;
+        beforeEach(function () {
+            a = new ByteArray.ByteArray();
+            a[2] = 5;
+        });
+
+        it('implicitly lengthens the array', function () {
+            expect(a.length).toEqual(3);
+            expect(a[2]).toEqual(5);
+        });
+
+        it('implicitly creates zero bytes', function () {
+            expect(a[0]).toEqual(0);
+            expect(a[1]).toEqual(0);
+        });
+    });
+
+    it('changes the length when assigning to length property', function () {
+        let a = new ByteArray.ByteArray(20);
+        expect(a.length).toEqual(20);
+        a.length = 5;
+        expect(a.length).toEqual(5);
+    });
+
+    describe('conversions', function () {
+        let a;
+        beforeEach(function () {
+            a = new ByteArray.ByteArray();
+            a[0] = 255;
+        });
+
+        it('gives a byte 5 when assigning 5', function () {
+            a[0] = 5;
+            expect(a[0]).toEqual(5);
+        });
+
+        it('gives a byte 0 when assigning null', function () {
+            a[0] = null;
+            expect(a[0]).toEqual(0);
+        });
+
+        it('gives a byte 0 when assigning undefined', function () {
+            a[0] = undefined;
+            expect(a[0]).toEqual(0);
+        });
+
+        it('rounds off when assigning a double', function () {
+            a[0] = 3.14;
+            expect(a[0]).toEqual(3);
+        });
+    });
+
+    it('can be created from a string', function () {
+        let a = ByteArray.fromString('abcd');
+        expect(a.length).toEqual(4);
+        [97, 98, 99, 100].forEach((val, ix) => expect(a[ix]).toEqual(val));
+    });
+
+    it('can be created from an array', function () {
+        let a = ByteArray.fromArray([ 1, 2, 3, 4 ]);
+        expect(a.length).toEqual(4);
+        [1, 2, 3, 4].forEach((val, ix) => expect(a[ix]).toEqual(val));
+    });
+
+    it('can be converted to a string of ASCII characters', function () {
+        let a = new ByteArray.ByteArray();
+        a[0] = 97;
+        a[1] = 98;
+        a[2] = 99;
+        a[3] = 100;
+        let s = a.toString();
+        expect(s.length).toEqual(4);
+        expect(s).toEqual('abcd');
+    });
+});
diff --git a/installed-tests/js/testClass.js b/installed-tests/js/testClass.js
index f41fcb9..066134e 100644
--- a/installed-tests/js/testClass.js
+++ b/installed-tests/js/testClass.js
@@ -1,15 +1,7 @@
 // -*- mode: js; indent-tabs-mode: nil -*-
 
-const JSUnit = imports.jsUnit;
 const Lang = imports.lang;
 
-function assertArrayEquals(expected, got) {
-    JSUnit.assertEquals(expected.length, got.length);
-    for (let i = 0; i < expected.length; i ++) {
-        JSUnit.assertEquals(expected[i], got[i]);
-    }
-}
-
 const MagicBase = new Lang.Class({
     Name: 'MagicBase',
 
@@ -51,15 +43,6 @@ const Magic = new Lang.Class({
     }
 });
 
-const ToStringOverride = new Lang.Class({
-    Name: 'ToStringOverride',
-
-    toString: function() {
-        let oldToString = this.parent();
-        return oldToString + '; hello';
-    }
-});
-
 const Accessor = new Lang.Class({
     Name: 'AccessorMagic',
 
@@ -87,120 +70,126 @@ const AbstractBase = new Lang.Class({
     }
 });
 
-const AbstractImpl = new Lang.Class({
-    Name: 'AbstractImpl',
-    Extends: AbstractBase,
-
-    _init: function() {
-        this.parent();
-        this.bar = 42;
-    }
-});
-
-const AbstractImpl2 = new Lang.Class({
-    Name: 'AbstractImpl2',
-    Extends: AbstractBase,
+describe('Class framework', function () {
+    it('calls _init constructors', function () {
+        let newMagic = new MagicBase('A');
+        expect(newMagic.a).toEqual('A');
+    });
 
-    // no _init here, we inherit the parent one
-});
+    it('calls parent constructors', function () {
+        let buffer = [];
 
-const CustomConstruct = new Lang.Class({
-    Name: 'CustomConstruct',
+        let newMagic = new Magic('a', 'b', buffer);
+        expect(buffer).toEqual(['a', 'b']);
 
-    _construct: function(one, two) {
-        return [one, two];
-    }
-});
+        buffer = [];
+        let val = newMagic.foo(10, 20, buffer);
+        expect(buffer).toEqual([10, 20]);
+        expect(val).toEqual(10 * 6);
+    });
 
-function testClassFramework() {
-    let newMagic = new MagicBase('A');
-    JSUnit.assertEquals('A',  newMagic.a);
-}
+    it('sets the right constructor properties', function () {
+        expect(Magic.prototype.constructor).toBe(Magic);
 
-function testInheritance() {
-    let buffer = [];
+        let newMagic = new Magic();
+        expect(newMagic.constructor).toBe(Magic);
+    });
 
-    let newMagic = new Magic('a', 'b', buffer);
-    assertArrayEquals(['a', 'b'], buffer);
+    it('sets up instanceof correctly', function () {
+        let newMagic = new Magic();
 
-    buffer = [];
-    let val = newMagic.foo(10, 20, buffer);
-    assertArrayEquals([10, 20], buffer);
-    JSUnit.assertEquals(10*6, val);
-}
+        expect(newMagic instanceof Magic).toBeTruthy();
+        expect(newMagic instanceof MagicBase).toBeTruthy();
+    });
 
-function testConstructor() {
-    JSUnit.assertEquals(Magic, Magic.prototype.constructor);
+    it('reports a sensible value for toString()', function () {
+        let newMagic = new MagicBase();
+        expect(newMagic.toString()).toEqual('[object MagicBase]');
+    });
 
-    let newMagic = new Magic();
-    JSUnit.assertEquals(Magic, newMagic.constructor);
-}
+    it('allows overriding toString()', function () {
+        const ToStringOverride = new Lang.Class({
+            Name: 'ToStringOverride',
 
-function testInstanceOf() {
-    let newMagic = new Magic();
+            toString: function() {
+                let oldToString = this.parent();
+                return oldToString + '; hello';
+            }
+        });
 
-    JSUnit.assertTrue(newMagic instanceof Magic);
-    JSUnit.assertTrue(newMagic instanceof MagicBase);
-}
+        let override = new ToStringOverride();
+        expect(override.toString()).toEqual('[object ToStringOverride]; hello');
+    });
 
-function testToString() {
-    let newMagic = new MagicBase();
-    JSUnit.assertEquals('[object MagicBase]', newMagic.toString());
+    it('is not configurable', function () {
+        let newMagic = new MagicBase();
 
-    let override = new ToStringOverride();
-    JSUnit.assertEquals('[object ToStringOverride]; hello', override.toString());
-}
+        delete newMagic.foo;
+        expect(newMagic.foo).toBeDefined();
+    });
 
-function testConfigurable() {
-    let newMagic = new MagicBase();
+    it('allows accessors for properties', function () {
+        let newAccessor = new Accessor(11);
 
-    delete newMagic.foo;
-    JSUnit.assertNotUndefined(newMagic.foo);
-}
+        expect(newAccessor.value).toEqual(11);
+        expect(() => newAccessor.value = 12).toThrow();
 
-function testAccessor() {
-    let newAccessor = new Accessor(11);
+        newAccessor.value = 42;
+        expect(newAccessor.value).toEqual(42);
+    });
 
-    JSUnit.assertEquals(11, newAccessor.value);
-    JSUnit.assertRaises(function() {
-        newAccessor.value = 12;
+    it('raises an exception when creating an abstract class', function () {
+        expect(() => new AbstractBase()).toThrow();
     });
 
-    newAccessor.value = 42;
-    JSUnit.assertEquals(42, newAccessor.value);
-}
+    it('inherits properties from abstract base classes', function () {
+        const AbstractImpl = new Lang.Class({
+            Name: 'AbstractImpl',
+            Extends: AbstractBase,
+
+            _init: function() {
+                this.parent();
+                this.bar = 42;
+            }
+        });
 
-function testAbstract() {
-    JSUnit.assertRaises(function() {
-        let newAbstract = new AbstractBase();
+        let newAbstract = new AbstractImpl();
+        expect(newAbstract.foo).toEqual(42);
+        expect(newAbstract.bar).toEqual(42);
     });
 
-    let newAbstract = new AbstractImpl();
-    JSUnit.assertEquals(42, newAbstract.foo);
-    JSUnit.assertEquals(42, newAbstract.bar);
+    it('inherits constructors from abstract base classes', function () {
+        const AbstractImpl = new Lang.Class({
+            Name: 'AbstractImpl',
+            Extends: AbstractBase,
+        });
 
-    newAbstract = new AbstractImpl2();
-    JSUnit.assertEquals(42, newAbstract.foo);
-}
+        let newAbstract = new AbstractImpl();
+        expect(newAbstract.foo).toEqual(42);
+    });
 
-function testCrossCall() {
-    // test that a method can call another without clobbering
-    // __caller__
-    let newMagic = new Magic();
-    let buffer = [];
+    it('lets methods call other methods without clobbering __caller__', function () {
+        let newMagic = new Magic();
+        let buffer = [];
 
-    let res = newMagic.bar(10, buffer);
-    assertArrayEquals([10, 20], buffer);
-    JSUnit.assertEquals(50, res);
-}
+        let res = newMagic.bar(10, buffer);
+        expect(buffer).toEqual([10, 20]);
+        expect(res).toEqual(50);
+    });
 
-function testConstruct() {
-    let instance = new CustomConstruct(1, 2);
+    it('allows custom return values from constructors', function () {
+        const CustomConstruct = new Lang.Class({
+            Name: 'CustomConstruct',
 
-    JSUnit.assertTrue(instance instanceof Array);
-    JSUnit.assertTrue(!(instance instanceof CustomConstruct));
+            _construct: function(one, two) {
+                return [one, two];
+            }
+        });
 
-    assertArrayEquals([1, 2], instance);
-}
+        let instance = new CustomConstruct(1, 2);
 
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+        expect(instance instanceof Array).toBeTruthy();
+        expect(instance instanceof CustomConstruct).toBeFalsy();
+        expect(instance).toEqual([1, 2]);
+    });
+});
diff --git a/installed-tests/js/testCoverage.js b/installed-tests/js/testCoverage.js
index 6b48adb..8224d93 100644
--- a/installed-tests/js/testCoverage.js
+++ b/installed-tests/js/testCoverage.js
@@ -1,1499 +1,1156 @@
-const JSUnit = imports.jsUnit;
 const Coverage = imports.coverage;
 
-function parseScriptForExpressionLines(script) {
-    const ast = Reflect.parse(script);
-    return Coverage.expressionLinesForAST(ast);
-}
-
-function assertArrayEquals(actual, expected, assertion) {
-    if (actual.length != expected.length)
-        throw new Error("Arrays not equal length. Actual array was " +
-                        actual.length + " and Expected array was " +
-                        expected.length);
-
-    for (let i = 0; i < actual.length; i++)
-        assertion(expected[i], actual[i]);
-}
-
-function testExpressionLinesWithNoTrailingNewline() {
-    let foundLines = parseScriptForExpressionLines("let x;\n" +
-                                                   "let y;");
-    assertArrayEquals(foundLines, [1, 2], JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForAssignmentExpressionSides() {
-    let foundLinesOnBothExpressionSides =
-        parseScriptForExpressionLines("var x;\n" +
-                                      "x = (function() {\n" +
-                                      "    return 10;\n" +
-                                      "})();\n");
-    assertArrayEquals(foundLinesOnBothExpressionSides,
-                      [1, 2, 3],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForLinesInsideFunctions() {
-    let foundLinesInsideNamedFunction =
-        parseScriptForExpressionLines("function f(a, b) {\n" +
-                                      "    let x = a;\n" +
-                                      "    let y = b;\n" +
-                                      "    return x + y;\n" +
-                                      "}\n" +
-                                      "\n" +
-                                      "var z = f(1, 2);\n");
-    assertArrayEquals(foundLinesInsideNamedFunction,
-                      [2, 3, 4, 7],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForLinesInsideAnonymousFunctions() {
-    let foundLinesInsideAnonymousFunction =
-        parseScriptForExpressionLines("var z = (function f(a, b) {\n" +
-                                      "     let x = a;\n" +
-                                      "     let y = b;\n" +
-                                      "     return x + y;\n" +
-                                      " })();\n");
-    assertArrayEquals(foundLinesInsideAnonymousFunction,
-                      [1, 2, 3, 4],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForBodyOfFunctionProperty() {
-    let foundLinesInsideFunctionProperty =
-        parseScriptForExpressionLines("var o = {\n" +
-                                      "    foo: function() {\n" +
-                                      "        let x = a;\n" +
-                                      "    }\n" +
-                                      "};\n");
-    assertArrayEquals(foundLinesInsideFunctionProperty,
-                      [1, 2, 3],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForCallArgsOfFunctionProperty() {
-    let foundLinesInsideCallArgs =
-        parseScriptForExpressionLines("function f(a) {\n" +
-                                      "}\n" +
-                                      "f({\n" +
-                                      "    foo: function() {\n" +
-                                      "        let x = a;\n" +
-                                      "    }\n" +
-                                      "});\n");
-    assertArrayEquals(foundLinesInsideCallArgs,
-                      [1, 3, 4, 5],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForMultilineCallArgs() {
-    let foundLinesInsideMultilineCallArgs =
-        parseScriptForExpressionLines("function f(a, b, c) {\n" +
-                                      "}\n" +
-                                      "f(1,\n" +
-                                      "  2,\n" +
-                                      "  3);\n");
-    assertArrayEquals(foundLinesInsideMultilineCallArgs,
-                      [1, 3, 4, 5],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForNewCallWithObject() {
-    let foundLinesInsideObjectCallArg =
-        parseScriptForExpressionLines("function f(o) {\n" +
-                                      "}\n" +
-                                      "let obj = {\n" +
-                                      "    Name: new f({ a: 1,\n" +
-                                      "                  b: 2,\n" +
-                                      "                  c: 3\n" +
-                                      "                })\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideObjectCallArg,
-                      [1, 3, 4, 5, 6],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForWhileLoop() {
-    let foundLinesInsideWhileLoop =
-        parseScriptForExpressionLines("var a = 0;\n" +
-                                      "while (a < 1) {\n" +
-                                      "    let x = 0;\n" +
-                                      "    let y = 1;\n" +
-                                      "    a++;" +
-                                      "\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideWhileLoop,
-                      [1, 2, 3, 4, 5],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForTryCatchFinally() {
-    let foundLinesInsideTryCatchFinally =
-        parseScriptForExpressionLines("var a = 0;\n" +
-                                      "try {\n" +
-                                      "    a++;\n" +
-                                      "} catch (e) {\n" +
-                                      "    a++;\n" +
-                                      "} finally {\n" +
-                                      "    a++;\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideTryCatchFinally,
-                      [1, 2, 3, 4, 5, 7],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForCaseStatements() {
-    let foundLinesInsideCaseStatements =
-        parseScriptForExpressionLines("var a = 0;\n" +
-                                      "switch (a) {\n" +
-                                      "case 1:\n" +
-                                      "    a++;\n" +
-                                      "    break;\n" +
-                                      "case 2:\n" +
-                                      "    a++;\n" +
-                                      "    break;\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideCaseStatements,
-                      [1, 2, 4, 5, 7, 8],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForCaseStatementsCharacters() {
-    let foundLinesInsideCaseStatements =
-        parseScriptForExpressionLines("var a = 'a';\n" +
-                                      "switch (a) {\n" +
-                                      "case 'a':\n" +
-                                      "    a++;\n" +
-                                      "    break;\n" +
-                                      "case 'b':\n" +
-                                      "    a++;\n" +
-                                      "    break;\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideCaseStatements,
-                      [1, 2, 4, 5, 7, 8],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForLoop() {
-    let foundLinesInsideLoop =
-        parseScriptForExpressionLines("for (let i = 0; i < 1; i++) {\n" +
-                                      "    let x = 0;\n" +
-                                      "    let y = 1;\n" +
-                                      "\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideLoop,
-                      [1, 2, 3],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForIfExits() {
-    let foundLinesInsideIfExits =
-        parseScriptForExpressionLines("if (1 > 0) {\n" +
-                                      "    let i = 0;\n" +
-                                      "} else {\n" +
-                                      "    let j = 1;\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideIfExits,
-                      [1, 2, 4],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForAllLinesOfMultilineIfTests() {
-    let foundLinesInsideMultilineIfTest =
-        parseScriptForExpressionLines("if (1 > 0 &&\n" +
-                                      "    2 > 0 &&\n" +
-                                      "    3 > 0) {\n" +
-                                      "    let a = 3;\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideMultilineIfTest,
-                      [1, 2, 3, 4],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForObjectPropertyLiterals() {
-    let foundLinesInsideObjectPropertyLiterals =
-        parseScriptForExpressionLines("var a = {\n" +
-                                      "    Name: 'foo',\n" +
-                                      "    Ex: 'bar'\n" +
-                                      "};\n");
-    assertArrayEquals(foundLinesInsideObjectPropertyLiterals,
-                      [1, 2, 3],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForObjectPropertyFunction() {
-    let foundLinesInsideObjectPropertyFunction =
-        parseScriptForExpressionLines("var a = {\n" +
-                                      "    Name: function() {},\n" +
-                                      "};\n");
-    assertArrayEquals(foundLinesInsideObjectPropertyFunction,
-                      [1, 2],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForObjectPropertyObjectExpression() {
-    let foundLinesInsideObjectPropertyObjectExpression =
-        parseScriptForExpressionLines("var a = {\n" +
-                                      "    Name: {},\n" +
-                                      "};\n");
-    assertArrayEquals(foundLinesInsideObjectPropertyObjectExpression,
-                      [1, 2],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForObjectPropertyArrayExpression() {
-    let foundLinesInsideObjectPropertyObjectExpression =
-        parseScriptForExpressionLines("var a = {\n" +
-                                      "    Name: [],\n" +
-                                      "};\n");
-    assertArrayEquals(foundLinesInsideObjectPropertyObjectExpression,
-                      [1, 2],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForObjectArgsToReturn() {
-    let foundLinesInsideObjectArgsToReturn =
-        parseScriptForExpressionLines("function f() {\n" +
-                                      "    return {};\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideObjectArgsToReturn,
-                      [2],
-                      JSUnit.assertEquals);
-}
-
-function testExpressionLinesFoundForObjectArgsToThrow() {
-    let foundLinesInsideObjectArgsToThrow =
-        parseScriptForExpressionLines("function f() {\n" +
-                                      "    throw {\n" +
-                                      "        a: 1,\n" +
-                                      "        b: 2\n" +
-                                      "    }\n" +
-                                      "}\n");
-    assertArrayEquals(foundLinesInsideObjectArgsToThrow,
-                      [2, 3, 4],
-                      JSUnit.assertEquals);
-}
-
-
-function parseScriptForFunctionNames(script) {
-    const ast = Reflect.parse(script);
-    return Coverage.functionsForAST(ast);
-}
-
-function functionDeclarationsEqual(actual, expected) {
-    JSUnit.assertEquals(expected.key, actual.key);
-    JSUnit.assertEquals(expected.line, actual.line);
-    JSUnit.assertEquals(expected.n_params, actual.n_params);
-}
-
-function testFunctionsFoundNoTrailingNewline() {
-    let foundFuncs = parseScriptForFunctionNames("function f1() {}\n" +
-                                                 "function f2() {}");
-    assertArrayEquals(foundFuncs,
-                      [
-                          { key: "f1:1:0", line: 1, n_params: 0 },
-                          { key: "f2:2:0", line: 2, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsFoundForDeclarations() {
-    let foundFunctionDeclarations =
-        parseScriptForFunctionNames("function f1() {}\n" +
-                                    "function f2() {}\n" +
-                                    "function f3() {}\n");
-    assertArrayEquals(foundFunctionDeclarations,
-                      [
-                          { key: "f1:1:0", line: 1, n_params: 0 },
-                          { key: "f2:2:0", line: 2, n_params: 0 },
-                          { key: "f3:3:0", line: 3, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsFoundForNestedFunctions() {
-    let foundFunctions =
-        parseScriptForFunctionNames("function f1() {\n" +
-                                    "    let f2 = function() {\n" +
-                                    "        let f3 = function() {\n" +
-                                    "        }\n" +
-                                    "    }\n" +
-                                    "}\n");
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "f1:1:0", line: 1, n_params: 0 },
-                          { key: "(anonymous):2:0", line: 2, n_params: 0 },
-                          { key: "(anonymous):3:0", line: 3, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsFoundOnSameLineButDifferentiatedOnArgs() {
-    /* Note the lack of newlines. This is all on
-     * one line */
-    let foundFunctionsOnSameLine =
-        parseScriptForFunctionNames("function f1() {" +
-                                    "    return (function(a) {" +
-                                    "        return function(a, b) {}" +
-                                    "    });" +
-                                    "}");
-    assertArrayEquals(foundFunctionsOnSameLine,
-                      [
-                          { key: "f1:1:0", line: 1, n_params: 0 },
-                          { key: "(anonymous):1:1", line: 1, n_params: 1 },
-                          { key: "(anonymous):1:2", line: 1, n_params: 2 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideArrayExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a = [function() {}];\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 },
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideArrowExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("(a) => (function() {})();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:1", line: 1, n_params: 1 },
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideSequence() {
-    let foundFunctions =
-        parseScriptForFunctionNames("(function(a) {})()," +
-                                    "(function(a, b) {})();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:1", line: 1, n_params: 1 },
-                          { key: "(anonymous):1:2", line: 1, n_params: 2 },
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideUnaryExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a = (function() {}())++;\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 },
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideBinaryExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a = function(a) {}() +" +
-                                    " function(a, b) {}();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:1", line: 1, n_params: 1 },
-                          { key: "(anonymous):1:2", line: 1, n_params: 2 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideAssignmentExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a = function() {}();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideUpdateExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a;\n" +
-                                    "a += function() {}();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):2:0", line: 2, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideIfConditions() {
-    let foundFunctions =
-        parseScriptForFunctionNames("if (function(a) {}(a) >" +
-                                    "    function(a, b) {}(a, b)) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:1", line: 1, n_params: 1 },
-                          { key: "(anonymous):1:2", line: 1, n_params: 2 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideWhileConditions() {
-    let foundFunctions =
-        parseScriptForFunctionNames("while (function(a) {}(a) >" +
-                                    "       function(a, b) {}(a, b)) {};\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:1", line: 1, n_params: 1 },
-                          { key: "(anonymous):1:2", line: 1, n_params: 2 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideForInitializer() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for (function() {}; ;) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-/* SpiderMonkey parses for (let i = <init>; <cond>; <update>) as though
- * they were let i = <init> { for (; <cond> <update>) } so test the
- * LetStatement initializer case too */
-function testFunctionsInsideForLetInitializer() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for (let i = function() {}; ;) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideForVarInitializer() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for (var i = function() {}; ;) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideForCondition() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for (; function() {}();) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideForIncrement() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for (; ;function() {}()) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideForInObject() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for (let x in function() {}()) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideForEachInObject() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for each (x in function() {}()) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInsideForOfObject() {
-    let foundFunctions =
-        parseScriptForFunctionNames("for (x of (function() {}())) {}\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsUsedAsObjectFound() {
-    let foundFunctions =
-        parseScriptForFunctionNames("f = function() {}.bind();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsUsedAsObjectDynamicProp() {
-    let foundFunctions =
-        parseScriptForFunctionNames("f = function() {}['bind']();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsOnEitherSideOfLogicalExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let f = function(a) {} ||" +
-                                    " function(a, b) {};\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):1:1", line: 1, n_params: 1 },
-                          { key: "(anonymous):1:2", line: 1, n_params: 2 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsOnEitherSideOfConditionalExpression() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a\n" +
-                                    "let f = a ? function(a) {}() :" +
-                                    " function(a, b) {}();\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):2:1", line: 2, n_params: 1 },
-                          { key: "(anonymous):2:2", line: 2, n_params: 2 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsYielded() {
-    let foundFunctions =
-        parseScriptForFunctionNames("function a() { yield function (){} };\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "a:1:0", line: 1, n_params: 0 },
-                          { key: "(anonymous):1:0", line: 1, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInArrayComprehensionBody() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a = new Array(1);\n" +
-                                    "let b = [function() {} for (i of a)];\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):2:0", line: 2, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInArrayComprehensionBlock() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a = new Array(1);\n" +
-                                    "let b = [i for (i of function() {})];\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):2:0", line: 2, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function testFunctionsInArrayComprehensionFilter() {
-    let foundFunctions =
-        parseScriptForFunctionNames("let a = new Array(1);\n" +
-                                    "let b = [i for (i of a)" +
-                                    "if (function() {}())];\n");
-
-    assertArrayEquals(foundFunctions,
-                      [
-                          { key: "(anonymous):2:0", line: 2, n_params: 0 }
-                      ],
-                      functionDeclarationsEqual);
-}
-
-function parseScriptForBranches(script) {
-    const ast = Reflect.parse(script);
-    return Coverage.branchesForAST(ast);
-}
-
-function branchInfoEqual(actual, expected) {
-    JSUnit.assertEquals(expected.point, actual.point);
-    assertArrayEquals(expected.exits, actual.exits, JSUnit.assertEquals);
-}
-
-function testFindBranchWhereNoTrailingNewline() {
-    let foundBranchExits = parseScriptForBranches("if (1) { let a = 1; }");
-    assertArrayEquals(foundBranchExits,
-                      [
-                          { point: 1, exits: [1] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testBothBranchExitsFoundForSimpleBranch() {
-    let foundBranchExitsForSimpleBranch =
-        parseScriptForBranches("if (1) {\n" +
-                               "    let a = 1;\n" +
-                               "} else {\n" +
-                               "    let b = 2;\n" +
-                               "}\n");
-    assertArrayEquals(foundBranchExitsForSimpleBranch,
-                      [
-                          { point: 1, exits: [2, 4] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testSingleExitFoundForBranchWithOneConsequent() {
-    let foundBranchExitsForSingleConsequentBranch =
-        parseScriptForBranches("if (1) {\n" +
-                               "    let a = 1.0;\n" +
-                               "}\n");
-    assertArrayEquals(foundBranchExitsForSingleConsequentBranch,
-                      [
-                          { point: 1, exits: [2] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testMultipleBranchesFoundForNestedIfElseBranches() {
-    let foundBranchesForNestedIfElseBranches =
-        parseScriptForBranches("if (1) {\n" +
-                               "    let a = 1.0;\n" +
-                               "} else if (2) {\n" +
-                               "    let b = 2.0;\n" +
-                               "} else if (3) {\n" +
-                               "    let c = 3.0;\n" +
-                               "} else {\n" +
-                               "    let d = 4.0;\n" +
-                               "}\n");
-    assertArrayEquals(foundBranchesForNestedIfElseBranches,
-                      [
-                          /* the 'else if' line is actually an
-                           * exit for the first branch */
-                          { point: 1, exits: [2, 3] },
-                          { point: 3, exits: [4, 5] },
-                          /* 'else' by itself is not executable,
-                           * it is the block it contains whcih
-                           * is */
-                          { point: 5, exits: [6, 8] }
-                      ],
-                      branchInfoEqual);
-}
-
-
-function testSimpleTwoExitBranchWithoutBlocks() {
-    let foundBranches =
-        parseScriptForBranches("let a, b;\n" +
-                               "if (1)\n" +
-                               "    a = 1.0\n" +
-                               "else\n" +
-                               "    b = 2.0\n" +
-                               "\n");
-    assertArrayEquals(foundBranches,
-                      [
-                          { point: 2, exits: [3, 5] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testNoBranchFoundIfConsequentWasEmpty() {
-    let foundBranches =
-        parseScriptForBranches("let a, b;\n" +
-                               "if (1) {}\n");
-    assertArrayEquals(foundBranches,
-                      [],
-                      branchInfoEqual);
-}
-
-function testSingleExitFoundIfOnlyAlternateExitDefined() {
-    let foundBranchesForOnlyAlternateDefinition =
-        parseScriptForBranches("let a, b;\n" +
-                               "if (1) {}\n" +
-                               "else\n" +
-                               "    a++;\n");
-    assertArrayEquals(foundBranchesForOnlyAlternateDefinition,
-                      [
-                          { point: 2, exits: [4] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testImplicitBranchFoundForWhileStatement() {
-    let foundBranchesForWhileStatement =
-        parseScriptForBranches("while (1) {\n" +
-                               "    let a = 1;\n" +
-                               "}\n" +
-                               "let b = 2;");
-    assertArrayEquals(foundBranchesForWhileStatement,
-                      [
-                          { point: 1, exits: [2] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testImplicitBranchFoundForDoWhileStatement() {
-    let foundBranchesForDoWhileStatement =
-        parseScriptForBranches("do {\n" +
-                               "    let a = 1;\n" +
-                               "} while (1)\n" +
-                               "let b = 2;");
-    assertArrayEquals(foundBranchesForDoWhileStatement,
-                      [
-                          /* For do-while loops the branch-point is
-                           * at the 'do' condition and not the
-                           * 'while' */
-                          { point: 1, exits: [2] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testAllExitsFoundForCaseStatements() {
-    let foundExitsInCaseStatement =
-        parseScriptForBranches("let a = 1;\n" +
-                               "switch (1) {\n" +
-                               "case '1':\n" +
-                               "    a++;\n" +
-                               "    break;\n" +
-                               "case '2':\n" +
-                               "    a++\n" +
-                               "    break;\n" +
-                               "default:\n" +
-                               "    a++\n" +
-                               "    break;\n" +
-                               "}\n");
-    assertArrayEquals(foundExitsInCaseStatement,
-                      [
-                          /* There are three potential exits here */
-                          { point: 2, exits: [4, 7, 10] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testAllExitsFoundForFallthroughCaseStatements() {
-    let foundExitsInCaseStatement =
-        parseScriptForBranches("let a = 1;\n" +
-                               "switch (1) {\n" +
-                               "case '1':\n" +
-                               "case 'a':\n" +
-                               "case 'b':\n" +
-                               "    a++;\n" +
-                               "    break;\n" +
-                               "case '2':\n" +
-                               "    a++\n" +
-                               "    break;\n" +
-                               "default:\n" +
-                               "    a++\n" +
-                               "    break;\n" +
-                               "}\n");
-    assertArrayEquals(foundExitsInCaseStatement,
-                      [
-                          /* There are three potential exits here */
-                          { point: 2, exits: [6, 9, 12] }
-                      ],
-                      branchInfoEqual);
-}
-
-function testAllNoExitsFoundForCaseStatementsWithNoopLabels() {
-    let foundExitsInCaseStatement =
-        parseScriptForBranches("let a = 1;\n" +
-                               "switch (1) {\n" +
-                               "case '1':\n" +
-                               "case '2':\n" +
-                               "default:\n" +
-                               "}\n");
-    assertArrayEquals(foundExitsInCaseStatement,
-                      [],
-                      branchInfoEqual);
-}
-
-
-function testGetNumberOfLinesInScript() {
-    let script = "\n\n";
-    let number = Coverage._getNumberOfLinesForScript(script);
-    JSUnit.assertEquals(3, number);
-}
-
-function testZeroExpressionLinesToCounters() {
-    let expressionLines = [];
-    let nLines = 1;
-    let counters = Coverage._expressionLinesToCounters(expressionLines, nLines);
-
-    assertArrayEquals([undefined, undefined], counters, JSUnit.assertEquals);
-}
-
-function testSingleExpressionLineToCounters() {
-    let expressionLines = [1, 2];
-    let nLines = 4;
-    let counters = Coverage._expressionLinesToCounters(expressionLines, nLines);
-
-    assertArrayEquals([undefined, 0, 0, undefined, undefined],
-                      counters, JSUnit.assertEquals);
-}
-
-const MockFoundBranches = [
-    {
-        point: 5,
-        exits: [6, 8]
-    },
-    {
-        point: 1,
-        exits: [2, 4]
-    }
-];
-
-const MockNLines = 9;
-
-function testGetsSameNumberOfCountersAsNLinesPlusOne() {
-    let counters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    JSUnit.assertEquals(MockNLines + 1, counters.length);
-}
-
-function testEmptyArrayReturnedForNoBranches() {
-    let counters = Coverage._branchesToBranchCounters([], 1);
-    assertArrayEquals([undefined, undefined], counters, JSUnit.assertEquals);
-}
-
-function testBranchesOnLinesForArrayIndicies() {
-    let counters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    JSUnit.assertNotEquals(undefined, counters[1]);
-    JSUnit.assertNotEquals(undefined, counters[5]);
-}
-
-function testExitsSetForBranch() {
-    let counters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    let countersForFirstBranch = counters[1];
-
-    assertArrayEquals(countersForFirstBranch.exits,
-                      [
-                          { line: 2, hitCount: 0 },
-                          { line: 4, hitCount: 0 }
-                      ],
-                      function(expectedExit, actualExit) {
-                          JSUnit.assertEquals(expectedExit.line, actualExit.line);
-                          JSUnit.assertEquals(expectedExit.hitCount, actualExit.hitCount);
-                      });
-}
-
-function testLastExitIsSetToHighestExitStartLine() {
-    let counters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    let countersForFirstBranch = counters[1];
-
-    JSUnit.assertEquals(4, countersForFirstBranch.lastExit);
-}
-
-function testHitIsAlwaysInitiallyFalse() {
-    let counters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    let countersForFirstBranch = counters[1];
-
-    JSUnit.assertEquals(false, countersForFirstBranch.hit);
-}
-
-function testFunctionForKeyFromFunctionWithNameMatchesSchema() {
-    let expectedFunctionKey = 'f:1:2';
-    let functionKeyForFunctionName =
-        Coverage._getFunctionKeyFromReflectedFunction({
-            id: {
-                name: 'f'
-            },
-            loc: {
-              start: {
-                  line: 1
-              }
-            },
-            params: ['a', 'b']
+describe('Coverage.expressionLinesForAST', function () {
+    let testTable = {
+        'works with no trailing newline': [
+            "let x;\n" +
+            "let y;",
+            [1, 2],
+        ],
+
+        'finds lines on both sides of an assignment expression': [
+            "var x;\n" +
+            "x = (function() {\n" +
+            "    return 10;\n" +
+            "})();\n",
+            [1, 2, 3],
+        ],
+
+        'finds lines inside functions': [
+            "function f(a, b) {\n" +
+            "    let x = a;\n" +
+            "    let y = b;\n" +
+            "    return x + y;\n" +
+            "}\n" +
+            "\n" +
+            "var z = f(1, 2);\n",
+            [2, 3, 4, 7],
+        ],
+
+        'finds lines inside anonymous functions': [
+            "var z = (function f(a, b) {\n" +
+            "     let x = a;\n" +
+            "     let y = b;\n" +
+            "     return x + y;\n" +
+            " })();\n",
+            [1, 2, 3, 4],
+        ],
+
+        'finds lines inside body of function property': [
+            "var o = {\n" +
+            "    foo: function() {\n" +
+            "        let x = a;\n" +
+            "    }\n" +
+            "};\n",
+            [1, 2, 3],
+        ],
+
+        'finds lines inside arguments of function property': [
+            "function f(a) {\n" +
+            "}\n" +
+            "f({\n" +
+            "    foo: function() {\n" +
+            "        let x = a;\n" +
+            "    }\n" +
+            "});\n",
+            [1, 3, 4, 5],
+        ],
+
+        'finds lines inside multiline function arguments': [
+            "function f(a, b, c) {\n" +
+            "}\n" +
+            "f(1,\n" +
+            "  2,\n" +
+            "  3);\n",
+            [1, 3, 4, 5],
+        ],
+
+        'finds lines inside function argument that is an object': [
+            "function f(o) {\n" +
+            "}\n" +
+            "let obj = {\n" +
+            "    Name: new f({ a: 1,\n" +
+            "                  b: 2,\n" +
+            "                  c: 3\n" +
+            "                })\n" +
+            "}\n",
+            [1, 3, 4, 5, 6],
+        ],
+
+        'finds lines inside a while loop': [
+            "var a = 0;\n" +
+            "while (a < 1) {\n" +
+            "    let x = 0;\n" +
+            "    let y = 1;\n" +
+            "    a++;" +
+            "\n" +
+            "}\n",
+            [1, 2, 3, 4, 5],
+        ],
+
+        'finds lines inside try, catch, and finally': [
+            "var a = 0;\n" +
+            "try {\n" +
+            "    a++;\n" +
+            "} catch (e) {\n" +
+            "    a++;\n" +
+            "} finally {\n" +
+            "    a++;\n" +
+            "}\n",
+            [1, 2, 3, 4, 5, 7],
+        ],
+
+        'finds lines inside case statements': [
+            "var a = 0;\n" +
+            "switch (a) {\n" +
+            "case 1:\n" +
+            "    a++;\n" +
+            "    break;\n" +
+            "case 2:\n" +
+            "    a++;\n" +
+            "    break;\n" +
+            "}\n",
+            [1, 2, 4, 5, 7, 8],
+        ],
+
+        'finds lines inside case statements with character cases': [
+            "var a = 'a';\n" +
+            "switch (a) {\n" +
+            "case 'a':\n" +
+            "    a++;\n" +
+            "    break;\n" +
+            "case 'b':\n" +
+            "    a++;\n" +
+            "    break;\n" +
+            "}\n",
+            [1, 2, 4, 5, 7, 8],
+        ],
+
+        'finds lines inside a for loop': [
+            "for (let i = 0; i < 1; i++) {\n" +
+            "    let x = 0;\n" +
+            "    let y = 1;\n" +
+            "\n" +
+            "}\n",
+            [1, 2, 3],
+        ],
+
+        'finds lines inside if-statement branches': [
+            "if (1 > 0) {\n" +
+            "    let i = 0;\n" +
+            "} else {\n" +
+            "    let j = 1;\n" +
+            "}\n",
+            [1, 2, 4],
+        ],
+
+        'finds all lines of multiline if-conditions': [
+            "if (1 > 0 &&\n" +
+            "    2 > 0 &&\n" +
+            "    3 > 0) {\n" +
+            "    let a = 3;\n" +
+            "}\n",
+            [1, 2, 3, 4],
+        ],
+
+        'finds lines for object property literals': [
+            "var a = {\n" +
+            "    Name: 'foo',\n" +
+            "    Ex: 'bar'\n" +
+            "};\n",
+            [1, 2, 3],
+        ],
+
+        'finds lines for function-valued object properties': [
+            "var a = {\n" +
+            "    Name: function() {},\n" +
+            "};\n",
+            [1, 2],
+        ],
+
+        'finds lines inside object-valued object properties': [
+            "var a = {\n" +
+            "    Name: {},\n" +
+            "};\n",
+            [1, 2],
+        ],
+
+        'finds lines inside array-valued object properties': [
+            "var a = {\n" +
+            "    Name: [],\n" +
+            "};\n",
+            [1, 2],
+        ],
+
+        'finds lines inside object-valued argument to return statement': [
+            "function f() {\n" +
+            "    return {};\n" +
+            "}\n",
+            [2],
+        ],
+
+        'finds lines inside object-valued argument to throw statement': [
+            "function f() {\n" +
+            "    throw {\n" +
+            "        a: 1,\n" +
+            "        b: 2\n" +
+            "    }\n" +
+            "}\n",
+            [2, 3, 4],
+        ],
+    };
+
+    Object.keys(testTable).forEach(testcase => {
+        it(testcase, function () {
+            const ast = Reflect.parse(testTable[testcase][0]);
+            let foundLines = Coverage.expressionLinesForAST(ast);
+            expect(foundLines).toEqual(testTable[testcase][1]);
         });
+    });
+});
+
+describe('Coverage.functionsForAST', function () {
+    let testTable = {
+        'works with no trailing newline': [
+            "function f1() {}\n" +
+            "function f2() {}",
+            [
+                { key: "f1:1:0", line: 1, n_params: 0 },
+                { key: "f2:2:0", line: 2, n_params: 0 },
+            ],
+        ],
+
+        'finds functions': [
+            "function f1() {}\n" +
+            "function f2() {}\n" +
+            "function f3() {}\n",
+            [
+                { key: "f1:1:0", line: 1, n_params: 0 },
+                { key: "f2:2:0", line: 2, n_params: 0 },
+                { key: "f3:3:0", line: 3, n_params: 0 }
+            ],
+        ],
+
+        'finds nested functions': [
+            "function f1() {\n" +
+            "    let f2 = function() {\n" +
+            "        let f3 = function() {\n" +
+            "        }\n" +
+            "    }\n" +
+            "}\n",
+            [
+                { key: "f1:1:0", line: 1, n_params: 0 },
+                { key: "(anonymous):2:0", line: 2, n_params: 0 },
+                { key: "(anonymous):3:0", line: 3, n_params: 0 }
+            ],
+        ],
+
+        /* Note the lack of newlines. This is all on one line */
+        'finds functions on the same line but with different arguments': [
+            "function f1() {" +
+            "    return (function(a) {" +
+            "        return function(a, b) {}" +
+            "    });" +
+            "}",
+            [
+                { key: "f1:1:0", line: 1, n_params: 0 },
+                { key: "(anonymous):1:1", line: 1, n_params: 1 },
+                { key: "(anonymous):1:2", line: 1, n_params: 2 }
+            ],
+        ],
+
+        'finds functions inside an array expression': [
+            "let a = [function() {}];\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 },
+            ],
+        ],
+
+        'finds functions inside an arrow expression': [
+            "(a) => (function() {})();\n",
+            [
+                { key: "(anonymous):1:1", line: 1, n_params: 1 },
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside a sequence': [
+            "(function(a) {})()," +
+            "(function(a, b) {})();\n",
+            [
+                { key: "(anonymous):1:1", line: 1, n_params: 1 },
+                { key: "(anonymous):1:2", line: 1, n_params: 2 },
+            ],
+        ],
+
+        'finds functions inside a unary expression': [
+            "let a = (function() {}())++;\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 },
+            ],
+        ],
+
+        'finds functions inside a binary expression': [
+            "let a = function(a) {}() +" +
+            " function(a, b) {}();\n",
+            [
+                { key: "(anonymous):1:1", line: 1, n_params: 1 },
+                { key: "(anonymous):1:2", line: 1, n_params: 2 }
+            ],
+        ],
+
+        'finds functions inside an assignment expression': [
+            "let a = function() {}();\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside a reflexive assignment expression': [
+            "let a;\n" +
+            "a += function() {}();\n",
+            [
+                { key: "(anonymous):2:0", line: 2, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside if-statement conditions': [
+            "if (function(a) {}(a) >" +
+            "    function(a, b) {}(a, b)) {}\n",
+            [
+                { key: "(anonymous):1:1", line: 1, n_params: 1 },
+                { key: "(anonymous):1:2", line: 1, n_params: 2 }
+            ],
+        ],
+
+        'finds functions inside while-statement conditions': [
+            "while (function(a) {}(a) >" +
+            "       function(a, b) {}(a, b)) {};\n",
+            [
+                { key: "(anonymous):1:1", line: 1, n_params: 1 },
+                { key: "(anonymous):1:2", line: 1, n_params: 2 }
+            ],
+        ],
+
+        'finds functions inside for-statement initializer': [
+            "for (function() {}; ;) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        /* SpiderMonkey parses for (let i = <init>; <cond>; <update>) as though
+         * they were let i = <init> { for (; <cond> <update>) } so test the
+         * LetStatement initializer case too */
+        'finds functions inside let-statement in for-statement initializer': [
+            "for (let i = function() {}; ;) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside var-statement inside for-statement initializer': [
+            "for (var i = function() {}; ;) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside for-statement condition': [
+            "for (; function() {}();) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside for-statement increment': [
+            "for (; ;function() {}()) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside for-in-statement': [
+            "for (let x in function() {}()) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside for-each statement': [
+            "for each (x in function() {}()) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions inside for-of statement': [
+            "for (x of (function() {}())) {}\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds function literals used as an object': [
+            "f = function() {}.bind();\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds function literals used as an object in a dynamic property expression': [
+            "f = function() {}['bind']();\n",
+            [
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions on either side of a logical expression': [
+            "let f = function(a) {} ||" +
+            " function(a, b) {};\n",
+            [
+                { key: "(anonymous):1:1", line: 1, n_params: 1 },
+                { key: "(anonymous):1:2", line: 1, n_params: 2 }
+            ],
+        ],
+
+        'finds functions on either side of a conditional expression': [
+            "let a\n" +
+            "let f = a ? function(a) {}() :" +
+            " function(a, b) {}();\n",
+            [
+                { key: "(anonymous):2:1", line: 2, n_params: 1 },
+                { key: "(anonymous):2:2", line: 2, n_params: 2 }
+            ],
+        ],
+
+        'finds functions as the argument of a yield statement': [
+            "function a() { yield function (){} };\n",
+            [
+                { key: "a:1:0", line: 1, n_params: 0 },
+                { key: "(anonymous):1:0", line: 1, n_params: 0 }
+            ],
+        ],
+
+        'finds functions in an array comprehension body': [
+            "let a = new Array(1);\n" +
+            "let b = [function() {} for (i of a)];\n",
+            [
+                { key: "(anonymous):2:0", line: 2, n_params: 0 }
+            ],
+        ],
+
+        'finds functions in an array comprehension block': [
+            "let a = new Array(1);\n" +
+            "let b = [i for (i of function() {})];\n",
+            [
+                { key: "(anonymous):2:0", line: 2, n_params: 0 }
+            ],
+        ],
+
+        'finds functions in an array comprehension filter': [
+            "let a = new Array(1);\n" +
+            "let b = [i for (i of a)" +
+            "if (function() {}())];\n",
+            [
+                { key: "(anonymous):2:0", line: 2, n_params: 0 }
+            ],
+        ],
+    };
 
-    JSUnit.assertEquals(expectedFunctionKey, functionKeyForFunctionName);
-}
-
-function testFunctionKeyFromFunctionWithoutNameIsAnonymous() {
-    let expectedFunctionKey = '(anonymous):2:3';
-    let functionKeyForAnonymousFunction =
-        Coverage._getFunctionKeyFromReflectedFunction({
-            id: null,
-            loc: {
-              start: {
-                  line: 2
-              }
-            },
-            params: ['a', 'b', 'c']
+    Object.keys(testTable).forEach(testcase => {
+        it(testcase, function () {
+            const ast = Reflect.parse(testTable[testcase][0]);
+            let foundFuncs = Coverage.functionsForAST(ast);
+            expect(foundFuncs).toEqual(testTable[testcase][1]);
         });
+    });
+});
+
+describe('Coverage.branchesForAST', function () {
+    let testTable = {
+        'works with no trailing newline': [
+            "if (1) { let a = 1; }",
+            [
+                { point: 1, exits: [1] },
+            ],
+        ],
+
+        'finds both branch exits for a simple branch': [
+            "if (1) {\n" +
+            "    let a = 1;\n" +
+            "} else {\n" +
+            "    let b = 2;\n" +
+            "}\n",
+            [
+                { point: 1, exits: [2, 4] }
+            ],
+        ],
+
+        'finds a single exit for a branch with one consequent': [
+            "if (1) {\n" +
+            "    let a = 1.0;\n" +
+            "}\n",
+            [
+                { point: 1, exits: [2] }
+            ],
+        ],
+
+        'finds multiple exits for nested if-else branches': [
+            "if (1) {\n" +
+            "    let a = 1.0;\n" +
+            "} else if (2) {\n" +
+            "    let b = 2.0;\n" +
+            "} else if (3) {\n" +
+            "    let c = 3.0;\n" +
+            "} else {\n" +
+            "    let d = 4.0;\n" +
+            "}\n",
+            [
+                // the 'else if' line is actually an exit for the first branch
+                { point: 1, exits: [2, 3] },
+                { point: 3, exits: [4, 5] },
+                // 'else' by itself is not executable, it is the block it
+                // contains which is
+                { point: 5, exits: [6, 8] }
+            ],
+        ],
+
+        'finds a simple two-exit branch without blocks': [
+            "let a, b;\n" +
+            "if (1)\n" +
+            "    a = 1.0\n" +
+            "else\n" +
+            "    b = 2.0\n" +
+            "\n",
+            [
+                { point: 2, exits: [3, 5] }
+            ],
+        ],
+
+        'does not find a branch if the consequent was empty': [
+            "let a, b;\n" +
+            "if (1) {}\n",
+            [],
+        ],
+
+        'finds a single exit if only the alternate exit was defined': [
+            "let a, b;\n" +
+            "if (1) {}\n" +
+            "else\n" +
+            "    a++;\n",
+            [
+                { point: 2, exits: [4] }
+            ],
+        ],
+
+        'finds an implicit branch for while statement': [
+            "while (1) {\n" +
+            "    let a = 1;\n" +
+            "}\n" +
+            "let b = 2;",
+            [
+                { point: 1, exits: [2] }
+            ],
+        ],
+
+        'finds an implicit branch for a do-while statement': [
+            "do {\n" +
+            "    let a = 1;\n" +
+            "} while (1)\n" +
+            "let b = 2;",
+            [
+                // For do-while loops the branch-point is at the 'do' condition
+                // and not the 'while'
+                { point: 1, exits: [2] }
+            ],
+        ],
+
+        'finds all exits for case statements': [
+            "let a = 1;\n" +
+            "switch (1) {\n" +
+            "case '1':\n" +
+            "    a++;\n" +
+            "    break;\n" +
+            "case '2':\n" +
+            "    a++\n" +
+            "    break;\n" +
+            "default:\n" +
+            "    a++\n" +
+            "    break;\n" +
+            "}\n",
+            [
+                /* There are three potential exits here */
+                { point: 2, exits: [4, 7, 10] }
+            ],
+        ],
+
+        'finds all exits for case statements with fallthrough': [
+            "let a = 1;\n" +
+            "switch (1) {\n" +
+            "case '1':\n" +
+            "case 'a':\n" +
+            "case 'b':\n" +
+            "    a++;\n" +
+            "    break;\n" +
+            "case '2':\n" +
+            "    a++\n" +
+            "    break;\n" +
+            "default:\n" +
+            "    a++\n" +
+            "    break;\n" +
+            "}\n",
+            [
+                /* There are three potential exits here */
+                { point: 2, exits: [6, 9, 12] }
+            ],
+        ],
+
+        'finds no exits for case statements with only no-ops': [
+            "let a = 1;\n" +
+            "switch (1) {\n" +
+            "case '1':\n" +
+            "case '2':\n" +
+            "default:\n" +
+            "}\n",
+            [],
+        ],
+    };
 
-    JSUnit.assertEquals(expectedFunctionKey, functionKeyForAnonymousFunction);
-}
+    Object.keys(testTable).forEach(testcase => {
+        it(testcase, function () {
+            const ast = Reflect.parse(testTable[testcase][0]);
+            let foundBranchExits = Coverage.branchesForAST(ast);
+            expect(foundBranchExits).toEqual(testTable[testcase][1]);
+        });
+    });
+});
 
-function testFunctionCounterMapReturnedForFunctionKeys() {
-    let ast = {
-        body: [{
-            type: 'FunctionDeclaration',
-            id: {
-                name: 'name'
-            },
-            loc: {
-              start: {
-                  line: 1
-              }
-            },
-            params: [],
-            body: {
-                type: 'BlockStatement',
-                body: []
-            }
-        }]
-    };
+describe('Coverage', function () {
+    it('gets the number of lines in the script', function () {
+        let script = "\n\n";
+        let number = Coverage._getNumberOfLinesForScript(script);
+        expect(number).toEqual(3);
+    });
 
-    let detectedFunctions = Coverage.functionsForAST(ast);
-    let functionCounters = Coverage._functionsToFunctionCounters('script',
-                                                                 detectedFunctions);
+    it('turns zero expression lines into counters', function () {
+        let expressionLines = [];
+        let nLines = 1;
+        let counters = Coverage._expressionLinesToCounters(expressionLines, nLines);
 
-    JSUnit.assertEquals(0, functionCounters.name['1']['0'].hitCount);
-}
+        expect(counters).toEqual([undefined, undefined]);
+    });
 
-function _fetchLogMessagesFrom(func) {
-    let oldLog = window.log;
-    let collectedMessages = [];
-    window.log = function(message) {
-        collectedMessages.push(message);
-    };
+    it('turns a single expression line into counters', function () {
+        let expressionLines = [1, 2];
+        let nLines = 4;
+        let counters = Coverage._expressionLinesToCounters(expressionLines, nLines);
 
-    try {
-        func.apply(this, arguments);
-    } finally {
-        window.log = oldLog;
-    }
-
-    return collectedMessages;
-}
-
-function testErrorReportedWhenTwoIndistinguishableFunctionsPresent() {
-    let ast = {
-        body: [{
-            type: 'FunctionDeclaration',
-            id: {
-                name: '(anonymous)'
-            },
-            loc: {
-              start: {
-                  line: 1
-              }
-            },
-            params: [],
-            body: {
-                type: 'BlockStatement',
-                body: []
-            }
-        }, {
-            type: 'FunctionDeclaration',
-            id: {
-                name: '(anonymous)'
-            },
-            loc: {
-              start: {
-                  line: 1
-              }
-            },
-            params: [],
-            body: {
-                type: 'BlockStatement',
-                body: []
-            }
-        }]
-    };
+        expect(counters).toEqual([undefined, 0, 0, undefined, undefined]);
+    });
 
-    let detectedFunctions = Coverage.functionsForAST(ast);
-    let messages = _fetchLogMessagesFrom(function() {
-        Coverage._functionsToFunctionCounters('script', detectedFunctions);
+    it('returns empty array for no branches', function () {
+        let counters = Coverage._branchesToBranchCounters([], 1);
+        expect(counters).toEqual([undefined, undefined]);
     });
 
-    JSUnit.assertEquals('script:1 Function identified as (anonymous):1:0 ' +
-                        'already seen in this file. Function coverage will ' +
-                        'be incomplete.',
-                        messages[0]);
-}
-
-function testKnownFunctionsArrayPopulatedForFunctions() {
-    let functions = [
-        { line: 1 },
-        { line: 2 }
-    ];
-
-    let knownFunctionsArray = Coverage._populateKnownFunctions(functions, 4);
-
-    assertArrayEquals(knownFunctionsArray,
-                      [undefined, true, true, undefined, undefined],
-                      JSUnit.assertEquals);
-}
-
-function testIncrementFunctionCountersForFunctionOnSameExecutionStartLine() {
-    let functionCounters = Coverage._functionsToFunctionCounters('script', [
-        { key: 'f:1:0',
-          line: 1,
-          n_params: 0 }
-    ]);
-    Coverage._incrementFunctionCounters(functionCounters, null, 'f', 1, 0);
-
-    JSUnit.assertEquals(functionCounters.f['1']['0'].hitCount, 1);
-}
-
-function testIncrementFunctionCountersCanDisambiguateTwoFunctionsWithSameName() {
-    let functionCounters = Coverage._functionsToFunctionCounters('script', [
-        { key: '(anonymous):1:0',
-          line: 1,
-          n_params: 0 },
-        { key: '(anonymous):2:0',
-          line: 2,
-          n_params: 0 }
-    ]);
-    Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 0);
-    Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 2, 0);
-
-    JSUnit.assertEquals(functionCounters['(anonymous)']['1']['0'].hitCount, 1);
-    JSUnit.assertEquals(functionCounters['(anonymous)']['2']['0'].hitCount, 1);
-}
-
-function testIncrementFunctionCountersCanDisambiguateTwoFunctionsOnSameLineWithDifferentParams() {
-    let functionCounters = Coverage._functionsToFunctionCounters('script', [
-        { key: '(anonymous):1:0',
-          line: 1,
-          n_params: 0 },
-        { key: '(anonymous):1:1',
-          line: 1,
-          n_params: 1 }
-    ]);
-    Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 0);
-    Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 1);
-
-    JSUnit.assertEquals(functionCounters['(anonymous)']['1']['0'].hitCount, 1);
-    JSUnit.assertEquals(functionCounters['(anonymous)']['1']['1'].hitCount, 1);
-}
-
-function testIncrementFunctionCountersCanDisambiguateTwoFunctionsOnSameLineByGuessingClosestParams() {
-    let functionCounters = Coverage._functionsToFunctionCounters('script', [
-        { key: '(anonymous):1:0',
-          line: 1,
-          n_params: 0 },
-        { key: '(anonymous):1:3',
-          line: 1,
-          n_params: 3 }
-    ]);
-
-    /* Eg, we called the function with 3 params with just two arguments. We
-     * should be able to work out that we probably intended to call the
-     * latter function as opposed to the former. */
-    Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 2);
-
-    JSUnit.assertEquals(functionCounters['(anonymous)']['1']['0'].hitCount, 0);
-    JSUnit.assertEquals(functionCounters['(anonymous)']['1']['3'].hitCount, 1);
-}
-
-function testIncrementFunctionCountersForFunctionOnEarlierStartLine() {
-    let ast = {
-        body: [{
-            type: 'FunctionDeclaration',
-            id: {
-                name: 'name'
+    describe('branch counters', function () {
+        const MockFoundBranches = [
+            {
+                point: 5,
+                exits: [6, 8]
             },
-            loc: {
-              start: {
-                  line: 1
-              }
-            },
-            params: [],
-            body: {
-                type: 'BlockStatement',
-                body: []
+            {
+                point: 1,
+                exits: [2, 4]
             }
-        }]
-    };
+        ];
+
+        const MockNLines = 9;
 
-    let detectedFunctions = Coverage.functionsForAST(ast);
-    let knownFunctionsArray = Coverage._populateKnownFunctions(detectedFunctions, 3);
-    let functionCounters = Coverage._functionsToFunctionCounters('script',
-                                                                 detectedFunctions);
+        let counters;
+        beforeEach(function () {
+            counters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
+        });
 
-    /* We're entering at line two, but the function definition was actually
-     * at line one */
-    Coverage._incrementFunctionCounters(functionCounters, knownFunctionsArray, 'name', 2, 0);
+        it('gets same number of counters as number of lines plus one', function () {
+            expect(counters.length).toEqual(MockNLines + 1);
+        });
 
-    JSUnit.assertEquals(functionCounters.name['1']['0'].hitCount, 1);
-}
+        it('branches on lines for array indices', function () {
+            expect(counters[1]).toBeDefined();
+            expect(counters[5]).toBeDefined();
+        });
 
-function testIncrementFunctionCountersThrowsErrorOnUnexpectedFunction() {
-    let ast = {
-        body: [{
-            type: 'FunctionDeclaration',
-            id: {
-                name: 'name'
-            },
-            loc: {
-              start: {
-                  line: 1
-              }
-            },
-            params: [],
-            body: {
-                type: 'BlockStatement',
-                body: []
-            }
-        }]
-    };
-    let detectedFunctions = Coverage.functionsForAST(ast);
-    let functionKey = Coverage._getFunctionKeyFromReflectedFunction(ast.body[0]);
-    let knownFunctionsArray = Coverage._populateKnownFunctions(detectedFunctions, 3);
-    let functionCounters = Coverage._functionsToFunctionCounters('script',
-                                                                 detectedFunctions);
-
-    /* We're entering at line two, but the function definition was actually
-     * at line one */
-    JSUnit.assertRaises(function() {
-        Coverage._incrementFunctionCounters(functionCounters,
-                                            knownFunctionsArray,
-                                            'doesnotexist',
-                                            2,
-                                            0);
+        it('sets exits for branch', function () {
+            expect(counters[1].exits).toEqual([
+                { line: 2, hitCount: 0 },
+                { line: 4, hitCount: 0 },
+            ]);
+        });
+
+        it('sets last exit to highest exit start line', function () {
+            expect(counters[1].lastExit).toEqual(4);
+        });
+
+        it('always has hit initially false', function () {
+            expect(counters[1].hit).toBeFalsy();
+        });
+
+        describe('branch tracker', function () {
+            let branchTracker;
+            beforeEach(function () {
+                branchTracker = new Coverage._BranchTracker(counters);
+            });
+
+            it('sets branch to hit on point execution', function () {
+                branchTracker.incrementBranchCounters(1);
+                expect(counters[1].hit).toBeTruthy();
+            });
+
+            it('sets exit to hit on execution', function () {
+                branchTracker.incrementBranchCounters(1);
+                branchTracker.incrementBranchCounters(2);
+                expect(counters[1].exits[0].hitCount).toEqual(1);
+            });
+
+            it('finds next branch', function () {
+                branchTracker.incrementBranchCounters(1);
+                branchTracker.incrementBranchCounters(2);
+                branchTracker.incrementBranchCounters(5);
+                expect(counters[5].hit).toBeTruthy();
+            });
+        });
     });
-}
 
-function testIncrementExpressionCountersThrowsIfLineOutOfRange() {
-    let expressionCounters = [
-        undefined,
-        0
-    ];
+    it('function key from function with name matches schema', function () {
+        let functionKeyForFunctionName =
+            Coverage._getFunctionKeyFromReflectedFunction({
+                id: {
+                    name: 'f'
+                },
+                loc: {
+                  start: {
+                      line: 1
+                  }
+                },
+                params: ['a', 'b']
+            });
+        expect(functionKeyForFunctionName).toEqual('f:1:2');
+    });
 
-    JSUnit.assertRaises(function() {
-        Coverage._incrementExpressionCounters(expressionCounters, 'script', 2);
+    it('function key from function without name is anonymous', function () {
+        let functionKeyForAnonymousFunction =
+            Coverage._getFunctionKeyFromReflectedFunction({
+                id: null,
+                loc: {
+                  start: {
+                      line: 2
+                  }
+                },
+                params: ['a', 'b', 'c']
+            });
+        expect(functionKeyForAnonymousFunction).toEqual('(anonymous):2:3');
     });
-}
-
-function testIncrementExpressionCountersIncrementsIfInRange() {
-    let expressionCounters = [
-        undefined,
-        0
-    ];
-
-    Coverage._incrementExpressionCounters(expressionCounters, 'script', 1);
-    JSUnit.assertEquals(1, expressionCounters[1]);
-}
-
-function testWarnsIfWeHitANonExecutableLine() {
-    let expressionCounters = [
-        undefined,
-        0,
-        undefined
-    ];
-
-    let messages = _fetchLogMessagesFrom(function() {
-        Coverage._incrementExpressionCounters(expressionCounters, 'script', 2);
+
+    it('returns a function counter map for function keys', function () {
+        let ast = {
+            body: [{
+                type: 'FunctionDeclaration',
+                id: {
+                    name: 'name'
+                },
+                loc: {
+                  start: {
+                      line: 1
+                  }
+                },
+                params: [],
+                body: {
+                    type: 'BlockStatement',
+                    body: []
+                }
+            }]
+        };
+
+        let detectedFunctions = Coverage.functionsForAST(ast);
+        let functionCounters =
+            Coverage._functionsToFunctionCounters('script', detectedFunctions);
+        expect(functionCounters.name['1']['0'].hitCount).toEqual(0);
     });
 
-    JSUnit.assertEquals(messages[0],
-                        "script:2 Executed line previously marked " +
-                        "non-executable by Reflect");
-    JSUnit.assertEquals(expressionCounters[2], 1);
-}
+    it('reports an error when two indistinguishable functions are present', function () {
+        spyOn(window, 'log');
+        let ast = {
+            body: [{
+                type: 'FunctionDeclaration',
+                id: {
+                    name: '(anonymous)'
+                },
+                loc: {
+                  start: {
+                      line: 1
+                  }
+                },
+                params: [],
+                body: {
+                    type: 'BlockStatement',
+                    body: []
+                }
+            }, {
+                type: 'FunctionDeclaration',
+                id: {
+                    name: '(anonymous)'
+                },
+                loc: {
+                  start: {
+                      line: 1
+                  }
+                },
+                params: [],
+                body: {
+                    type: 'BlockStatement',
+                    body: []
+                }
+            }]
+        };
+
+        let detectedFunctions = Coverage.functionsForAST(ast);
+        Coverage._functionsToFunctionCounters('script', detectedFunctions);
 
-function testBranchTrackerSetsBranchToHitOnPointExecution() {
-    let branchCounters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    let branchTracker = new Coverage._BranchTracker(branchCounters);
+        expect(window.log).toHaveBeenCalledWith('script:1 Function ' +
+            'identified as (anonymous):1:0 already seen in this file. ' +
+            'Function coverage will be incomplete.');
+    });
 
-    branchTracker.incrementBranchCounters(1);
+    it('populates a known functions array', function () {
+        let functions = [
+            { line: 1 },
+            { line: 2 }
+        ];
 
-    JSUnit.assertEquals(true, branchCounters[1].hit);
-}
+        let knownFunctionsArray = Coverage._populateKnownFunctions(functions, 4);
 
-function testBranchTrackerSetsExitToHitOnExecution() {
-    let branchCounters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    let branchTracker = new Coverage._BranchTracker(branchCounters);
+        expect(knownFunctionsArray)
+            .toEqual([undefined, true, true, undefined, undefined]);
+    });
 
-    branchTracker.incrementBranchCounters(1);
-    branchTracker.incrementBranchCounters(2);
+    it('converts function counters to an array', function () {
+        let functionsMap = {
+            '(anonymous)': {
+                '2': {
+                    '0': {
+                        hitCount: 1
+                    },
+                },
+            },
+            'name': {
+                '1': {
+                    '0': {
+                        hitCount: 0
+                    },
+                },
+            }
+        };
 
-    JSUnit.assertEquals(1, branchCounters[1].exits[0].hitCount);
-}
+        let expectedFunctionCountersArray = [
+            jasmine.objectContaining({ name: '(anonymous):2:0', hitCount: 1 }),
+            jasmine.objectContaining({ name: 'name:1:0', hitCount: 0 })
+        ];
 
-function testBranchTrackerFindsNextBranch() {
-    let branchCounters = Coverage._branchesToBranchCounters(MockFoundBranches, MockNLines);
-    let branchTracker = new Coverage._BranchTracker(branchCounters);
+        let convertedFunctionCounters = Coverage._convertFunctionCountersToArray(functionsMap);
 
-    branchTracker.incrementBranchCounters(1);
-    branchTracker.incrementBranchCounters(2);
-    branchTracker.incrementBranchCounters(5);
+        expect(convertedFunctionCounters).toEqual(expectedFunctionCountersArray);
+    });
+});
+
+describe('Coverage.incrementFunctionCounters', function () {
+    it('increments for function on same execution start line', function () {
+        let functionCounters = Coverage._functionsToFunctionCounters('script', [
+            { key: 'f:1:0',
+              line: 1,
+              n_params: 0 }
+        ]);
+        Coverage._incrementFunctionCounters(functionCounters, null, 'f', 1, 0);
+
+        expect(functionCounters.f['1']['0'].hitCount).toEqual(1);
+    });
 
-    JSUnit.assertEquals(true, branchCounters[5].hit);
-}
+    it('can disambiguate two functions with the same name', function () {
+        let functionCounters = Coverage._functionsToFunctionCounters('script', [
+            { key: '(anonymous):1:0',
+              line: 1,
+              n_params: 0 },
+            { key: '(anonymous):2:0',
+              line: 2,
+              n_params: 0 }
+        ]);
+        Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 0);
+        Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 2, 0);
+
+        expect(functionCounters['(anonymous)']['1']['0'].hitCount).toEqual(1);
+        expect(functionCounters['(anonymous)']['2']['0'].hitCount).toEqual(1);
+    });
 
-function testConvertFunctionCountersToArray() {
-    let functionsMap = {
-        '(anonymous)': {
-            '2': {
-                '0': {
-                    hitCount: 1
+    it('can disambiguate two functions on same line with different params', function () {
+        let functionCounters = Coverage._functionsToFunctionCounters('script', [
+            { key: '(anonymous):1:0',
+              line: 1,
+              n_params: 0 },
+            { key: '(anonymous):1:1',
+              line: 1,
+              n_params: 1 }
+        ]);
+        Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 0);
+        Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 1);
+
+        expect(functionCounters['(anonymous)']['1']['0'].hitCount).toEqual(1);
+        expect(functionCounters['(anonymous)']['1']['1'].hitCount).toEqual(1);
+    });
+
+    it('can disambiguate two functions on same line by guessing closest params', function () {
+        let functionCounters = Coverage._functionsToFunctionCounters('script', [
+            { key: '(anonymous):1:0',
+              line: 1,
+              n_params: 0 },
+            { key: '(anonymous):1:3',
+              line: 1,
+              n_params: 3 }
+        ]);
+
+        /* Eg, we called the function with 3 params with just two arguments. We
+         * should be able to work out that we probably intended to call the
+         * latter function as opposed to the former. */
+        Coverage._incrementFunctionCounters(functionCounters, null, '(anonymous)', 1, 2);
+
+        expect(functionCounters['(anonymous)']['1']['0'].hitCount).toEqual(0);
+        expect(functionCounters['(anonymous)']['1']['3'].hitCount).toEqual(1);
+    });
+
+    it('increments for function on earlier start line', function () {
+        let ast = {
+            body: [{
+                type: 'FunctionDeclaration',
+                id: {
+                    name: 'name'
                 },
-            },
-        },
-        'name': {
-            '1': {
-                '0': {
-                    hitCount: 0
+                loc: {
+                  start: {
+                      line: 1
+                  }
                 },
-            },
-        }
-    };
+                params: [],
+                body: {
+                    type: 'BlockStatement',
+                    body: []
+                }
+            }]
+        };
+
+        let detectedFunctions = Coverage.functionsForAST(ast);
+        let knownFunctionsArray = Coverage._populateKnownFunctions(detectedFunctions, 3);
+        let functionCounters = Coverage._functionsToFunctionCounters('script',
+                                                                     detectedFunctions);
+
+        /* We're entering at line two, but the function definition was actually
+         * at line one */
+        Coverage._incrementFunctionCounters(functionCounters, knownFunctionsArray, 'name', 2, 0);
+
+        expect(functionCounters.name['1']['0'].hitCount).toEqual(1);
+    });
 
-    let expectedFunctionCountersArray = [
-        { name: '(anonymous):2:0', hitCount: 1 },
-        { name: 'name:1:0', hitCount: 0 }
-    ];
-
-    let convertedFunctionCounters = Coverage._convertFunctionCountersToArray(functionsMap);
-
-    assertArrayEquals(expectedFunctionCountersArray,
-                      convertedFunctionCounters,
-                      function(expected, actual) {
-                          JSUnit.assertEquals(expected.name, actual.name);
-                          JSUnit.assertEquals(expected.hitCount, actual.hitCount);
-                      });
-}
-
-function testConvertFunctionCountersToArrayIsSorted() {
-    let functionsMap = {
-        '(anonymous)': {
-            '2': {
-                '0': {
-                    hitCount: 1
+    it('throws an error on unexpected function', function () {
+        let ast = {
+            body: [{
+                type: 'FunctionDeclaration',
+                id: {
+                    name: 'name'
                 },
-            },
-        },
-        'name': {
-            '1': {
-                '0': {
-                    hitCount: 0
+                loc: {
+                  start: {
+                      line: 1
+                  }
                 },
-            },
-        }
-    };
+                params: [],
+                body: {
+                    type: 'BlockStatement',
+                    body: []
+                }
+            }]
+        };
+        let detectedFunctions = Coverage.functionsForAST(ast);
+        let knownFunctionsArray = Coverage._populateKnownFunctions(detectedFunctions, 3);
+        let functionCounters = Coverage._functionsToFunctionCounters('script',
+                                                                     detectedFunctions);
+
+        /* We're entering at line two, but the function definition was actually
+         * at line one */
+        expect(() => {
+            Coverage._incrementFunctionCounters(functionCounters,
+                                                knownFunctionsArray,
+                                                'doesnotexist',
+                                                2,
+                                                0);
+        }).toThrow();
+    });
+
+    it('throws if line out of range', function () {
+        let expressionCounters = [
+            undefined,
+            0
+        ];
+
+        expect(() => {
+            Coverage._incrementExpressionCounters(expressionCounters, 'script', 2);
+        }).toThrow();
+    });
+
+    it('increments if in range', function () {
+        let expressionCounters = [
+            undefined,
+            0
+        ];
 
-    let expectedFunctionCountersArray = [
-        { name: '(anonymous):2:0', hitCount: 1 },
-        { name: 'name:1:0', hitCount: 0 }
-    ];
-
-    let convertedFunctionCounters = Coverage._convertFunctionCountersToArray(functionsMap);
-
-    assertArrayEquals(expectedFunctionCountersArray,
-                      convertedFunctionCounters,
-                      function(expected, actual) {
-                          JSUnit.assertEquals(expected.name, actual.name);
-                          JSUnit.assertEquals(expected.hitCount, actual.hitCount);
-                      });
-}
-
-const MockFiles = {
-    'filename': "function f() {\n" +
-                "    return 1;\n" +
-                "}\n" +
-                "if (f())\n" +
-                "    f = 0;\n" +
-                "\n",
-    'uncached': "function f() {\n" +
-                "    return 1;\n" +
-                "}\n"
-};
-
-const MockFilenames = (function() {
-    let keys = Object.keys(MockFiles);
-    keys.push('nonexistent');
-    return keys;
-})();
-
-Coverage.getFileContents = function(filename) {
-    if (MockFiles[filename])
-        return MockFiles[filename];
-    return undefined;
-};
-
-Coverage.getFileChecksum = function(filename) {
-    return "abcd";
-};
-
-Coverage.getFileModificationTime = function(filename) {
-    return [1, 2];
-};
-
-function testCoverageStatisticsContainerFetchesValidStatisticsForFile() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames);
-
-    let statistics = container.fetchStatistics('filename');
-    JSUnit.assertNotEquals(undefined, statistics);
-
-    let files = container.getCoveredFiles();
-    assertArrayEquals(files, ['filename'], JSUnit.assertEquals);
-}
-
-function testCoverageStatisticsContainerThrowsForNonExistingFile() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames);
-
-    JSUnit.assertRaises(function() {
-        container.fetchStatistics('nonexistent');
+        Coverage._incrementExpressionCounters(expressionCounters, 'script', 1);
+        expect(expressionCounters[1]).toEqual(1);
     });
-}
-
-const MockCache = '{ \
-    "filename": { \
-        "mtime": [1, 2], \
-        "checksum": null, \
-        "lines": [2, 4, 5], \
-        "branches": [ \
-            { \
-                "point": 4, \
-                "exits": [5] \
-            } \
-        ], \
-        "functions": [ \
-            { \
-                "key": "f:1:0", \
-                "line": 1 \
-            } \
-        ] \
-    } \
-}';
-
-/* A simple wrapper to monkey-patch object[functionProperty] with
- * a wrapper that checks to see if it was called. Returns true
- * if the function was called at all */
-function _checkIfCalledWhilst(object, functionProperty, clientCode) {
-    let original = object[functionProperty];
-    let called = false;
-
-    object[functionProperty] = function() {
-        called = true;
-        return original.apply(this, arguments);
+
+    it('warns if we hit a non-executable line', function () {
+        spyOn(window, 'log');
+        let expressionCounters = [
+            undefined,
+            0,
+            undefined
+        ];
+
+        Coverage._incrementExpressionCounters(expressionCounters, 'script', 2);
+
+        expect(window.log).toHaveBeenCalledWith("script:2 Executed line " +
+            "previously marked non-executable by Reflect");
+        expect(expressionCounters[2]).toEqual(1);
+    });
+});
+
+describe('Coverage statistics container', function () {
+    const MockFiles = {
+        'filename': "function f() {\n" +
+                    "    return 1;\n" +
+                    "}\n" +
+                    "if (f())\n" +
+                    "    f = 0;\n" +
+                    "\n",
+        'uncached': "function f() {\n" +
+                    "    return 1;\n" +
+                    "}\n"
     };
 
-    clientCode();
-
-    object[functionProperty] = original;
-    return called;
-}
-
-function testCoverageCountersFetchedFromCache() {
-    let called = _checkIfCalledWhilst(Coverage,
-                                      '_fetchCountersFromReflection',
-                                      function() {
-                                          let container = new 
Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                                                                   
MockCache);
-                                          let statistics = container.fetchStatistics('filename');
-                                      });
-    JSUnit.assertFalse(called);
-}
-
-function testCoverageCountersFetchedFromReflectionIfMissed() {
-    let called = _checkIfCalledWhilst(Coverage,
-                                      '_fetchCountersFromReflection',
-                                      function() {
-                                          let container = new 
Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                                                                   
MockCache);
-                                          let statistics = container.fetchStatistics('uncached');
-                                      });
-    JSUnit.assertTrue(called);
-}
-
-function testCoverageContainerCacheNotStaleIfAllHit() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                             MockCache);
-    let statistics = container.fetchStatistics('filename');
-    JSUnit.assertFalse(container.staleCache());
-}
-
-function testCoverageContainerCacheStaleIfMiss() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                             MockCache);
-    let statistics = container.fetchStatistics('uncached');
-    JSUnit.assertTrue(container.staleCache());
-}
-
-function testCoverageCountersFromCacheHaveSameExecutableLinesAsReflection() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                             MockCache);
-    let statistics = container.fetchStatistics('filename');
-
-    let containerWithNoCaching = new Coverage.CoverageStatisticsContainer(MockFilenames);
-    let statisticsWithNoCaching = containerWithNoCaching.fetchStatistics('filename');
-
-    assertArrayEquals(statisticsWithNoCaching.expressionCounters,
-                      statistics.expressionCounters,
-                      JSUnit.assertEquals);
-}
-
-function testCoverageCountersFromCacheHaveSameBranchExitsAsReflection() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                             MockCache);
-    let statistics = container.fetchStatistics('filename');
-
-    let containerWithNoCaching = new Coverage.CoverageStatisticsContainer(MockFilenames);
-    let statisticsWithNoCaching = containerWithNoCaching.fetchStatistics('filename');
-
-    /* Branch starts on line 4 */
-    JSUnit.assertEquals(statisticsWithNoCaching.branchCounters[4].exits[0].line,
-                        statistics.branchCounters[4].exits[0].line);
-}
-
-function testCoverageCountersFromCacheHaveSameBranchPointsAsReflection() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                             MockCache);
-    let statistics = container.fetchStatistics('filename');
-
-    let containerWithNoCaching = new Coverage.CoverageStatisticsContainer(MockFilenames);
-    let statisticsWithNoCaching = containerWithNoCaching.fetchStatistics('filename');
-    JSUnit.assertEquals(statisticsWithNoCaching.branchCounters[4].point,
-                        statistics.branchCounters[4].point);
-}
-
-function testCoverageCountersFromCacheHaveSameFunctionKeysAsReflection() {
-    let container = new Coverage.CoverageStatisticsContainer(MockFilenames,
-                                                             MockCache);
-    let statistics = container.fetchStatistics('filename');
-
-    let containerWithNoCaching = new Coverage.CoverageStatisticsContainer(MockFilenames);
-    let statisticsWithNoCaching = containerWithNoCaching.fetchStatistics('filename');
-
-    /* Functions start on line 1 */
-    assertArrayEquals(Object.keys(statisticsWithNoCaching.functionCounters),
-                      Object.keys(statistics.functionCounters),
-                      JSUnit.assertEquals);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    const MockFilenames = Object.keys(MockFiles).concat(['nonexistent']);
+
+    beforeEach(function () {
+        Coverage.getFileContents =
+            jasmine.createSpy('getFileContents').and.callFake(f => MockFiles[f]);
+        Coverage.getFileChecksum =
+            jasmine.createSpy('getFileChecksum').and.returnValue('abcd');
+        Coverage.getFileModificationTime =
+            jasmine.createSpy('getFileModificationTime').and.returnValue([1, 2]);
+    });
+
+    it('fetches valid statistics for file', function () {
+        let container = new Coverage.CoverageStatisticsContainer(MockFilenames);
+
+        let statistics = container.fetchStatistics('filename');
+        expect(statistics).toBeDefined();
+
+        let files = container.getCoveredFiles();
+        expect(files).toEqual(['filename']);
+    });
+
+    it('throws for nonexisting file', function () {
+        let container = new Coverage.CoverageStatisticsContainer(MockFilenames);
+        expect(() => container.fetchStatistics('nonexistent')).toThrow();
+    });
+
+    const MockCache = '{ \
+        "filename": { \
+            "mtime": [1, 2], \
+            "checksum": null, \
+            "lines": [2, 4, 5], \
+            "branches": [ \
+                { \
+                    "point": 4, \
+                    "exits": [5] \
+                } \
+            ], \
+            "functions": [ \
+                { \
+                    "key": "f:1:0", \
+                    "line": 1 \
+                } \
+            ] \
+        } \
+    }';
+
+    describe('with cache', function () {
+        let container;
+        beforeEach(function () {
+            spyOn(Coverage, '_fetchCountersFromReflection').and.callThrough();
+            container = new Coverage.CoverageStatisticsContainer(MockFilenames,
+                                                                 MockCache);
+        });
+
+        it('fetches counters from cache', function () {
+            container.fetchStatistics('filename');
+            expect(Coverage._fetchCountersFromReflection).not.toHaveBeenCalled();
+        });
+
+        it('fetches counters from reflection if missed', function () {
+            container.fetchStatistics('uncached');
+            expect(Coverage._fetchCountersFromReflection).toHaveBeenCalled();
+        });
+
+        it('cache is not stale if all hit', function () {
+            container.fetchStatistics('filename');
+            expect(container.staleCache()).toBeFalsy();
+        });
+
+        it('cache is stale if missed', function () {
+            container.fetchStatistics('uncached');
+            expect(container.staleCache()).toBeTruthy();
+        });
+    });
+
+    describe('coverage counters from cache', function () {
+        let container, statistics;
+        let containerWithNoCaching, statisticsWithNoCaching;
+        beforeEach(function () {
+            container = new Coverage.CoverageStatisticsContainer(MockFilenames,
+                                                                 MockCache);
+            statistics = container.fetchStatistics('filename');
+
+            containerWithNoCaching = new Coverage.CoverageStatisticsContainer(MockFilenames);
+            statisticsWithNoCaching = containerWithNoCaching.fetchStatistics('filename');
+        });
+
+        it('have same executable lines as reflection', function () {
+            expect(statisticsWithNoCaching.expressionCounters)
+                .toEqual(statistics.expressionCounters);
+        });
+
+        it('have same branch exits as reflection', function () {
+            /* Branch starts on line 4 */
+            expect(statisticsWithNoCaching.branchCounters[4].exits[0].line)
+                .toEqual(statistics.branchCounters[4].exits[0].line);
+        });
+
+        it('have same branch points as reflection', function () {
+            expect(statisticsWithNoCaching.branchCounters[4].point)
+                .toEqual(statistics.branchCounters[4].point);
+        });
+
+        it('have same function keys as reflection', function () {
+            /* Functions start on line 1 */
+            expect(Object.keys(statisticsWithNoCaching.functionCounters))
+                .toEqual(Object.keys(statistics.functionCounters));
+        });
+    });
+});
diff --git a/installed-tests/js/testEverythingBasic.js b/installed-tests/js/testEverythingBasic.js
index e982172..143ef08 100644
--- a/installed-tests/js/testEverythingBasic.js
+++ b/installed-tests/js/testEverythingBasic.js
@@ -1,7 +1,4 @@
-// This used to be called "Everything"
-
-const JSUnit = imports.jsUnit;
-const Everything = imports.gi.Regress;
+const Regress = imports.gi.Regress;
 const WarnLib = imports.gi.WarnLib;
 
 // We use Gio to have some objects that we know exist
@@ -10,661 +7,649 @@ const Gio = imports.gi.Gio;
 const GObject = imports.gi.GObject;
 const Lang = imports.lang;
 
-const INT8_MIN = (-128);
-const INT16_MIN = (-32767-1);
-const INT32_MIN = (-2147483647-1);
-const INT64_MIN = (-9223372036854775807-1);
+describe('Life, the Universe and Everything', function () {
+    it('includes booleans', function () {
+        expect(Regress.test_boolean(false)).toBe(false);
+        expect(Regress.test_boolean(true)).toBe(true);
+    });
 
-const INT8_MAX = (127);
-const INT16_MAX = (32767);
-const INT32_MAX = (2147483647);
-const INT64_MAX = (9223372036854775807);
+    [8, 16, 32, 64].forEach(bits => {
+        it('includes ' + bits + '-bit integers', function () {
+            let method = 'test_int' + bits;
+            expect(Regress[method](42)).toBe(42);
+            expect(Regress[method](-42)).toBe(-42);
+        });
+
+        it('includes unsigned ' + bits + '-bit integers', function () {
+            let method = 'test_uint' + bits;
+            expect(Regress[method](42)).toBe(42);
+        });
+    });
 
-const UINT8_MAX = (255);
-const UINT16_MAX = (65535);
-const UINT32_MAX = (4294967295);
-const UINT64_MAX = (18446744073709551615);
+    ['short', 'int', 'long', 'ssize', 'float', 'double'].forEach(type => {
+        it('includes ' + type + 's', function () {
+            let method = 'test_' + type;
+            expect(Regress[method](42)).toBe(42);
+            expect(Regress[method](-42)).toBe(-42);
+        });
+    });
 
-function testLifeUniverseAndEverything() {
-    JSUnit.assertEquals(false, Everything.test_boolean(false));
-    JSUnit.assertEquals(true, Everything.test_boolean(true));
+    ['ushort', 'uint', 'ulong', 'size'].forEach(type => {
+        it('includes ' + type + 's', function () {
+            let method = 'test_' + type;
+            expect(Regress[method](42)).toBe(42);
+        });
+    });
 
-    JSUnit.assertEquals(42, Everything.test_int8(42));
-    JSUnit.assertEquals(-42, Everything.test_int8(-42));
+    it('includes wide characters', function () {
+        expect(Regress.test_unichar('c')).toBe('c');
+        expect(Regress.test_unichar('')).toBe('');
+        expect(Regress.test_unichar('\u2665')).toBe('\u2665');
+    });
 
-    JSUnit.assertEquals(42, Everything.test_uint8(42));
+    it('includes time_t', function () {
+        let now = Math.floor(new Date().getTime() / 1000);
+        let bounced = Math.floor(Regress.test_timet(now));
+        expect(bounced).toEqual(now);
+    });
 
-    JSUnit.assertEquals(42, Everything.test_int16(42));
-    JSUnit.assertEquals(-42, Everything.test_int16(-42));
+    describe('Limits', function () {
+        const Limits = {
+            '8': {
+                MIN: -128,
+                MAX: 127,
+                UMAX: 255,
+            },
+            '16': {
+                MIN: -32767 - 1,
+                MAX: 32767,
+                UMAX: 65535,
+            },
+            '32': {
+                MIN: -2147483647 - 1,
+                MAX: 2147483647,
+                UMAX: 4294967295,
+            },
+            '64': {
+                MIN: -9223372036854775807 - 1,
+                MAX: 9223372036854775807,
+                UMAX: 18446744073709551615,
+            },
+        };
+
+        const skip = {
+            'UMAX64': true,  // FAIL: expected 18446744073709552000, got 0
+            'MAX64': true,   // FAIL: expected 9223372036854776000, got -9223372036854776000
+        };
+
+        function run_test(bytes, limit, method_stem) {
+            if(skip[limit + bytes])
+                pending("This test doesn't work");
+            let val = Limits[bytes][limit];
+            expect(Regress[method_stem + bytes](val)).toBe(val);
+        }
+        ['8', '16', '32', '64'].forEach(bytes => {
+            it('marshals max value of unsigned ' + bytes + '-bit integers', function () {
+                run_test(bytes, 'UMAX', 'test_uint');
+            });
+
+            it('marshals min value of signed ' + bytes + '-bit integers', function () {
+                run_test(bytes, 'MIN', 'test_int');
+            });
+
+            it('marshals max value of signed ' + bytes + '-bit integers', function () {
+                run_test(bytes, 'MAX', 'test_int');
+            });
+        });
+    });
 
-    JSUnit.assertEquals(42, Everything.test_uint16(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();
+            });
+        });
+    });
 
-    JSUnit.assertEquals(42, Everything.test_int32(42));
-    JSUnit.assertEquals(-42, Everything.test_int32(-42));
+    it('throws when constructor called without new', function () {
+        expect(() => Gio.AppLaunchContext())
+            .toThrowError(/Constructor called as normal method/);
+    });
 
-    JSUnit.assertEquals(42, Everything.test_uint32(42));
+    describe('String arrays', function () {
+        it('marshalling in', function () {
+            expect(Regress.test_strv_in(['1', '2', '3'])).toBeTruthy();
+            // 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 out with container transfer', function () {
+            expect(Regress.test_strv_out_container()).toEqual(['1', '2', '3']);
+        });
+    });
 
-    JSUnit.assertEquals(42, Everything.test_int64(42));
-    JSUnit.assertEquals(-42, Everything.test_int64(-42));
+    it('in after out', function () {
+        const str = "hello";
+        let len = Regress.test_int_out_utf8(str);
+        expect(len).toEqual(str.length);
+    });
 
-    JSUnit.assertEquals(42, Everything.test_uint64(42));
+    describe('UTF-8 strings', function () {
+        const CONST_STR = "const \u2665 utf8";
+        const NONCONST_STR = "nonconst \u2665 utf8";
 
-    JSUnit.assertEquals(42, Everything.test_short(42));
-    JSUnit.assertEquals(-42, Everything.test_short(-42));
+        it('as return types', function () {
+            expect(Regress.test_utf8_const_return()).toEqual(CONST_STR);
+            expect(Regress.test_utf8_nonconst_return()).toEqual(NONCONST_STR);
+        });
 
-    JSUnit.assertEquals(42, Everything.test_ushort(42));
+        it('as in parameters', function () {
+            Regress.test_utf8_const_in(CONST_STR);
+        });
 
-    JSUnit.assertEquals(42, Everything.test_int(42));
-    JSUnit.assertEquals(-42, Everything.test_int(-42));
+        it('as out parameters', function () {
+            expect(Regress.test_utf8_out()).toEqual(NONCONST_STR);
+        });
 
-    JSUnit.assertEquals(42, Everything.test_uint(42));
+        // FIXME: this is broken due to a change in gobject-introspection.
+        xit('as in-out parameters', function () {
+            expect(Regress.test_utf8_inout(CONST_STR)).toEqual(NONCONST_STR);
+        }).pend('https://bugzilla.gnome.org/show_bug.cgi?id=736517');
+    });
 
-    JSUnit.assertEquals(42, Everything.test_long(42));
-    JSUnit.assertEquals(-42, Everything.test_long(-42));
+    it('return values in filename encoding', function () {
+        let filenames = Regress.test_filename_return();
+        expect(filenames).toEqual(['\u00e5\u00e4\u00f6', '/etc/fstab']);
+    });
 
-    JSUnit.assertEquals(42, Everything.test_ulong(42));
+    it('static methods', function () {
+        let v = Regress.TestObj.new_from_file("/enoent");
+        expect(v instanceof Regress.TestObj).toBeTruthy();
+    });
 
-    JSUnit.assertEquals(42, Everything.test_ssize(42));
-    JSUnit.assertEquals(-42, Everything.test_ssize(-42));
+    it('closures', function () {
+        let callback = jasmine.createSpy('callback').and.returnValue(42);
+        expect(Regress.test_closure(callback)).toEqual(42);
+        expect(callback).toHaveBeenCalledWith();
+    });
 
-    JSUnit.assertEquals(42, Everything.test_size(42));
+    it('closures with one argument', function () {
+        let callback = jasmine.createSpy('callback')
+            .and.callFake(someValue => someValue);
+        expect(Regress.test_closure_one_arg(callback, 42)).toEqual(42);
+        expect(callback).toHaveBeenCalledWith(42);
+    });
 
-    JSUnit.assertEquals(42, Everything.test_float(42));
-    JSUnit.assertEquals(-42, Everything.test_float(-42));
+    it('callbacks', function () {
+        let callback = jasmine.createSpy('callback').and.returnValue(42);
+        expect(Regress.test_callback(callback)).toEqual(42);
+    });
 
-    JSUnit.assertEquals(42, Everything.test_double(42));
-    JSUnit.assertEquals(-42, Everything.test_double(-42));
+    it('null / undefined callback', function () {
+        expect(Regress.test_callback(null)).toEqual(0);
+        expect(() => Regress.test_callback(undefined)).toThrow();
+    });
 
-    JSUnit.assertEquals("c", Everything.test_unichar("c"));
-    JSUnit.assertEquals("", Everything.test_unichar(""));
-    JSUnit.assertEquals("\u2665", Everything.test_unichar("\u2665"));
+    it('array callbacks', function () {
+        let 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"]);
+    });
 
-    let now = Math.floor(new Date().getTime() / 1000);
-    let bounced = Math.floor(Everything.test_timet(now));
-    JSUnit.assertEquals(bounced, now);
-}
+    it('null array callback', function () {
+        expect(() => Regress.test_array_callback(null)).toThrow();
+    });
 
-function testLimits() {
-    JSUnit.assertEquals(UINT8_MAX, Everything.test_uint8(UINT8_MAX));
-    JSUnit.assertEquals(UINT16_MAX, Everything.test_uint16(UINT16_MAX));
-    JSUnit.assertEquals(UINT32_MAX, Everything.test_uint32(UINT32_MAX));
+    it('callback with transfer-full return value', function () {
+        function callback() {
+            return Regress.TestObj.new_from_file("/enoent");
+        }
+        Regress.test_callback_return_full(callback);
+    });
 
-    // FAIL: expected 18446744073709552000, got 0
-    //assertEquals(UINT64_MAX, Everything.test_uint64(UINT64_MAX));
+    it('callback with destroy-notify', function () {
+        let testObj = {
+            test: function (data) { return data; },
+        };
+        spyOn(testObj, 'test').and.callThrough();
+        expect(Regress.test_callback_destroy_notify(function () {
+            return testObj.test(42);
+        }.bind(testObj))).toEqual(42);
+        expect(testObj.test).toHaveBeenCalledTimes(1);
+        expect(Regress.test_callback_thaw_notifications()).toEqual(42);
+    });
 
-    JSUnit.assertEquals(INT8_MIN, Everything.test_int8(INT8_MIN));
-    JSUnit.assertEquals(INT8_MAX, Everything.test_int8(INT8_MAX));
-    JSUnit.assertEquals(INT16_MIN, Everything.test_int16(INT16_MIN));
-    JSUnit.assertEquals(INT16_MAX, Everything.test_int16(INT16_MAX));
-    JSUnit.assertEquals(INT32_MIN, Everything.test_int32(INT32_MIN));
-    JSUnit.assertEquals(INT32_MAX, Everything.test_int32(INT32_MAX));
-    JSUnit.assertEquals(INT64_MIN, Everything.test_int64(INT64_MIN));
+    it('async callback', function () {
+        Regress.test_callback_async(() => 44);
+        expect(Regress.test_callback_thaw_async()).toEqual(44);
+    });
 
-    // FAIL: expected 9223372036854776000, got -9223372036854776000
-    //assertEquals(INT64_MAX, Everything.test_int64(INT64_MAX));
-}
+    it('method taking a GValue', function () {
+        expect(Regress.test_int_value_arg(42)).toEqual(42);
+    });
 
-function testNoImplicitConversionToUnsigned() {
-    JSUnit.assertRaises(function() { return Everything.test_uint8(-42); });
-    JSUnit.assertRaises(function() { return Everything.test_uint16(-42); });
-    JSUnit.assertRaises(function() { return Everything.test_uint32(-42); });
-
-    JSUnit.assertRaises(function() { return Everything.test_uint64(-42); });
-
-    JSUnit.assertRaises(function() { return Everything.test_uint(-42); });
-    JSUnit.assertRaises(function() { return Everything.test_size(-42); });
-}
-
-
-function testBadConstructor() {
-    try {
-        Gio.AppLaunchContext();
-    } catch (e) {
-        JSUnit.assert(e.message.indexOf("Constructor called as normal method") >= 0);
-    }
-}
-
-function testStrv() {
-    JSUnit.assertTrue(Everything.test_strv_in(['1', '2', '3']));
-    // Second two are deliberately not strings
-    JSUnit.assertRaises(function() { Everything.test_strv_in(['1', 2, 3]); });
+    it('method returning a GValue', function () {
+        expect(Regress.test_value_return(42)).toEqual(42);
+    });
 
-    let strv = Everything.test_strv_out();
-    JSUnit.assertEquals(5, strv.length);
-    JSUnit.assertEquals("thanks", strv[0]);
-    JSUnit.assertEquals("for", strv[1]);
-    JSUnit.assertEquals("all", strv[2]);
-    JSUnit.assertEquals("the", strv[3]);
-    JSUnit.assertEquals("fish", strv[4]);
-
-    strv = Everything.test_strv_out_container();
-    JSUnit.assertEquals(3, strv.length);
-    JSUnit.assertEquals("1", strv[0]);
-    JSUnit.assertEquals("2", strv[1]);
-    JSUnit.assertEquals("3", strv[2]);
-}
-
-function testInAfterOut() {
-    const str = "hello";
-
-    let len = Everything.test_int_out_utf8(str);
-    JSUnit.assertEquals("testInAfterOut", str.length, len);
-}
-
-function testUtf8() {
-    const CONST_STR = "const \u2665 utf8";
-    const NONCONST_STR = "nonconst \u2665 utf8";
-
-    JSUnit.assertEquals(CONST_STR, Everything.test_utf8_const_return());
-    JSUnit.assertEquals(NONCONST_STR, Everything.test_utf8_nonconst_return());
-    Everything.test_utf8_const_in(CONST_STR);
-    JSUnit.assertEquals(NONCONST_STR, Everything.test_utf8_out());
-    // FIXME: these are broken due to a change in gobject-introspection.
-    // Disable them for now. See https://bugzilla.gnome.org/show_bug.cgi?id=736517
-    // JSUnit.assertEquals(NONCONST_STR, Everything.test_utf8_inout(CONST_STR));
-    // JSUnit.assertEquals(NONCONST_STR, Everything.test_utf8_inout(CONST_STR));
-    // JSUnit.assertEquals(NONCONST_STR, Everything.test_utf8_inout(CONST_STR));
-    // JSUnit.assertEquals(NONCONST_STR, Everything.test_utf8_inout(CONST_STR));
-}
-
-function testFilenameReturn() {
-    var filenames = Everything.test_filename_return();
-    JSUnit.assertEquals(2, filenames.length);
-    JSUnit.assertEquals('\u00e5\u00e4\u00f6', filenames[0]);
-    JSUnit.assertEquals('/etc/fstab', filenames[1]);
-}
-
-function testStaticMeth() {
-    let v = Everything.TestObj.new_from_file("/enoent");
-    JSUnit.assertTrue(v instanceof Everything.TestObj);
-}
-
-function testClosure() {
-    let arguments_length = -1;
-    let someCallback = function() {
-                           arguments_length = arguments.length;
-                           return 42;
-                       };
-
-    let i = Everything.test_closure(someCallback);
-
-    JSUnit.assertEquals('callback arguments length', 0, arguments_length);
-    JSUnit.assertEquals('callback return value', 42, i);
-}
-
-function testClosureOneArg() {
-    let arguments_length = -1;
-    let someCallback = function(someValue) {
-                           arguments_length = arguments.length;
-                           JSUnit.assertEquals(1, arguments.length);
-                           return someValue;
-                       };
-
-    let i = Everything.test_closure_one_arg(someCallback, 42);
-
-    JSUnit.assertEquals('callback arguments length', 1, arguments_length);
-    JSUnit.assertEquals('callback with one arg return value', 42, i);
-}
-
-function testCallback() {
-    let callback = function() {
-                       return 42;
-                   };
-    JSUnit.assertEquals('Callback', Everything.test_callback(callback), 42);
-
-    JSUnit.assertEquals('CallbackNull', Everything.test_callback(null), 0);
-    JSUnit.assertRaises('CallbackUndefined', function () { Everything.test_callback(undefined); });
-}
-
-function testArrayCallback() {
-    function arrayEqual(ref, one) {
-        JSUnit.assertEquals(ref.length, one.length);
-        for (let i = 0; i < ref.length; i++)
-            JSUnit.assertEquals(ref[i], one[i]);
-    }
-
-    let callback = function(ints, strings) {
-        JSUnit.assertEquals(2, arguments.length);
-
-        arrayEqual([-1, 0, 1, 2], ints);
-        arrayEqual(["one", "two", "three"], strings);
-
-        return 7;
-    };
-    JSUnit.assertEquals(Everything.test_array_callback(callback), 14);
-    JSUnit.assertRaises(function () { Everything.test_array_callback(null) });
-}
-
-function testCallbackTransferFull() {
-    let callback = function() {
-                       let obj = Everything.TestObj.new_from_file("/enoent");
-                       return obj;
-                   };
-
-    Everything.test_callback_return_full(callback);
-}
-
-function testCallbackDestroyNotify() {
-    let testObj = {
-        called: 0,
-        test: function(data) {
-            this.called++;
-            return data;
-        }
-    };
-    JSUnit.assertEquals('CallbackDestroyNotify',
-                 Everything.test_callback_destroy_notify(Lang.bind(testObj,
-                     function() {
-                         return testObj.test(42);
-                     })), 42);
-    JSUnit.assertEquals('CallbackDestroyNotify', testObj.called, 1);
-    JSUnit.assertEquals('CallbackDestroyNotify', Everything.test_callback_thaw_notifications(), 42);
-}
-
-function testCallbackAsync() {
-    let test = function() {
-                   return 44;
-               };
-    Everything.test_callback_async(test);
-    let i = Everything.test_callback_thaw_async();
-    JSUnit.assertEquals('testCallbackAsyncFinish', 44, i);
-}
-
-function testIntValueArg() {
-    let i = Everything.test_int_value_arg(42);
-    JSUnit.assertEquals('Method taking a GValue', 42, i);
-}
-
-function testValueReturn() {
-    let i = Everything.test_value_return(42);
-    JSUnit.assertEquals('Method returning a GValue', 42, i);
-}
-
-/* GList types */
-function testGListOut() {
-    JSUnit.assertEquals("1,2,3", Everything.test_glist_nothing_return().join(','));
-    JSUnit.assertEquals("1,2,3", Everything.test_glist_nothing_return2().join(','));
-    JSUnit.assertEquals("1,2,3", Everything.test_glist_container_return().join(','));
-    JSUnit.assertEquals("1,2,3", Everything.test_glist_everything_return().join(','));
-}
-function testGListIn() {
-    const STR_LIST = ["1", "2", "3" ];
-    Everything.test_glist_nothing_in(STR_LIST);
-    Everything.test_glist_nothing_in2(STR_LIST);
-    //Everything.test_glist_container_in(STR_LIST);
-}
-
-/* GSList types */
-function testGSListOut() {
-    JSUnit.assertEquals("1,2,3", Everything.test_gslist_nothing_return().join(','));
-    JSUnit.assertEquals("1,2,3", Everything.test_gslist_nothing_return2().join(','));
-    JSUnit.assertEquals("1,2,3", Everything.test_gslist_container_return().join(','));
-    JSUnit.assertEquals("1,2,3", Everything.test_gslist_everything_return().join(','));
-}
-function testGSListIn() {
-    const STR_LIST = ["1", "2", "3" ];
-    Everything.test_gslist_nothing_in(STR_LIST);
-    Everything.test_gslist_nothing_in2(STR_LIST);
-    //Everything.test_gslist_container_in(STR_LIST);
-}
-
-/* Array tests */
-function testArrayIn() {
-    JSUnit.assertEquals(10, Everything.test_array_int_in([1,2,3,4]));
-    JSUnit.assertEquals(10, Everything.test_array_gint8_in([1,2,3,4]));
-    JSUnit.assertEquals(10, Everything.test_array_gint16_in([1,2,3,4]));
-    JSUnit.assertEquals(10, Everything.test_array_gint32_in([1,2,3,4]));
-    JSUnit.assertEquals(10, Everything.test_array_gint64_in([1,2,3,4]));
-
-    // implicit conversions from strings to int arrays
-    JSUnit.assertEquals(10, Everything.test_array_gint8_in("\x01\x02\x03\x04"));
-    JSUnit.assertEquals(10, Everything.test_array_gint16_in("\x01\x02\x03\x04"));
-    JSUnit.assertEquals(2560, Everything.test_array_gint16_in("\u0100\u0200\u0300\u0400"));
-
-    // GType arrays
-    JSUnit.assertEquals('[GSimpleAction,GIcon,GBoxed,]',
-                 Everything.test_array_gtype_in([Gio.SimpleAction, Gio.Icon, GObject.TYPE_BOXED]));
-    JSUnit.assertRaises(function() {
-        Everything.test_array_gtype_in(42);
-    });
-    JSUnit.assertRaises(function() {
-        Everything.test_array_gtype_in([undefined]);
-    });
-    JSUnit.assertRaises(function() {
+    ['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);
+            });
+
+            xit('in with transfer-container', function () {
+                Regress['test_' + list + '_container_in'](STR_LIST);
+            }).pend('Not sure why this is skipped');
+        });
+    });
+
+    ['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('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
-        Everything.test_array_gtype_in([80]);
-    });
-}
-
-function testArrayOut() {
-    function arrayEqual(ref, res) {
-        JSUnit.assertEquals(ref.length, res.length);
-        for (let i = 0; i < ref.length; i++)
-            JSUnit.assertEquals(ref[i], res[i]);
-    }
-
-    let array = Everything.test_array_int_out();
-    arrayEqual([0, 1, 2, 3, 4], array);
-
-    let array =  Everything.test_array_fixed_size_int_out();
-    JSUnit.assertEquals(0, array[0]);
-    JSUnit.assertEquals(4, array[4]);
-    array =  Everything.test_array_fixed_size_int_return();
-    JSUnit.assertEquals(0, array[0]);
-    JSUnit.assertEquals(4, array[4]);
-
-    array = Everything.test_array_int_none_out();
-    arrayEqual([1, 2, 3, 4, 5], array);
-
-    array = Everything.test_array_int_full_out();
-    arrayEqual([0, 1, 2, 3, 4], array);
-
-    array = Everything.test_array_int_null_out();
-    JSUnit.assertEquals(0, array.length);
-
-    Everything.test_array_int_null_in(null);
-}
-
-function testArrayOfStructsOut() {
-   let array = Everything.test_array_struct_out();
-   let ints = array.map(struct => struct.some_int);
-   JSUnit.assertEquals(22, ints[0]);
-   JSUnit.assertEquals(33, ints[1]);
-   JSUnit.assertEquals(44, ints[2]);
-}
-
-/* GHash type */
-
-// Convert an object to a predictable (not-hash-order-dependent) string
-function objToString(v) {
-    if (typeof(v) == "object") {
-        let keys = [];
-        for (let k in v)
-            keys.push(k);
-        keys.sort();
-        return "{" + keys.map(function(k) {
-            return k + ":" + objToString(v[k]);
-        }) + "}";
-    } else if (typeof(v) == "string") {
-        return '"' + v + '"';
-    } else {
-        return v;
-    }
-}
-
-function testGHashOut() {
-    const HASH_STR = '{baz:"bat",foo:"bar",qux:"quux"}';
-    JSUnit.assertEquals(null, Everything.test_ghash_null_return());
-    JSUnit.assertEquals(HASH_STR, objToString(Everything.test_ghash_nothing_return()));
-    JSUnit.assertEquals(HASH_STR, objToString(Everything.test_ghash_nothing_return2()));
-    JSUnit.assertEquals(HASH_STR, objToString(Everything.test_ghash_container_return()));
-    JSUnit.assertEquals(HASH_STR, objToString(Everything.test_ghash_everything_return()));
-}
-
-function testGHashIn() {
-    const STR_HASH = { foo: 'bar', baz: 'bat', qux: 'quux' };
-    Everything.test_ghash_null_in(null);
-    Everything.test_ghash_nothing_in(STR_HASH);
-    Everything.test_ghash_nothing_in2(STR_HASH);
-}
-
-function testNestedGHashOut() {
-    const HASH_STR = '{wibble:{baz:"bat",foo:"bar",qux:"quux"}}';
-    JSUnit.assertEquals(HASH_STR, objToString(Everything.test_ghash_nested_everything_return()));
-    JSUnit.assertEquals(HASH_STR, objToString(Everything.test_ghash_nested_everything_return2()));
-}
-
-/* Enums */
-function testEnumParam() {
-    let e;
-
-    e = Everything.test_enum_param(Everything.TestEnum.VALUE1);
-    JSUnit.assertEquals('Enum parameter', 'value1', e);
-    e = Everything.test_enum_param(Everything.TestEnum.VALUE3);
-    JSUnit.assertEquals('Enum parameter', 'value3', e);
-
-    e = Everything.test_unsigned_enum_param(Everything.TestEnumUnsigned.VALUE1);
-    JSUnit.assertEquals('Enum parameter', 'value1', e);
-    e = Everything.test_unsigned_enum_param(Everything.TestEnumUnsigned.VALUE2);
-    JSUnit.assertEquals('Enum parameter', 'value2', e);
-
-    JSUnit.assertNotUndefined("Enum $gtype", Everything.TestEnumUnsigned.$gtype);
-    JSUnit.assertTrue("Enum $gtype enumerable", "$gtype" in Everything.TestEnumUnsigned);
-
-    JSUnit.assertEquals(Number(Everything.TestError), Everything.TestError.quark());
-    JSUnit.assertEquals('value4', Everything.TestEnum.param(Everything.TestEnum.VALUE4));
-}
-
-function testSignal() {
-    let handlerCounter = 0;
-    let o = new Everything.TestObj();
-    let theObject = null;
-
-    let handlerId = o.connect('test', function(signalObject) {
-                                          handlerCounter ++;
-                                          theObject = signalObject;
-                                          o.disconnect(handlerId);
-                                      });
-
-    o.emit('test');
-    JSUnit.assertEquals('handler callled', 1, handlerCounter);
-    JSUnit.assertEquals('Signal handlers gets called with right object', o, theObject);
-    o.emit('test');
-    JSUnit.assertEquals('disconnected handler not called', 1, handlerCounter);
-}
-
-function testInvalidSignal() {
-    let o = new Everything.TestObj();
-
-    JSUnit.assertRaises('connect to invalid signal',
-                 function() { o.connect('invalid-signal', function(o) {}); });
-    JSUnit.assertRaises('emit invalid signal',
-                 function() { o.emit('invalid-signal'); });
-}
-
-function testSignalWithStaticScopeArg() {
-    let o = new Everything.TestObj();
-    let b = new Everything.TestSimpleBoxedA({ some_int: 42,
-                                              some_int8: 43,
-                                              some_double: 42.5,
-                                              some_enum: Everything.TestEnum.VALUE3 });
-
-    o.connect('test-with-static-scope-arg', function(signalObject, signalArg) {
-                                                signalArg.some_int = 44;
-                                            });
-
-    o.emit('test-with-static-scope-arg', b);
-    JSUnit.assertEquals('signal handler was passed arg as reference', 44, b.some_int);
-}
-
-function testSignalWithArrayLenParam() {
-    let o = new Everything.TestObj();
-    let array;
-    o.connect('sig-with-array-len-prop', function(signalObj, signalArray, shouldBeUndefined) {
-        array = signalArray;
-        JSUnit.assertUndefined('no extra length arg', shouldBeUndefined);
-    });
-
-    o.emit_sig_with_array_len_prop();
-    JSUnit.assertEquals('handler was passed array with length', array.length, 5);
-    for (let i = 0; i < 5; i++)
-        JSUnit.assertEquals('handler was passed correct array', array[i], i);
-
-    // FIXME not yet implemented:
-    // o.emit('sig-with-array-len-prop', [0, 1, 2, 3, 4]);
-    // JSUnit.assertEquals('handler was passed array with length', array.length, 5);
-    // for (let i = 0; i < 5; i++)
-    //     JSUnit.assertEquals('handler was passed correct array', array[i], i);
-    // o.emit('sig-with-array-len-prop', null);
-    // JSUnit.assertNull('handler was passed null array', array);
-}
-
-function testTortureSignature0() {
-    let [y, z, q] = Everything.test_torture_signature_0(42, 'foo', 7);
-    JSUnit.assertEquals(Math.floor(y), 42);
-    JSUnit.assertEquals(z, 84);
-    JSUnit.assertEquals(q, 10);
-}
-
-function testTortureSignature1Fail() {
-    JSUnit.assertRaises(function () {
-        let [success, y, z, q] = Everything.test_torture_signature_1(42, 'foo', 7);
-    });
-}
-
-function testTortureSignature1Success() {
-    let [success, y, z, q] = Everything.test_torture_signature_1(11, 'barbaz', 8);
-    JSUnit.assertEquals(Math.floor(y), 11);
-    JSUnit.assertEquals(z, 22);
-    JSUnit.assertEquals(q, 14);
-}
-
-function testTortureSignature2() {
-    let [y, z, q] = Everything.test_torture_signature_2(42, function () {
-        return 0;
-    }, 'foo', 7);
-    JSUnit.assertEquals(Math.floor(y), 42);
-    JSUnit.assertEquals(z, 84);
-    JSUnit.assertEquals(q, 10);
-}
-
-function testObjTortureSignature0() {
-    let o = new Everything.TestObj();
-    let [y, z, q] = o.torture_signature_0(42, 'foo', 7);
-    JSUnit.assertEquals(Math.floor(y), 42);
-    JSUnit.assertEquals(z, 84);
-    JSUnit.assertEquals(q, 10);
-}
-
-function testObjTortureSignature1Fail() {
-    let o = new Everything.TestObj();
-    JSUnit.assertRaises(function () {
-        let [success, y, z, q] = o.torture_signature_1(42, 'foo', 7);
-    });
-}
-
-function testObjTortureSignature1Success() {
-    let o = new Everything.TestObj();
-    let [success, y, z, q] = o.torture_signature_1(11, 'barbaz', 8);
-    JSUnit.assertEquals(Math.floor(y), 11);
-    JSUnit.assertEquals(z, 22);
-    JSUnit.assertEquals(q, 14);
-}
-
-function testStrvInGValue() {
-    let v = Everything.test_strv_in_gvalue();
-
-    JSUnit.assertEquals(v.length, 3);
-    JSUnit.assertEquals(v[0], "one");
-    JSUnit.assertEquals(v[1], "two");
-    JSUnit.assertEquals(v[2], "three");
-}
-
-function testVariant() {
-    // Cannot access the variant contents, for now
-    let ivar = Everything.test_gvariant_i();
-    JSUnit.assertEquals('i', ivar.get_type_string());
-    JSUnit.assertTrue(ivar.equal(GLib.Variant.new_int32(1)));
-
-    let svar = Everything.test_gvariant_s();
-    JSUnit.assertEquals('s', String.fromCharCode(svar.classify()));
-    JSUnit.assertEquals('one', svar.get_string()[0]);
-
-    let asvvar = Everything.test_gvariant_asv();
-    JSUnit.assertEquals(2, asvvar.n_children());
-
-    let asvar = Everything.test_gvariant_as();
-    let as = asvar.get_strv();
-    JSUnit.assertEquals('one', as[0]);
-    JSUnit.assertEquals('two', as[1]);
-    JSUnit.assertEquals('three', as[2]);
-    JSUnit.assertEquals(3, as.length);
-}
-
-function testGError() {
-    JSUnit.assertEquals(Gio.io_error_quark(), Number(Gio.IOErrorEnum));
-
-    try {
-        let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist");
-        file.read(null);
-    } catch (x) {
-        JSUnit.assertTrue(x instanceof Gio.IOErrorEnum);
-        JSUnit.assertTrue(x.matches(Gio.io_error_quark(), Gio.IOErrorEnum.NOT_FOUND));
-        JSUnit.assertTrue(x.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND));
-
-        JSUnit.assertEquals(Gio.io_error_quark(), x.domain);
-        JSUnit.assertEquals(Gio.IOErrorEnum.NOT_FOUND, x.code);
-    }
-
-    Everything.test_gerror_callback(function(e) {
-        JSUnit.assertTrue(e instanceof Gio.IOErrorEnum);
-        JSUnit.assertEquals(Gio.io_error_quark(), e.domain);
-        JSUnit.assertEquals(Gio.IOErrorEnum.NOT_SUPPORTED, e.code);
-        JSUnit.assertEquals('regression test error', e.message);
-    });
-    Everything.test_owned_gerror_callback(function(e) {
-        JSUnit.assertTrue(e instanceof Gio.IOErrorEnum);
-        JSUnit.assertEquals(Gio.io_error_quark(), e.domain);
-        JSUnit.assertEquals(Gio.IOErrorEnum.PERMISSION_DENIED, e.code);
-        JSUnit.assertEquals('regression test owned error', e.message);
+        expect(() => Regress.test_array_gtype_in([80])).toThrow();
     });
 
-    // Calling matches() on an unpaired error used to JSUnit.assert:
-    // https://bugzilla.gnome.org/show_bug.cgi?id=689482
-    try {
-        WarnLib.throw_unpaired();
-        JSUnit.assertTrue(false);
-    } catch (e) {
-        JSUnit.assertFalse(e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND));
-    }
-}
+    it('out arrays of integers', function () {
+        expect(Regress.test_array_int_out()).toEqual([0, 1, 2, 3, 4]);
+
+        let array = Regress.test_array_fixed_size_int_out();
+        expect(array[0]).toEqual(0);
+        expect(array[4]).toEqual(4);
+        let array = Regress.test_array_fixed_size_int_return();
+        expect(array[0]).toEqual(0);
+        expect(array[4]).toEqual(4);
+
+        expect(Regress.test_array_int_none_out()).toEqual([1, 2, 3, 4, 5]);
 
-function testWrongClassGObject() {
-    /* Function calls */
-    // Everything.func_obj_null_in expects a Everything.TestObj
-    JSUnit.assertRaises(function() {
-        Everything.func_obj_null_in(new Gio.SimpleAction);
+        expect(Regress.test_array_int_full_out()).toEqual([0, 1, 2, 3, 4]);
+
+        expect(Regress.test_array_int_null_out()).toEqual([]);
+    });
+
+    it('null in-array', function () {
+        Regress.test_array_int_null_in(null);
     });
-    JSUnit.assertRaises(function() {
-        Everything.func_obj_null_in(new GLib.KeyFile);
+
+    it('out arrays of structs', function () {
+        let array = Regress.test_array_struct_out();
+        let ints = array.map(struct => struct.some_int);
+        expect(ints).toEqual([22, 33, 44]);
+    });
+
+    describe('GHash type', function () {;
+        const EXPECTED_HASH = { baz: 'bat', foo: 'bar', qux: 'quux' };
+
+        it('null GHash in', function () {
+            Regress.test_ghash_null_in(null);
+        });
+
+        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);
+            expect(Regress.test_ghash_container_return()).toEqual(EXPECTED_HASH);
+            expect(Regress.test_ghash_everything_return()).toEqual(EXPECTED_HASH);
+        });
+
+        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);
+        });
     });
-    JSUnit.assertRaises(function() {
-        Everything.func_obj_null_in(Gio.File.new_for_path('/'));
+
+    it('enum parameter', function () {
+        expect(Regress.test_enum_param(Regress.TestEnum.VALUE1)).toEqual('value1');
+        expect(Regress.test_enum_param(Regress.TestEnum.VALUE3)).toEqual('value3');
     });
-    Everything.func_obj_null_in(new Everything.TestSubObj);
 
-    /* Method calls */
-    JSUnit.assertRaises(function() {
-        Everything.TestObj.prototype.instance_method.call(new Gio.SimpleAction);
+    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');
     });
-    JSUnit.assertRaises(function() {
-        Everything.TestObj.prototype.instance_method.call(new GLib.KeyFile);
+
+    it('enum has a $gtype property', function () {
+        expect(Regress.TestEnumUnsigned.$gtype).toBeDefined();
     });
-    Everything.TestObj.prototype.instance_method.call(new Everything.TestSubObj);
-}
 
-function testWrongClassGBoxed() {
-    let simpleBoxed = new Everything.TestSimpleBoxedA;
-    // simpleBoxed.equals expects a Everything.TestSimpleBoxedA
-    JSUnit.assertRaises(function() {
-        simpleBoxed.equals(new Gio.SimpleAction);
+    it('enum $gtype property is enumerable', function () {
+        expect('$gtype' in Regress.TestEnumUnsigned).toBeTruthy();
     });
-    JSUnit.assertRaises(function() {
-        simpleBoxed.equals(new Everything.TestObj);
+
+    it('Number converts error to quark', function () {
+        expect(Regress.TestError.quark()).toEqual(Number(Regress.TestError));
     });
-    JSUnit.assertRaises(function() {
-        simpleBoxed.equals(new GLib.KeyFile);
+
+    it('converts enum to string', function () {
+        expect(Regress.TestEnum.param(Regress.TestEnum.VALUE4)).toEqual('value4');
     });
-    JSUnit.assertTrue(simpleBoxed.equals(simpleBoxed));
 
-    JSUnit.assertRaises(function() {
-        Everything.TestSimpleBoxedA.prototype.copy.call(new Gio.SimpleAction);
+    describe('Signal connection', function () {
+        let o;
+        beforeEach(function () {
+            o = new Regress.TestObj();
+        });
+
+        it('calls correct handlers with correct arguments', function () {
+            let handler = jasmine.createSpy('handler');
+            let 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', o => {})).toThrow();
+            expect(() => o.emit('invalid-signal')).toThrow();
+        });
+
+        it('signal handler with static scope arg gets arg passed by reference', function () {
+            let 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 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 () {
+            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 () {
+            let 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');
     });
-    JSUnit.assertRaises(function() {
-        Everything.TestSimpleBoxedA.prototype.copy.call(new GLib.KeyFile);
+
+    it('torture signature 0', function () {
+        let [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);
     });
-    Everything.TestSimpleBoxedA.prototype.copy.call(simpleBoxed);
-}
 
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('torture signature 1 fail', function () {
+        expect(() => Regress.test_torture_signature_1(42, 'foo', 7)).toThrow();
+    });
+
+    it('torture signature 1 success', function () {
+        let [, 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 () {
+        let [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('Object torture signature', function () {
+        let o;
+        beforeEach(function () {
+            o = new Regress.TestObj();
+        });
+
+        it('0', function () {
+            let [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 () {
+            let [, y, z, q] = o.torture_signature_1(11, 'barbaz', 8);
+            expect(Math.floor(y)).toEqual(11);
+            expect(z).toEqual(22);
+            expect(q).toEqual(14);
+        });
+    });
+
+    it('strv in GValue', function () {
+        expect(Regress.test_strv_in_gvalue()).toEqual(['one', 'two', 'three']);
+    });
+
+    // Cannot access the variant contents, for now
+    it('integer GVariant', function () {
+        let ivar = Regress.test_gvariant_i();
+        expect(ivar.get_type_string()).toEqual('i');
+        expect(ivar.equal(GLib.Variant.new_int32(1))).toBeTruthy();
+    });
 
+    it('string GVariant', function () {
+        let svar = Regress.test_gvariant_s();
+        expect(String.fromCharCode(svar.classify())).toEqual('s');
+        expect(svar.get_string()[0]).toEqual('one');
+    });
+
+    it('a{sv} GVariant', function () {
+        let asvvar = Regress.test_gvariant_asv();
+        expect(asvvar.n_children()).toEqual(2);
+    });
+
+    it('as Variant', function () {
+        let asvar = Regress.test_gvariant_as();
+        expect(asvar.get_strv()).toEqual(['one', 'two', 'three']);
+    });
+
+    it('error enum names match error quarks', function () {
+        expect(Number(Gio.IOErrorEnum)).toEqual(Gio.io_error_quark());
+    });
+
+    describe('thrown GError', function () {
+        let err;
+        beforeEach(function () {
+            try {
+                let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist");
+                file.read(null);
+            } catch (x) {
+                err = x;
+            }
+        });
+
+        it('is an instance of error enum type', function () {
+            expect(err instanceof Gio.IOErrorEnum).toBeTruthy();
+        });
+
+        it('matches error domain and code', function () {
+            expect(err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.NOT_FOUND))
+                .toBeTruthy();
+        });
+
+        it('has properties for domain and code', function () {
+            expect(err.domain).toEqual(Gio.io_error_quark());
+            expect(err.code).toEqual(Gio.IOErrorEnum.NOT_FOUND);
+        });
+    });
+
+    it('GError callback', function (done) {
+        Regress.test_gerror_callback(e => {
+            expect(e instanceof Gio.IOErrorEnum).toBeTruthy();
+            expect(e.domain).toEqual(Gio.io_error_quark());
+            expect(e.code).toEqual(Gio.IOErrorEnum.NOT_SUPPORTED);
+            done();
+        });
+    });
+
+    it('owned GError callback', function (done) {
+        Regress.test_owned_gerror_callback(e => {
+            expect(e instanceof Gio.IOErrorEnum).toBeTruthy();
+            expect(e.domain).toEqual(Gio.io_error_quark());
+            expect(e.code).toEqual(Gio.IOErrorEnum.PERMISSION_DENIED);
+            done();
+        });
+    });
+
+    // 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();
+        }
+    });
+
+    describe('wrong type for GObject', function () {
+        let wrongObject, wrongBoxed, subclassObject;
+        beforeEach(function () {
+            wrongObject = new Gio.SimpleAction();
+            wrongBoxed = new GLib.KeyFile();
+            subclassObject = new Regress.TestSubObj();
+        });
+
+        // Everything.func_obj_null_in expects a Everything.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 () {
+            let 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();
+        });
+    });
+
+    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();
+        });
+    });
+});
diff --git a/installed-tests/js/testEverythingEncapsulated.js 
b/installed-tests/js/testEverythingEncapsulated.js
index 3b7f5be..ee09b79 100644
--- a/installed-tests/js/testEverythingEncapsulated.js
+++ b/installed-tests/js/testEverythingEncapsulated.js
@@ -1,164 +1,191 @@
-// This used to be called "Everything"
-const JSUnit = imports.jsUnit;
-const Everything = imports.gi.Regress;
-
-function testStruct() {
-    let struct = new Everything.TestStructA();
-    struct.some_int = 42;
-    struct.some_int8 = 43;
-    struct.some_double = 42.5;
-    struct.some_enum = Everything.TestEnum.VALUE3;
-    JSUnit.assertEquals(42, struct.some_int);
-    JSUnit.assertEquals(43, struct.some_int8);
-    JSUnit.assertEquals(42.5, struct.some_double);
-    JSUnit.assertEquals(Everything.TestEnum.VALUE3, struct.some_enum);
-    let b = struct.clone();
-    JSUnit.assertEquals(42, b.some_int);
-    JSUnit.assertEquals(43, b.some_int8);
-    JSUnit.assertEquals(42.5, b.some_double);
-    JSUnit.assertEquals(Everything.TestEnum.VALUE3, b.some_enum);
-
-    struct = new Everything.TestStructB();
-    struct.some_int8 = 43;
-    struct.nested_a.some_int8 = 66;
-    JSUnit.assertEquals(43, struct.some_int8);
-    JSUnit.assertEquals(66, struct.nested_a.some_int8);
-    b = struct.clone();
-    JSUnit.assertEquals(43, b.some_int8);
-    JSUnit.assertEquals(66, struct.nested_a.some_int8);
-}
-
-function testStructConstructor()
-{
-    // "Copy" an object from a hash of field values
-    let struct = new Everything.TestStructA({ some_int: 42,
-                                              some_int8: 43,
-                                              some_double: 42.5,
-                                              some_enum: Everything.TestEnum.VALUE3 });
-
-    JSUnit.assertEquals(42, struct.some_int);
-    JSUnit.assertEquals(43, struct.some_int8);
-    JSUnit.assertEquals(42.5, struct.some_double);
-    JSUnit.assertEquals(Everything.TestEnum.VALUE3, struct.some_enum);
-
-    // Make sure we catch bad field names
-    JSUnit.assertRaises(function() {
-        let t = new Everything.TestStructA({ junk: 42 });
+const Regress = imports.gi.Regress;
+
+describe('Introspected structs', function () {
+    let struct;
+
+    describe('simple', function () {
+        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 () {
+            let 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);
+        });
     });
 
-    // Copy an object from another object of the same type, shortcuts to memcpy()
-    let copy = new Everything.TestStructA(struct);
-
-    JSUnit.assertEquals(42, copy.some_int);
-    JSUnit.assertEquals(43, copy.some_int8);
-    JSUnit.assertEquals(42.5, copy.some_double);
-    JSUnit.assertEquals(Everything.TestEnum.VALUE3, copy.some_enum);
-}
-
-function testSimpleBoxed() {
-    let simple_boxed = new Everything.TestSimpleBoxedA();
-    simple_boxed.some_int = 42;
-    simple_boxed.some_int8 = 43;
-    simple_boxed.some_double = 42.5;
-    simple_boxed.some_enum = Everything.TestEnum.VALUE3;
-    JSUnit.assertEquals(42, simple_boxed.some_int);
-    JSUnit.assertEquals(43, simple_boxed.some_int8);
-    JSUnit.assertEquals(42.5, simple_boxed.some_double);
-    JSUnit.assertEquals(Everything.TestEnum.VALUE3, simple_boxed.some_enum);
-}
-
-function testBoxedCopyConstructor()
-{
-    // "Copy" an object from a hash of field values
-    let simple_boxed = new Everything.TestSimpleBoxedA({ some_int: 42,
-                                                         some_int8: 43,
-                                                         some_double: 42.5,
-                                                         some_enum: Everything.TestEnum.VALUE3 });
-
-    JSUnit.assertEquals(42, simple_boxed.some_int);
-    JSUnit.assertEquals(43, simple_boxed.some_int8);
-    JSUnit.assertEquals(42.5, simple_boxed.some_double);
-    JSUnit.assertEquals(Everything.TestEnum.VALUE3, simple_boxed.some_enum);
-
-    // Make sure we catch bad field names
-    JSUnit.assertRaises(function() {
-        let t = new Everything.TestSimpleBoxedA({ junk: 42 });
+    describe('nested', function () {
+        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 () {
+            let b = struct.clone();
+            expect(b.some_int8).toEqual(43);
+            expect(b.nested_a.some_int8).toEqual(66);
+        });
     });
 
-    // Copy an object from another object of the same type, shortcuts to the boxed copy
-    let copy = new Everything.TestSimpleBoxedA(simple_boxed);
-
-    JSUnit.assertTrue(copy instanceof Everything.TestSimpleBoxedA);
-    JSUnit.assertEquals(42, copy.some_int);
-    JSUnit.assertEquals(43, copy.some_int8);
-    JSUnit.assertEquals(42.5, copy.some_double);
-    JSUnit.assertEquals(Everything.TestEnum.VALUE3, copy.some_enum);
- }
-
-function testNestedSimpleBoxed() {
-    let simple_boxed = new Everything.TestSimpleBoxedB();
-
-    // Test reading fields and nested fields
-    simple_boxed.some_int8 = 42;
-    simple_boxed.nested_a.some_int = 43;
-    JSUnit.assertEquals(42, simple_boxed.some_int8);
-    JSUnit.assertEquals(43, simple_boxed.nested_a.some_int);
-
-    // Try assigning the nested struct field from an instance
-    simple_boxed.nested_a = new Everything.TestSimpleBoxedA({ some_int: 53 });
-    JSUnit.assertEquals(53, simple_boxed.nested_a.some_int);
-
-    // And directly from a hash of field values
-    simple_boxed.nested_a = { some_int: 63 };
-    JSUnit.assertEquals(63, simple_boxed.nested_a.some_int);
-
-    // Try constructing with a nested hash of field values
-    let simple2 = new Everything.TestSimpleBoxedB({
-        some_int8: 42,
-        nested_a: {
-            some_int: 43,
-            some_int8: 44,
-            some_double: 43.5
-        }
+    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 () {
+            let 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);
+        });
     });
-    JSUnit.assertEquals(42, simple2.some_int8);
-    JSUnit.assertEquals(43, simple2.nested_a.some_int);
-    JSUnit.assertEquals(44, simple2.nested_a.some_int8);
-    JSUnit.assertEquals(43.5, simple2.nested_a.some_double);
-}
-
-function testBoxed() {
-    let boxed = new Everything.TestBoxed();
-    boxed.some_int8 = 42;
-    JSUnit.assertEquals(42, boxed.some_int8);
-}
-
-function testTestStructFixedArray() {
-    let struct = new Everything.TestStructFixedArray();
-    struct.frob();
-    JSUnit.assertEquals(7, struct.just_int);
-    JSUnit.assertEquals(42, struct.array[0]);
-    JSUnit.assertEquals(43, struct.array[1]);
-    JSUnit.assertEquals(51, struct.array[9]);
-}
-
-function testComplexConstructor() {
-    let boxed = new Everything.TestBoxedD('abcd', 8);
-
-    JSUnit.assertEquals(12, boxed.get_magic());
-}
-
-function testComplexConstructorBackwardCompatibility() {
+
+    it('containing fixed array', function () {
+        let 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]);
+    });
+});
+
+describe('Introspected boxed types', function () {
+    let simple_boxed;
+
+    it('sets fields correctly', function () {
+        simple_boxed = new Regress.TestSimpleBoxedA();
+        simple_boxed.some_int = 42;
+        simple_boxed.some_int8 = 43;
+        simple_boxed.some_double = 42.5;
+        simple_boxed.some_enum = Regress.TestEnum.VALUE3;
+        expect(simple_boxed.some_int).toEqual(42);
+        expect(simple_boxed.some_int8).toEqual(43);
+        expect(simple_boxed.some_double).toEqual(42.5);
+        expect(simple_boxed.some_enum).toEqual(Regress.TestEnum.VALUE3);
+
+        let boxed = new Regress.TestBoxed();
+        boxed.some_int8 = 42;
+        expect(boxed.some_int8).toEqual(42);
+    });
+
+    describe('copy constructors', function () {
+        beforeEach(function () {
+            simple_boxed = 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(simple_boxed.some_int).toEqual(42);
+            expect(simple_boxed.some_int8).toEqual(43);
+            expect(simple_boxed.some_double).toEqual(42.5);
+            expect(simple_boxed.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 () {
+            let copy = new Regress.TestSimpleBoxedA(simple_boxed);
+            expect(copy instanceof Regress.TestSimpleBoxedA).toBeTruthy();
+            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('nested', function () {
+        beforeEach(function () {
+            simple_boxed = new Regress.TestSimpleBoxedB();
+        });
+
+        it('reads fields and nested fields', function () {
+            simple_boxed.some_int8 = 42;
+            simple_boxed.nested_a.some_int = 43;
+            expect(simple_boxed.some_int8).toEqual(42);
+            expect(simple_boxed.nested_a.some_int).toEqual(43);
+        });
+
+        it('assigns nested struct field from an instance', function () {
+            simple_boxed.nested_a = new Regress.TestSimpleBoxedA({ some_int: 53 });
+            expect(simple_boxed.nested_a.some_int).toEqual(53);
+        });
+
+        it('assigns nested struct field directly from a hash of field values', function () {
+            simple_boxed.nested_a = { some_int: 63 };
+            expect(simple_boxed.nested_a.some_int).toEqual(63);
+        });
+    });
+
+    it('constructs with a nested hash of field values', function () {
+        let 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('constructs using a custom constructor', function () {
+        let boxed = new Regress.TestBoxedD('abcd', 8);
+        expect(boxed.get_magic()).toEqual(12);
+    });
+
     // RegressTestBoxedB 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.
-    let boxed = new Everything.TestBoxedB({ some_int8: 7, some_long: 5 });
-
-    JSUnit.assertEquals(7, boxed.some_int8);
-    JSUnit.assertEquals(5, boxed.some_long);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
-
+    it('constructs using a custom constructor in backwards compatibility mode', function () {
+        let boxed = new Regress.TestBoxedB({ some_int8: 7, some_long: 5 });
+        expect(boxed.some_int8).toEqual(7);
+        expect(boxed.some_long).toEqual(5);
+    });
+});
diff --git a/installed-tests/js/testExceptions.js b/installed-tests/js/testExceptions.js
index cf16ad8..d5fcd4b 100644
--- a/installed-tests/js/testExceptions.js
+++ b/installed-tests/js/testExceptions.js
@@ -1,4 +1,3 @@
-const JSUnit = imports.jsUnit;
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const GObject = imports.gi.GObject;
@@ -29,157 +28,147 @@ const Bar = new Lang.Class({
     }
 });
 
-function testExceptionInPropertySetter() {
-    let foo = new Foo();
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-                             'JS ERROR: Error: set*');
+describe('Exceptions', function () {
+    it('are thrown from property setter', function () {
+        let foo = new Foo();
+        expect(() => foo.prop = 'bar').toThrowError(/set/);
+    });
 
-    try {
-       foo.prop = 'bar';
-    } catch (e) {
-       logError(e);
-    }
+    it('are thrown from property getter', function () {
+        let foo = new Foo();
+        expect(() => foo.prop).toThrowError(/get/);
+    });
 
-    GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
-                                                'testExceptionInPropertySetter');
-}
+    // FIXME: In the next cases the errors aren't thrown but logged
 
-function testExceptionInPropertyGetter() {
-    let foo = new Foo();
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-                             'JS ERROR: Error: get*');
+    it('are logged from constructor', function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: Error: set*');
 
-    try {
-       let bar = foo.prop;
-    } catch (e) {
-       logError(e);
-    }
+        new Foo({ prop: 'bar' });
 
-    GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
-                                                'testExceptionInPropertyGetter');
-}
+        GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
+            'testExceptionInPropertySetterFromConstructor');
+    });
 
-function testExceptionInPropertySetterFromConstructor() {
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-                             'JS ERROR: Error: set*');
+    it('are logged from property setter with binding', function () {
+        let foo = new Foo();
+        let bar = new Bar();
 
-    try {
-       let foo = new Foo({ prop: 'bar' });
-    } catch (e) {
-       logError(e);
-    }
+        bar.bind_property('prop',
+            foo, 'prop',
+            GObject.BindingFlags.DEFAULT);
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: Error: set*');
 
-    GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
-                                                'testExceptionInPropertySetterFromConstructor');
-}
-
-function testExceptionInPropertySetterWithBinding() {
-    let foo = new Foo();
-    let bar = new Bar();
-
-    bar.bind_property('prop',
-                     foo, 'prop',
-                     GObject.BindingFlags.DEFAULT);
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-                             'JS ERROR: Error: set*');
-
-    try {
-       // wake up the binding so that g_object_set() is called on foo
-       bar.notify('prop');
-    } catch (e) {
-       logError(e);
-    }
+        // wake up the binding so that g_object_set() is called on foo
+        bar.notify('prop');
 
-    GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
-                                                'testExceptionInPropertySetterWithBinding');
-}
-
-function testExceptionInPropertyGetterWithBinding() {
-    let foo = new Foo();
-    let bar = new Bar();
-
-    foo.bind_property('prop',
-                     bar, 'prop',
-                     GObject.BindingFlags.DEFAULT);
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-                             'JS ERROR: Error: get*');
-
-    try {
-       // wake up the binding so that g_object_get() is called on foo
-       foo.notify('prop');
-    } catch (e) {
-       logError(e);
-    }
+        GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
+            'testExceptionInPropertySetterWithBinding');
+    });
 
-    GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
-                                                'testExceptionInPropertyGetterWithBinding');
-}
-
-function testGErrorMessages() {
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: Gio.IOErrorEnum: *');
-    try {
-        let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist");
-        file.read(null);
-    } catch(e) {
-        logError(e);
-    }
+    it('are logged from property getter with binding', function () {
+        let foo = new Foo();
+        let bar = new Bar();
 
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: Gio.IOErrorEnum: a message\ntestGErrorMessages@*');
-    try {
-        throw new Gio.IOErrorEnum({ message: 'a message', code: 0 });
-    } catch(e) {
-        logError(e);
-    }
+        foo.bind_property('prop',
+            bar, 'prop',
+            GObject.BindingFlags.DEFAULT);
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: Error: get*');
 
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: Gio.IOErrorEnum: a message\ntestGErrorMessages@*');
-    logError(new Gio.IOErrorEnum({ message: 'a message', code: 0 }));
+        // wake up the binding so that g_object_get() is called on foo
+        foo.notify('prop');
 
-    // No stack for GLib.Error constructor
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: Gio.IOErrorEnum: a message');
-    logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message'));
+        GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
+            'testExceptionInPropertyGetterWithBinding');
+    });
+});
 
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: GLib.Error my-error: a message');
-    logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message'));
+describe('logError', function () {
+    afterEach(function () {
+        GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js',
+            0, 'testGErrorMessages');
+    });
+
+    it('logs a warning for a GError', function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: Gio.IOErrorEnum: *');
+        try {
+            let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist");
+            file.read(null);
+        } catch(e) {
+            logError(e);
+        }
+    });
+
+    it('logs a warning with a message if given', function marker() {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*');
+        try {
+            throw new Gio.IOErrorEnum({ message: 'a message', code: 0 });
+        } catch(e) {
+            logError(e);
+        }
+    });
+
+    it('also logs an error for a created GError that is not thrown', function marker() {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: Gio.IOErrorEnum: a message\nmarker@*');
+        logError(new Gio.IOErrorEnum({ message: 'a message', code: 0 }));
+    });
+
+    it('logs an error with no stack trace for an error created with the GLib.Error constructor', function () 
{
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: Gio.IOErrorEnum: a message');
+        logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message'));
+    });
+
+    it('logs the quark for a JS-created GError type', function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: GLib.Error my-error: a message');
+        logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message'));
+    });
 
     // Now with prefix
 
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: prefix: Gio.IOErrorEnum: *');
-    try {
-        let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist");
-        file.read(null);
-    } catch(e) {
-        logError(e, 'prefix');
-    }
-
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: prefix: Gio.IOErrorEnum: a message\ntestGErrorMessages@*');
-    try {
-        throw new Gio.IOErrorEnum({ message: 'a message', code: 0 });
-    } catch(e) {
-        logError(e, 'prefix');
-    }
-
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: prefix: Gio.IOErrorEnum: a message\ntestGErrorMessages@*');
-    logError(new Gio.IOErrorEnum({ message: 'a message', code: 0 }), 'prefix');
-
-    // No stack for GLib.Error constructor
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: prefix: Gio.IOErrorEnum: a message');
-    logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message'), 'prefix');
-
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-        'JS ERROR: prefix: GLib.Error my-error: a message');
-    logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message'), 'prefix');
-
-    GLib.test_assert_expected_messages_internal('Gjs', 'testExceptions.js', 0,
-        'testGErrorMessages');
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('logs an error with a prefix if given', function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: prefix: Gio.IOErrorEnum: *');
+        try {
+            let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist");
+            file.read(null);
+        } catch(e) {
+            logError(e, 'prefix');
+        }
+    });
+
+    it('logs an error with prefix and message', function marker() {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: prefix: Gio.IOErrorEnum: a message\nmarker@*');
+        try {
+            throw new Gio.IOErrorEnum({ message: 'a message', code: 0 });
+        } catch(e) {
+            logError(e, 'prefix');
+        }
+    });
+
+    it('logs a non-thrown error with prefix', function marker() {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: prefix: Gio.IOErrorEnum: a message\nmarker@*');
+        logError(new Gio.IOErrorEnum({ message: 'a message', code: 0 }), 'prefix');
+    });
+
+    it('logs a GLib.Error with prefix', function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: prefix: Gio.IOErrorEnum: a message');
+        logError(new GLib.Error(Gio.IOErrorEnum, 0, 'a message'), 'prefix');
+    });
+
+    it('logs a JS-created GLib.Error with prefix', function () {
+        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+            'JS ERROR: prefix: GLib.Error my-error: a message');
+        logError(new GLib.Error(GLib.quark_from_string('my-error'), 0, 'a message'), 'prefix');
+    });
+});
diff --git a/installed-tests/js/testFormat.js b/installed-tests/js/testFormat.js
index 000e78d..72d2028 100644
--- a/installed-tests/js/testFormat.js
+++ b/installed-tests/js/testFormat.js
@@ -1,66 +1,60 @@
-// tests for imports.format module
-
 const Format = imports.format;
-const JSUnit = imports.jsUnit;
-
-function testEscape() {
-    var foo = '%d%%'.format(10);
-    JSUnit.assertEquals("escaped '%'", "10%", foo);
-}
-
-function testStrings() {
-    var foo = '%s'.format("Foo");
-    var foobar = '%s %s'.format("Foo", "Bar");
-    var barfoo = '%2$s %1$s'.format("Foo", "Bar");
-
-    JSUnit.assertEquals("single string argument", "Foo", foo);
-    JSUnit.assertEquals("two string arguments", "Foo Bar", foobar);
-    JSUnit.assertEquals("two swapped string arguments", "Bar Foo", barfoo);
-}
-
-function testFixedNumbers() {
-    var foo = '%d'.format(42);
-    var bar = '%x'.format(42);
-
-    JSUnit.assertEquals("base-10 42", "42", foo);
-    JSUnit.assertEquals("base-16 42", "2a", bar);
-}
-
-function testFloating() {
-    var foo = '%f'.format(0.125);
-    var bar = '%.2f'.format(0.125);
-
-    JSUnit.assertEquals("0.125, no precision", "0.125", foo);
-    JSUnit.assertEquals("0.125, precision 2", "0.13", bar);
-}
-
-function testPadding() {
-    let zeroFormat = '%04d';
-    var foo1 = zeroFormat.format(1);
-    var foo10 = zeroFormat.format(10);
-    var foo100 = zeroFormat.format(100);
-
-    let spaceFormat = '%4d';
-    var bar1 = spaceFormat.format(1);
-    var bar10 = spaceFormat.format(10);
-    var bar100 = spaceFormat.format(100);
-
-    JSUnit.assertEquals("zero-padding 1", "0001", foo1);
-    JSUnit.assertEquals("zero-padding 10", "0010", foo10);
-    JSUnit.assertEquals("zero-padding 100", "0100", foo100);
-
-    JSUnit.assertEquals("space-padding 1", "   1", bar1);
-    JSUnit.assertEquals("space-padding 10", "  10", bar10);
-    JSUnit.assertEquals("space-padding 100", " 100", bar100);
-}
-
-function testErrors() {
-    JSUnit.assertRaises(function() { return '%z'.format(42); });
-    JSUnit.assertRaises(function() { return '%.2d'.format(42); });
-    JSUnit.assertRaises(function() { return '%Ix'.format(42); });
-    JSUnit.assertRaises(function() { return '%2$d %d %1$d'.format(1, 2, 3); });
-}
-
 String.prototype.format = Format.format;
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
 
+describe('imports.format', function () {
+    it('escapes % with another % character', function () {
+        expect('%d%%'.format(10)).toEqual('10%');
+    });
+
+    it('formats a single string argument', function () {
+        expect('%s'.format('Foo')).toEqual('Foo');
+    });
+
+    it('formats two string arguments', function () {
+        expect('%s %s'.format('Foo', 'Bar')).toEqual('Foo Bar');
+    });
+
+    it('formats two swapped string arguments', function () {
+        expect('%2$s %1$s'.format('Foo', 'Bar')).toEqual('Bar Foo');
+    });
+
+    it('formats a number in base 10', function () {
+        expect('%d'.format(42)).toEqual('42');
+    });
+
+    it('formats a number in base 16', function () {
+        expect('%x'.format(42)).toEqual('2a');
+    });
+
+    it('formats a floating point number with no precision', function () {
+        expect('%f'.format(0.125)).toEqual('0.125');
+    });
+
+    it('formats a floating point number with precision 2', function () {
+        expect('%.2f'.format(0.125)).toEqual('0.13');
+    });
+
+    it('pads with zeroes', function () {
+        let zeroFormat = '%04d';
+        expect(zeroFormat.format(1)).toEqual('0001');
+        expect(zeroFormat.format(10)).toEqual('0010');
+        expect(zeroFormat.format(100)).toEqual('0100');
+    });
+
+    it('pads with spaces', function () {
+        let spaceFormat = '%4d';
+        expect(spaceFormat.format(1)).toEqual('   1');
+        expect(spaceFormat.format(10)).toEqual('  10');
+        expect(spaceFormat.format(100)).toEqual(' 100');
+    });
+
+    it('throws an error when given incorrect modifiers for the conversion type', function () {
+        expect(() => '%z'.format(42)).toThrow();
+        expect(() => '%.2d'.format(42)).toThrow();
+        expect(() => '%Ix'.format(42)).toThrow();
+    });
+
+    it('throws an error when incorrectly instructed to swap arguments', function () {
+        expect(() => '%2$d %d %1$d'.format(1, 2, 3)).toThrow();
+    });
+});
diff --git a/installed-tests/js/testFundamental.js b/installed-tests/js/testFundamental.js
index 24031ca..ff94a23 100644
--- a/installed-tests/js/testFundamental.js
+++ b/installed-tests/js/testFundamental.js
@@ -1,16 +1,7 @@
-// application/javascript;version=1.8
+const Regress = imports.gi.Regress;
 
-const JSUnit = imports.jsUnit;
-const Everything = imports.gi.Regress;
-const WarnLib = imports.gi.WarnLib;
-
-const GLib = imports.gi.GLib;
-const Gio = imports.gi.Gio;
-const GObject = imports.gi.GObject;
-const Lang = imports.lang;
-
-function testFundamental() {
-    let f = new Everything.TestFundamentalSubObject('plop');
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+describe('Fundamental type support', function () {
+    it('constructs a subtype of a fundamental type', function () {
+        expect(() => new Regress.TestFundamentalSubObject('plop')).not.toThrow();
+    });
+});
diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js
index b67feb0..99d081b 100644
--- a/installed-tests/js/testGIMarshalling.js
+++ b/installed-tests/js/testGIMarshalling.js
@@ -1,15 +1,4 @@
-if (!('assertEquals' in this)) { /* allow running this test standalone */
-    imports.lang.copyPublicProperties(imports.jsUnit, this);
-    gjstestRun = function() { return imports.jsUnit.gjstestRun(this); };
-}
-
-function assertArrayEquals(expected, got) {
-    assertEquals(expected.length, got.length);
-    for (let i = 0; i < expected.length; i ++) {
-        assertEquals(expected[i], got[i]);
-    }
-}
-
+const ByteArray = imports.byteArray;
 const GIMarshallingTests = imports.gi.GIMarshallingTests;
 
 // We use Gio and GLib to have some objects that we know exist
@@ -18,243 +7,296 @@ const GLib = imports.gi.GLib;
 const GObject = imports.gi.GObject;
 const Lang = imports.lang;
 
-function testCArray() {
-    var array, sum;
-
-    var result = GIMarshallingTests.init_function(null);
-    assertEquals(result.length, 2);
-    var success = result[0];
-    var newArray = result[1];
-    assertEquals(newArray.length, 0);
-
-    array = GIMarshallingTests.array_zero_terminated_return();
-    assertEquals("0", array[0]);
-    assertEquals("1", array[1]);
-    assertEquals("2", array[2]);
-    assertEquals(3, array.length);
-
-    array = GIMarshallingTests.array_zero_terminated_return_struct();
-    assertEquals(3, array.length);
-    assertEquals(42, array[0].long_);
-    assertEquals(43, array[1].long_);
-    assertEquals(44, array[2].long_);
-
-    array = GIMarshallingTests.array_return();
-    assertEquals(4, array.length);
-    assertEquals(-1, array[0]);
-    assertEquals(0, array[1]);
-    assertEquals(1, array[2]);
-    assertEquals(2, array[3]);
-
-    [array, sum] = GIMarshallingTests.array_return_etc(9, 5);
-    assertEquals(14, sum);
-    assertEquals(4, array.length);
-    assertEquals(9, array[0]);
-    assertEquals(0, array[1]);
-    assertEquals(1, array[2]);
-    assertEquals(5, array[3]);
-
-    array = GIMarshallingTests.array_out();
-    assertEquals(4, array.length);
-    assertEquals(-1, array[0]);
-    assertEquals(0, array[1]);
-    assertEquals(1, array[2]);
-    assertEquals(2, array[3]);
-
-    [array, sum] = GIMarshallingTests.array_out_etc(9, 5);
-    assertEquals(14, sum);
-    assertEquals(4, array.length);
-    assertEquals(9, array[0]);
-    assertEquals(0, array[1]);
-    assertEquals(1, array[2]);
-    assertEquals(5, array[3]);
-
-    array = GIMarshallingTests.array_bool_out();
-    assertEquals(4, array.length);
-    assertEquals(true, array[0]);
-    assertEquals(false, array[1]);
-    assertEquals(true, array[2]);
-    assertEquals(true, array[3]);
-
-    assertEquals('const \u2665 utf8', GIMarshallingTests.array_unichar_out());
-    assertEquals('const \u2665 utf8',
-        GIMarshallingTests.array_zero_terminated_return_unichar());
-
-    array = GIMarshallingTests.array_inout([-1, 0, 1, 2]);
-    assertEquals(5, array.length);
-    assertEquals(-2, array[0]);
-    assertEquals(-1, array[1]);
-    assertEquals(0, array[2]);
-    assertEquals(1, array[3]);
-    assertEquals(2, array[4]);
-
-    [array, sum] = GIMarshallingTests.array_inout_etc(9, [-1, 0, 1, 2], 5);
-    assertEquals(14, sum);
-    assertEquals(5, array.length);
-    assertEquals(9, array[0]);
-    assertEquals(-1, array[1]);
-    assertEquals(0, array[2]);
-    assertEquals(1, array[3]);
-    assertEquals(5, array[4]);
-
-    GIMarshallingTests.array_string_in(["foo", "bar"]);
-
-    array = [];
-    for (var i = 0; i < 3; i++) {
-       array[i] = new GIMarshallingTests.BoxedStruct();
-       array[i].long_ = i + 1;
+describe('C array', function () {
+    function createStructArray() {
+        return [1, 2, 3].map(num => {
+            let struct = new GIMarshallingTests.BoxedStruct();
+            struct.long_ = num;
+            return struct;
+        });
     }
 
-    GIMarshallingTests.array_struct_in(array);
+    it('can be passed to a function', function () {
+        expect(() => GIMarshallingTests.array_in([-1, 0, 1, 2])).not.toThrow();
+    });
+
+    it('can be passed to a function with its length parameter before it', function () {
+        expect(() => GIMarshallingTests.array_in_len_before([-1, 0, 1, 2]))
+            .not.toThrow();
+    });
+
+    it('can be passed to a function with zero terminator', function () {
+        expect(() => GIMarshallingTests.array_in_len_zero_terminated([-1, 0, 1, 2]))
+            .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([]);
+    });
+
+    it('can be returned with zero terminator', function () {
+        expect(GIMarshallingTests.array_zero_terminated_return())
+            .toEqual(['0', '1', '2']);
+    });
+
+    it('can be returned', function () {
+        expect(GIMarshallingTests.array_return()).toEqual([-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 an out argument', function () {
+        expect(GIMarshallingTests.array_out()).toEqual([-1, 0, 1, 2]);
+    });
+
+    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 an in-out argument', function () {
+        expect(GIMarshallingTests.array_inout([-1, 0, 1, 2]))
+            .toEqual([-2, -1, 0, 1, 2]);
+    });
+
+    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]);
+    });
 
     // Run twice to ensure that copies are correctly made for (transfer full)
-    GIMarshallingTests.array_struct_take_in(array);
-    GIMarshallingTests.array_struct_take_in(array);
-
-    GIMarshallingTests.array_uint8_in ("abcd");
-    GIMarshallingTests.array_unichar_in('const \u2665 utf8');
-    GIMarshallingTests.array_unichar_in([0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20,
-        0x2665, 0x20, 0x75, 0x74, 0x66, 0x38]);
-    GIMarshallingTests.array_enum_in([GIMarshallingTests.Enum.VALUE1,
-                                     GIMarshallingTests.Enum.VALUE2,
-                                     GIMarshallingTests.Enum.VALUE3]);
-
-    array = [-1, 0, 1, 2];
-    GIMarshallingTests.array_in(array);
-    GIMarshallingTests.array_in_len_before(array);
-    GIMarshallingTests.array_in_len_zero_terminated(array);
-    GIMarshallingTests.array_in_guint64_len(array);
-    GIMarshallingTests.array_in_guint8_len(array);
-    GIMarshallingTests.array_int64_in(array);
-    GIMarshallingTests.array_uint64_in(array);
-    GIMarshallingTests.array_bool_in(array);
-}
+    it('copies correctly on transfer full', function () {
+        let array = createStructArray();
+        expect(() => {
+            GIMarshallingTests.array_struct_take_in(array);
+            GIMarshallingTests.array_struct_take_in(array);
+        }).not.toThrow();
+    });
 
-function testGArray() {
-    var array;
-    array = GIMarshallingTests.garray_int_none_return();
-    assertEquals(-1, array[0]);
-    assertEquals(0, array[1]);
-    assertEquals(1, array[2]);
-    assertEquals(2, array[3]);
-    array = GIMarshallingTests.garray_utf8_none_return()
-    assertEquals("0", array[0]);
-    assertEquals("1", array[1]);
-    assertEquals("2", array[2]);
-    array = GIMarshallingTests.garray_utf8_container_return()
-    assertEquals("0", array[0]);
-    assertEquals("1", array[1]);
-    assertEquals("2", array[2]);
-    array = GIMarshallingTests.garray_utf8_full_return()
-    assertEquals("0", array[0]);
-    assertEquals("1", array[1]);
-    assertEquals("2", array[2]);
-
-    GIMarshallingTests.garray_int_none_in([-1, 0, 1, 2]);
-    GIMarshallingTests.garray_utf8_none_in(["0", "1", "2"]);
-    GIMarshallingTests.garray_bool_none_in([-1, 0, 1, 2]);
-    GIMarshallingTests.garray_unichar_none_in('const \u2665 utf8');
-    GIMarshallingTests.garray_unichar_none_in([0x63, 0x6f, 0x6e, 0x73, 0x74,
-        0x20, 0x2665, 0x20, 0x75, 0x74, 0x66, 0x38]);
-
-    array = GIMarshallingTests.garray_utf8_none_out();
-    assertEquals("0", array[0]);
-    assertEquals("1", array[1]);
-    assertEquals("2", array[2]);
-    array = GIMarshallingTests.garray_utf8_container_out();
-    assertEquals("0", array[0]);
-    assertEquals("1", array[1]);
-    assertEquals("2", array[2]);
-    array = GIMarshallingTests.garray_utf8_full_out();
-    assertEquals("0", array[0]);
-    assertEquals("1", array[1]);
-    assertEquals("2", array[2]);
-}
+    describe('of structs', function () {
+        it('can be passed to a function', function () {
+            expect(() => GIMarshallingTests.array_struct_in(createStructArray()))
+                .not.toThrow();
+        });
 
-function testByteArray() {
-    var i = 0;
-    var refByteArray = new imports.byteArray.ByteArray();
-    refByteArray[i++] = 0;
-    refByteArray[i++] = 49;
-    refByteArray[i++] = 0xFF;
-    refByteArray[i++] = 51;
-    var byteArray = GIMarshallingTests.bytearray_full_return();
-    assertEquals(refByteArray.length, byteArray.length);
-    for (i = 0; i < refByteArray.length; i++)
-       assertEquals(refByteArray[i], byteArray[i]);
-    GIMarshallingTests.bytearray_none_in(refByteArray);
-
-    // Another test, with a normal array, to test conversion
-    GIMarshallingTests.bytearray_none_in([0, 49, 0xFF, 51]);
-}
+        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]);
+        });
+    });
 
-function testGBytes() {
-    var i = 0;
-    var refByteArray = new imports.byteArray.ByteArray();
-    refByteArray[i++] = 0;
-    refByteArray[i++] = 49;
-    refByteArray[i++] = 0xFF;
-    refByteArray[i++] = 51;
-    GIMarshallingTests.gbytes_none_in(refByteArray);
-
-    var bytes = GIMarshallingTests.gbytes_full_return();
-    GIMarshallingTests.gbytes_none_in(bytes);
-
-    var array = bytes.toArray();
-    assertEquals(array[0], 0);
-    assertEquals(array[1], 49);
-    assertEquals(array[2], 0xFF);
-    assertEquals(array[3], 51); 
-    
-    bytes = GLib.Bytes.new([0, 49, 0xFF, 51]);
-    GIMarshallingTests.gbytes_none_in(bytes);
-
-    bytes = GLib.Bytes.new("const \u2665 utf8");
-    GIMarshallingTests.utf8_as_uint8array_in(bytes.toArray());
-
-    bytes = GIMarshallingTests.gbytes_full_return();    
-    array = bytes.toArray(); // Array should just be holding a ref, not a copy
-    assertEquals(array[1], 49);
-    array[1] = 42;  // Assignment should force to GByteArray
-    assertEquals(array[1], 42);
-    array[1] = 49;  // Flip the value back
-    GIMarshallingTests.gbytes_none_in(array.toGBytes()); // Now convert back to GBytes
-
-    bytes = GLib.Bytes.new([97, 98, 99, 100]);
-    GIMarshallingTests.array_uint8_in(bytes.toArray());
-    assertRaises(function() {
-       GIMarshallingTests.array_uint8_in(bytes);
+    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]);
+        });
     });
-}
 
-function testPtrArray() {
-    var array;
+    describe('of unichars', function () {
+        it('can be passed to a function', function () {
+            expect(() => GIMarshallingTests.array_unichar_in('const \u2665 utf8'))
+                .not.toThrow();
+        });
 
-    GIMarshallingTests.gptrarray_utf8_none_in(["0", "1", "2"]);
+        it('can be an out argument', function () {
+            expect(GIMarshallingTests.array_unichar_out())
+                .toEqual('const \u2665 utf8');
+        });
 
-    var refArray = ["0", "1", "2"];
+        it('can be returned with zero terminator', function () {
+            expect(GIMarshallingTests.array_zero_terminated_return_unichar())
+                .toEqual('const \u2665 utf8');
+        });
 
-    assertArrayEquals(refArray, GIMarshallingTests.gptrarray_utf8_none_return());
-    assertArrayEquals(refArray, GIMarshallingTests.gptrarray_utf8_container_return());
-    assertArrayEquals(refArray, GIMarshallingTests.gptrarray_utf8_full_return());
+        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();
+        });
+    });
 
-    assertArrayEquals(refArray, GIMarshallingTests.gptrarray_utf8_none_out());
-    assertArrayEquals(refArray, GIMarshallingTests.gptrarray_utf8_container_out());
-    assertArrayEquals(refArray, GIMarshallingTests.gptrarray_utf8_full_out());
-}
+    describe('of strings', function () {
+        it('can be passed to a function', function () {
+            expect(() => GIMarshallingTests.array_string_in(['foo', 'bar']))
+                .not.toThrow();
+        });
+    });
 
-function testGHashTable() {
-    function dictEquals(dict, ref) {
-        let dict_keys = Object.keys(dict);
-        let ref_keys = Object.keys(ref);
-        assertEquals(ref_keys.length, dict_keys.length);
-        ref_keys.forEach((key, ix) => {
-            assertEquals(key, dict_keys[ix]);
-            assertEquals(ref[key], dict[key]);
+    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();
         });
-    }
+    });
+
+    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('can be implicitly converted from a string', function () {
+            expect(() => GIMarshallingTests.array_uint8_in('abcd')).not.toThrow();
+        });
+    });
+
+    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();
+        });
+
+        it('can be an in argument with length', function () {
+            expect(() => GIMarshallingTests.array_in_guint64_len([-1, 0, 1, 2]))
+                .not.toThrow();
+        });
+    });
+});
+
+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();
+        });
+
+        it('can be returned with transfer none', function () {
+            expect(GIMarshallingTests.garray_int_none_return())
+                .toEqual([-1, 0, 1, 2]);
+        });
+    });
+
+    describe('of strings', function () {
+        it('can be passed in with transfer none', function () {
+            expect(() => GIMarshallingTests.garray_utf8_none_in(['0', '1', '2']))
+                .not.toThrow();
+        });
+
+        ['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']);
+                });
+            });
+        });
+    });
+
+    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();
+        });
+    });
+
+    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('GByteArray', function () {
+    const refByteArray = ByteArray.fromArray([0, 49, 0xFF, 51]);
+
+    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();
+    });
+});
+
+describe('GBytes', function () {
+    const refByteArray = ByteArray.fromArray([0, 49, 0xFF, 51]);
+
+    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();
+    });
+
+    it('can be created by returning from a function and passed in', function () {
+        var bytes = GIMarshallingTests.gbytes_full_return();
+        expect(() => GIMarshallingTests.gbytes_none_in(bytes)).not.toThrow();
+        expect(bytes.toArray()).toEqual(refByteArray);
+    });
 
-    let INT_DICT = {
+    it('can be implicitly converted from a ByteArray', function () {
+        expect(() => GIMarshallingTests.gbytes_none_in(refByteArray))
+            .not.toThrow();
+    });
+
+    it('can be created from a string and is encoded in UTF-8', function () {
+        let bytes = GLib.Bytes.new("const \u2665 utf8");
+        expect(() => GIMarshallingTests.utf8_as_uint8array_in(bytes.toArray()))
+            .not.toThrow();
+    });
+
+    it('turns into a GByteArray on assignment', function () {
+        let bytes = GIMarshallingTests.gbytes_full_return();
+        let array = bytes.toArray();  // Array should just be holding a ref, not a copy
+        expect(array[1]).toEqual(49);
+        array[1] = 42;  // Assignment should force to GByteArray
+        expect(array[1]).toEqual(42);
+        array[1] = 49;  // Flip the value back
+        // Now convert back to GBytes
+        expect(() => GIMarshallingTests.gbytes_none_in(array.toGBytes()))
+            .not.toThrow();
+    });
+
+    it('cannot be passed to a function expecting a byte array', function () {
+        let bytes = GLib.Bytes.new([97, 98, 99, 100]);
+        expect(() => GIMarshallingTests.array_uint8_in(bytes.toArray())).not.toThrow();
+        expect(() => GIMarshallingTests.array_uint8_in(bytes)).toThrow();
+    });
+});
+
+describe('GPtrArray', function () {
+    const refArray = ['0', '1', '2'];
+
+    it('can be passed to a function with transfer none', function () {
+        expect(() => GIMarshallingTests.gptrarray_utf8_none_in(refArray))
+            .not.toThrow();
+    });
+
+    ['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);
+            });
+        });
+    });
+});
+
+describe('GHashTable', function () {
+    const INT_DICT = {
         '-1': 1,
         0: 0,
         1: -1,
@@ -278,114 +320,221 @@ function testGHashTable() {
         1: '1',
     };
 
-    dictEquals(GIMarshallingTests.ghashtable_int_none_return(), INT_DICT);
-    dictEquals(GIMarshallingTests.ghashtable_utf8_none_return(), STRING_DICT);
-    dictEquals(GIMarshallingTests.ghashtable_utf8_container_return(), STRING_DICT);
-    dictEquals(GIMarshallingTests.ghashtable_utf8_full_return(), STRING_DICT);
-
-    GIMarshallingTests.ghashtable_int_none_in(INT_DICT);
-    GIMarshallingTests.ghashtable_utf8_none_in(STRING_DICT);
-    GIMarshallingTests.ghashtable_double_in(NUMBER_DICT);
-    GIMarshallingTests.ghashtable_float_in(NUMBER_DICT);
-    GIMarshallingTests.ghashtable_int64_in({
-        '-1': -1,
-        0: 0,
-        1: 1,
-        2: 0x100000000,
+    it('can be passed in with integer value type', function () {
+        expect(() => GIMarshallingTests.ghashtable_int_none_in(INT_DICT))
+            .not.toThrow();
     });
-    GIMarshallingTests.ghashtable_uint64_in({
-        '-1': 0x100000000,
-        0: 0,
-        1: 1,
-        2: 2,
+
+    it('can be passed in with string value type', function () {
+        expect(() => GIMarshallingTests.ghashtable_utf8_none_in(STRING_DICT))
+            .not.toThrow();
     });
 
-    dictEquals(GIMarshallingTests.ghashtable_utf8_none_out(), STRING_DICT);
-    dictEquals(GIMarshallingTests.ghashtable_utf8_container_out(), STRING_DICT);
-    dictEquals(GIMarshallingTests.ghashtable_utf8_full_out(), STRING_DICT);
+    it('can be passed in with float value type', function () {
+        expect(() => GIMarshallingTests.ghashtable_float_in(NUMBER_DICT))
+            .not.toThrow();
+    });
 
-    dictEquals(GIMarshallingTests.ghashtable_utf8_none_inout(STRING_DICT), STRING_DICT_OUT);
-    // FIXME: Container transfer for in parameters not supported
-    //dictEquals(GIMarshallingTests.ghashtable_utf8_container_inout(STRING_DICT), STRING_DICT_OUT);
-    // FIXME: Broken
-    //dictEquals(GIMarshallingTests.ghashtable_utf8_full_inout(STRING_DICT), STRING_DICT_OUT);
-}
+    it('can be passed in with double value type', function () {
+        expect(() => GIMarshallingTests.ghashtable_double_in(NUMBER_DICT))
+            .not.toThrow();
+    });
+
+    it('can be passed in with int64 value type', function () {
+        const int64Dict = {
+            '-1': -1,
+            0: 0,
+            1: 1,
+            2: 0x100000000,
+        };
+        expect(() => GIMarshallingTests.ghashtable_int64_in(int64Dict))
+            .not.toThrow();
+    });
 
-function testGValue() {
-    assertEquals(42, GIMarshallingTests.gvalue_return());
-    assertEquals(42, GIMarshallingTests.gvalue_out());
+    it('can be passed in with uint64 value type', function () {
+        const uint64Dict = {
+            '-1': 0x100000000,
+            0: 0,
+            1: 1,
+            2: 2,
+        };
+        expect(() => GIMarshallingTests.ghashtable_uint64_in(uint64Dict))
+            .not.toThrow();
+    });
 
-    GIMarshallingTests.gvalue_in(42);
-    GIMarshallingTests.gvalue_flat_array([42, "42", true]);
+    it('can be returned with integer value type', function () {
+        expect(GIMarshallingTests.ghashtable_int_none_return()).toEqual(INT_DICT);
+    });
 
-    // gjs doesn't support native enum types
-    // GIMarshallingTests.gvalue_in_enum(GIMarshallingTests.Enum.VALUE_3);
+    ['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);
+            });
+        });
+    });
 
-    // Test a flat GValue round-trip return
-    let thing = GIMarshallingTests.return_gvalue_flat_array();
-    assertArrayEquals([42, "42", true], thing);
-}
+    it('can be passed as inout with transfer none', function () {
+        expect(GIMarshallingTests.ghashtable_utf8_none_inout(STRING_DICT))
+            .toEqual(STRING_DICT_OUT);
+    });
 
-function testGType() {
-    assertEquals(GObject.TYPE_NONE, GIMarshallingTests.gtype_return());
-    assertEquals(GObject.TYPE_STRING, GIMarshallingTests.gtype_string_return());
+    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');
 
-    GIMarshallingTests.gtype_in(GObject.TYPE_NONE);
-    GIMarshallingTests.gtype_in(GObject.VoidType);
-    GIMarshallingTests.gtype_string_in(GObject.TYPE_STRING);
-    GIMarshallingTests.gtype_string_in(GObject.String);
-    GIMarshallingTests.gtype_string_in(String);
+    xit('can be passed as inout with transfer full', function () {
+        expect(GIMarshallingTests.ghashtable_utf8_full_inout(STRING_DICT))
+            .toEqual(STRING_DICT_OUT);
+    }).pend('https://bugzilla.gnome.org/show_bug.cgi?id=773763');
+});
 
-    assertEquals(GObject.TYPE_NONE, GIMarshallingTests.gtype_out());
-    assertEquals(GObject.TYPE_STRING, GIMarshallingTests.gtype_string_out());
+describe('GValue', function () {
+    it('can be passed into a function and packed', function () {
+        expect(() => GIMarshallingTests.gvalue_in(42)).not.toThrow();
+    });
 
-    assertEquals(GObject.TYPE_INT, GIMarshallingTests.gtype_inout(GObject.TYPE_NONE));
-}
+    it('array can be passed into a function and packed', function () {
+        expect(() => GIMarshallingTests.gvalue_flat_array([42, '42', true]))
+            .not.toThrow();
+    });
 
-function testGValueGType() {
-    // test that inferring the GType for a primitive value or an object works
-
-    // Primitives (and primitive like)
-    GIMarshallingTests.gvalue_in_with_type(42, GObject.TYPE_INT);
-    GIMarshallingTests.gvalue_in_with_type(42.5, GObject.TYPE_DOUBLE);
-    GIMarshallingTests.gvalue_in_with_type('42', GObject.TYPE_STRING);
-    GIMarshallingTests.gvalue_in_with_type(GObject.TYPE_GTYPE, GObject.TYPE_GTYPE);
-
-    GIMarshallingTests.gvalue_in_with_type(42, GObject.Int);
-    GIMarshallingTests.gvalue_in_with_type(42.5, GObject.Double);
-    GIMarshallingTests.gvalue_in_with_type(42.5, Number);
-
-    // Object and interface
-    GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction, Gio.SimpleAction);
-    GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction, GObject.Object);
-    GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction, GObject.TYPE_OBJECT);
-    GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction, Gio.SimpleAction);
-
-    // Boxed and union
-    GIMarshallingTests.gvalue_in_with_type(new GLib.KeyFile, GLib.KeyFile);
-    GIMarshallingTests.gvalue_in_with_type(new GLib.KeyFile, GObject.TYPE_BOXED);
-    GIMarshallingTests.gvalue_in_with_type(GLib.Variant.new('u', 42), GLib.Variant);
-    GIMarshallingTests.gvalue_in_with_type(GLib.Variant.new('u', 42), GObject.TYPE_VARIANT);
-    GIMarshallingTests.gvalue_in_with_type(new GIMarshallingTests.BoxedStruct, 
GIMarshallingTests.BoxedStruct);
-    GIMarshallingTests.gvalue_in_with_type(GIMarshallingTests.union_returnv(), GIMarshallingTests.Union);
-
-    // Other
-    GIMarshallingTests.gvalue_in_with_type(GObject.ParamSpec.string('my-param', '', '', 
GObject.ParamFlags.READABLE, ''),
-                                          GObject.TYPE_PARAM);
-
-    // // Foreign
-    // let Cairo;
-    // try {
-    //     Cairo = imports.cairo;
-    // } catch(e) {
-    //     return;
-    // }
-
-    // let surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 2, 2);
-    // let cr = new Cairo.Context(surface);
-    // GIMarshallingTests.gvalue_in_with_type(cr, Cairo.Context);
-    // GIMarshallingTests.gvalue_in_with_type(surface, Cairo.Surface);
-}
+    xit('enum can be passed into a function and packed', function () {
+        expect(() => GIMarshallingTests.gvalue_in_enum(GIMarshallingTests.Enum.VALUE_3))
+            .not.toThrow();
+    }).pend("GJS doesn't support native enum types");
+
+    it('can be returned and unpacked', function () {
+        expect(GIMarshallingTests.gvalue_return()).toEqual(42);
+    });
+
+    it('can be passed as an out argument and unpacked', function () {
+        expect(GIMarshallingTests.gvalue_out()).toEqual(42);
+    });
+
+    it('array can be passed as an out argument and unpacked', function () {
+        expect(GIMarshallingTests.return_gvalue_flat_array())
+            .toEqual([42, '42', true]);
+    });
+
+    it('can have its type inferred from primitive values', function () {
+        expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.TYPE_INT))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(42.5, GObject.TYPE_DOUBLE))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type('42', GObject.TYPE_STRING))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(GObject.TYPE_GTYPE, GObject.TYPE_GTYPE))
+            .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();
+    });
+
+    it('can have its type inferred as a GObject type', function () {
+        expect(() => GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction(), Gio.SimpleAction))
+            .not.toThrow();
+    });
+
+    it('can have its type inferred as a superclass', function () {
+        let action = new Gio.SimpleAction();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(action, GObject.Object))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(action, GObject.TYPE_OBJECT))
+            .not.toThrow();
+    });
+
+    it('can have its type inferred as an interface that it implements', function () {
+        expect(() => GIMarshallingTests.gvalue_in_with_type(new Gio.SimpleAction(), Gio.SimpleAction))
+            .not.toThrow();
+    });
+
+    it('can have its type inferred as a boxed type', function () {
+        let keyfile = new GLib.KeyFile();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(keyfile, GLib.KeyFile))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(keyfile, GObject.TYPE_BOXED))
+            .not.toThrow();
+        let struct = new GIMarshallingTests.BoxedStruct();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(struct, GIMarshallingTests.BoxedStruct))
+            .not.toThrow();
+    });
+
+    it('can have its type inferred as GVariant', function () {
+        let variant = GLib.Variant.new('u', 42);
+        expect(() => GIMarshallingTests.gvalue_in_with_type(variant, GLib.Variant))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(variant, GObject.TYPE_VARIANT))
+            .not.toThrow();
+    });
+
+    it('can have its type inferred as a union type', function () {
+        let union = GIMarshallingTests.union_returnv();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(union, GIMarshallingTests.Union))
+            .not.toThrow();
+    });
+
+    it('can have its type inferred as a GParamSpec', function () {
+        let paramSpec = GObject.ParamSpec.string('my-param', '', '',
+            GObject.ParamFlags.READABLE, '');
+        expect(() => GIMarshallingTests.gvalue_in_with_type(paramSpec, GObject.TYPE_PARAM))
+            .not.toThrow();
+    });
+
+    xit('can have its type inferred as a foreign struct', function () {
+        let Cairo;
+        try {
+            Cairo = imports.cairo;
+        } catch(e) {
+            pending('Compiled without Cairo support');
+            return;
+        }
+
+        let surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, 2, 2);
+        let cr = new Cairo.Context(surface);
+        expect(() => GIMarshallingTests.gvalue_in_with_type(cr, Cairo.Context))
+            .not.toThrow();
+        expect(() => GIMarshallingTests.gvalue_in_with_type(surface, Cairo.Surface))
+            .not.toThrow();
+    }).pend('Errors out with "not a subclass of GObject_Boxed"');
+});
+
+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);
+    });
+
+    it('can be passed as an inout argument', function () {
+        expect(GIMarshallingTests.gtype_inout(GObject.TYPE_NONE))
+            .toEqual(GObject.TYPE_INT);
+    });
+
+    it('can be implicitly converted from a JS type', function () {
+        expect(() => GIMarshallingTests.gtype_string_in(String)).not.toThrow();
+    });
+});
 
 function callback_return_value_only() {
     return 42;
@@ -407,27 +556,32 @@ function callback_return_value_and_multiple_out_parameters() {
     return [48, 49, 50];
 }
 
-function testCallbacks() {
-    let a, b, c;
-    a = GIMarshallingTests.callback_return_value_only(callback_return_value_only);
-    assertEquals(42, a);
+describe('Callback', function () {
+    it('marshals a return value', function () {
+        expect(GIMarshallingTests.callback_return_value_only(callback_return_value_only))
+            .toEqual(42);
+    });
 
-    a = GIMarshallingTests.callback_one_out_parameter(callback_one_out_parameter);
-    assertEquals(43, a);
+    it('marshals one out parameter', function () {
+        expect(GIMarshallingTests.callback_one_out_parameter(callback_one_out_parameter))
+            .toEqual(43);
+    });
 
-    [a, b] = GIMarshallingTests.callback_multiple_out_parameters(callback_multiple_out_parameters);
-    assertEquals(44, a);
-    assertEquals(45, b);
+    it('marshals multiple out parameters', function () {
+        expect(GIMarshallingTests.callback_multiple_out_parameters(callback_multiple_out_parameters))
+            .toEqual([44, 45]);
+    });
 
-    [a, b] = 
GIMarshallingTests.callback_return_value_and_one_out_parameter(callback_return_value_and_one_out_parameter);
-    assertEquals(46, a);
-    assertEquals(47, b);
+    it('marshals a return value and one out parameter', function () {
+        
expect(GIMarshallingTests.callback_return_value_and_one_out_parameter(callback_return_value_and_one_out_parameter))
+            .toEqual([46, 47]);
+    });
 
-    [a, b, c] = 
GIMarshallingTests.callback_return_value_and_multiple_out_parameters(callback_return_value_and_multiple_out_parameters);
-    assertEquals(48, a);
-    assertEquals(49, b);
-    assertEquals(50, c);
-}
+    it('marshals a return value and multiple out parameters', function () {
+        
expect(GIMarshallingTests.callback_return_value_and_multiple_out_parameters(callback_return_value_and_multiple_out_parameters))
+            .toEqual([48, 49, 50]);
+    });
+});
 
 const VFuncTester = new Lang.Class({
     Name: 'VFuncTester',
@@ -440,34 +594,39 @@ const VFuncTester = new Lang.Class({
     vfunc_vfunc_return_value_and_multiple_out_parameters: callback_return_value_and_multiple_out_parameters
 });
 
-function testVFuncs() {
-    let tester = new VFuncTester();
-    let a, b, c;
-    a = tester.vfunc_return_value_only();
-    assertEquals(42, a);
-
-    a = tester.vfunc_one_out_parameter();
-    assertEquals(43, a);
+describe('Virtual function', function () {
+    let tester;
+    beforeEach(function () {
+        tester = new VFuncTester();
+    });
 
-    [a, b] = tester.vfunc_multiple_out_parameters();
-    assertEquals(44, a);
-    assertEquals(45, b);
+    it('marshals a return value', function () {
+        expect(tester.vfunc_return_value_only()).toEqual(42);
+    });
 
-    [a, b] = tester.vfunc_return_value_and_one_out_parameter();
-    assertEquals(46, a);
-    assertEquals(47, b);
+    it('marshals one out parameter', function () {
+        expect(tester.vfunc_one_out_parameter()).toEqual(43);
+    });
 
-    [a, b, c] = tester.vfunc_return_value_and_multiple_out_parameters();
-    assertEquals(48, a);
-    assertEquals(49, b);
-    assertEquals(50, c);
-}
+    it('marshals multiple out parameters', function () {
+        expect(tester.vfunc_multiple_out_parameters()).toEqual([44, 45]);
+    });
 
-function testInterfaces() {
-    let ifaceImpl = new GIMarshallingTests.InterfaceImpl();
-    let itself = ifaceImpl.get_as_interface();
+    it('marshals a return value and one out parameter', function () {
+        expect(tester.vfunc_return_value_and_one_out_parameter())
+            .toEqual([46, 47]);
+    });
 
-    assertEquals(ifaceImpl, itself);
-}
+    it('marshals a return value and multiple out parameters', function () {
+        expect(tester.vfunc_return_value_and_multiple_out_parameters())
+            .toEqual([48, 49, 50]);
+    });
+});
 
-gjstestRun();
+describe('Interface', function () {
+    it('can be returned', function () {
+        let ifaceImpl = new GIMarshallingTests.InterfaceImpl();
+        let itself = ifaceImpl.get_as_interface();
+        expect(ifaceImpl).toEqual(itself);
+    });
+});
diff --git a/installed-tests/js/testGLib.js b/installed-tests/js/testGLib.js
index d66c14e..4651671 100644
--- a/installed-tests/js/testGLib.js
+++ b/installed-tests/js/testGLib.js
@@ -1,37 +1,43 @@
 const GLib = imports.gi.GLib;
-const JSUnit = imports.jsUnit;
 
-function testVariantConstructor() {
-    let str_variant = new GLib.Variant('s', 'mystring');
-    JSUnit.assertEquals('mystring', str_variant.get_string()[0]);
-    JSUnit.assertEquals('mystring', str_variant.deep_unpack());
+describe('GVariant constructor', function () {
+    it('constructs a string variant', function () {
+        let str_variant = new GLib.Variant('s', 'mystring');
+        expect(str_variant.get_string()[0]).toEqual('mystring');
+        expect(str_variant.deep_unpack()).toEqual('mystring');
+    });
 
-    let str_variant_old = GLib.Variant.new('s', 'mystring');
-    JSUnit.assertTrue(str_variant.equal(str_variant_old));
+    it('constructs a string variant (backwards compatible API)', function () {
+        let str_variant = new GLib.Variant('s', 'mystring');
+        let str_variant_old = GLib.Variant.new('s', 'mystring');
+        expect(str_variant.equal(str_variant_old)).toBeTruthy();
+    });
 
-    let struct_variant = new GLib.Variant('(sogvau)', [
-        'a string',
-        '/a/object/path',
-        'asig', //nature
-        new GLib.Variant('s', 'variant'),
-        [ 7, 3 ]
-    ]);
-    JSUnit.assertEquals(5, struct_variant.n_children());
+    it('constructs a struct variant', function () {
+        let struct_variant = new GLib.Variant('(sogvau)', [
+            'a string',
+            '/a/object/path',
+            'asig', //nature
+            new GLib.Variant('s', 'variant'),
+            [ 7, 3 ]
+        ]);
+        expect(struct_variant.n_children()).toEqual(5);
 
-    let unpacked = struct_variant.deep_unpack();
-    JSUnit.assertEquals('a string', unpacked[0]);
-    JSUnit.assertEquals('/a/object/path', unpacked[1]);
-    JSUnit.assertEquals('asig', unpacked[2]);
-    JSUnit.assertTrue(unpacked[3] instanceof GLib.Variant);
-    JSUnit.assertEquals('variant', unpacked[3].deep_unpack());
-    JSUnit.assertTrue(unpacked[4] instanceof Array);
-    JSUnit.assertEquals(2, unpacked[4].length);
+        let unpacked = struct_variant.deep_unpack();
+        expect(unpacked[0]).toEqual('a string');
+        expect(unpacked[1]).toEqual('/a/object/path');
+        expect(unpacked[2]).toEqual('asig');
+        expect(unpacked[3] instanceof GLib.Variant).toBeTruthy();
+        expect(unpacked[3].deep_unpack()).toEqual('variant');
+        expect(unpacked[4] instanceof Array).toBeTruthy();
+        expect(unpacked[4].length).toEqual(2);
+    });
 
-    let maybe_variant = new GLib.Variant('ms', null);
-    JSUnit.assertEquals(null, maybe_variant.deep_unpack());
+    it('constructs a maybe variant', function () {
+        let maybe_variant = new GLib.Variant('ms', null);
+        expect(maybe_variant.deep_unpack()).toBeNull();
 
-    maybe_variant = new GLib.Variant('ms', 'string');
-    JSUnit.assertEquals('string', maybe_variant.deep_unpack());
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+        maybe_variant = new GLib.Variant('ms', 'string');
+        expect(maybe_variant.deep_unpack()).toEqual('string');
+    });
+});
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index 77d1f0c..c967550 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -1,6 +1,5 @@
 // -*- mode: js; indent-tabs-mode: nil -*-
 
-const JSUnit = imports.jsUnit;
 const Lang = imports.lang;
 const GObject = imports.gi.GObject;
 const Gio = imports.gi.Gio;
@@ -144,7 +143,8 @@ const MyInitable = new Lang.Class({
     },
 
     vfunc_init: function(cancellable) { // error?
-        JSUnit.assertTrue(cancellable instanceof Gio.Cancellable);
+        if (!(cancellable instanceof Gio.Cancellable))
+            throw 'Bad argument';
 
         this.inited = true;
     }
@@ -167,8 +167,6 @@ const MyCustomInit = new Lang.Class({
         this.foo = false;
 
         this.parent();
-
-        JSUnit.assert(this.foo);
     },
 
     _instance_init: function() {
@@ -176,192 +174,184 @@ const MyCustomInit = new Lang.Class({
     }
 });
 
-function testGObjectClass() {
-    let myInstance = new MyObject();
-
-    JSUnit.assertEquals('foo', myInstance.readwrite);
-    JSUnit.assertEquals('bar', myInstance.readonly);
-    JSUnit.assertEquals('default', myInstance.construct);
+describe('GObject class', function () {
+    let myInstance;
+    beforeEach(function () {
+        myInstance = new MyObject();
+    });
 
-    let myInstance2 = new MyObject({ readwrite: 'baz', construct: 'asdf' });
+    it('constructs with default values for properties', function () {
+        expect(myInstance.readwrite).toEqual('foo');
+        expect(myInstance.readonly).toEqual('bar');
+        expect(myInstance.construct).toEqual('default');
+    });
 
-    JSUnit.assertEquals('baz', myInstance2.readwrite);
-    JSUnit.assertEquals('bar', myInstance2.readonly);
-    JSUnit.assertEquals('asdf', myInstance2.construct);
+    it('constructs with a hash of property values', function () {
+        let myInstance2 = new MyObject({ readwrite: 'baz', construct: 'asdf' });
+        expect(myInstance2.readwrite).toEqual('baz');
+        expect(myInstance2.readonly).toEqual('bar');
+        expect(myInstance2.construct).toEqual('asdf');
+    });
 
-    let ui = '<interface> \
+    const ui = '<interface> \
                 <object class="Gjs_MyObject" id="MyObject"> \
                   <property name="readwrite">baz</property> \
                   <property name="construct">quz</property> \
                 </object> \
               </interface>';
-    let builder = Gtk.Builder.new_from_string(ui, -1);
-    let myInstance3 = builder.get_object('MyObject');
-    JSUnit.assertEquals('baz', myInstance3.readwrite);
-    JSUnit.assertEquals('bar', myInstance3.readonly);
-    JSUnit.assertEquals('quz', myInstance3.construct);
+
+    it('constructs with property values from Gtk.Builder', function () {
+        let builder = Gtk.Builder.new_from_string(ui, -1);
+        let myInstance3 = builder.get_object('MyObject');
+        expect(myInstance3.readwrite).toEqual('baz');
+        expect(myInstance3.readonly).toEqual('bar');
+        expect(myInstance3.construct).toEqual('quz');
+    });
 
     // the following would (should) cause a CRITICAL:
     // myInstance.readonly = 'val';
     // myInstance.construct = 'val';
-}
-
-function testNotify() {
-    let myInstance = new MyObject();
-    let counter = 0;
 
-    myInstance.connect('notify::readonly', function(obj) {
-        if (obj.readonly == 'changed')
-            counter++;
-    });
+    it('has a notify signal', function () {
+        let notifySpy = jasmine.createSpy('notifySpy');
+        myInstance.connect('notify::readonly', notifySpy);
 
-    myInstance.notify_prop();
-    myInstance.notify_prop();
+        myInstance.notify_prop();
+        myInstance.notify_prop();
 
-    JSUnit.assertEquals(2, counter);
-}
+        expect(notifySpy).toHaveBeenCalledTimes(2);
+    });
 
-function testSignals() {
-    let myInstance = new MyObject();
-    let ok = false;
+    it('can define its own signals', function () {
+        let emptySpy = jasmine.createSpy('emptySpy');
+        myInstance.connect('empty', emptySpy);
+        myInstance.emit_empty();
 
-    myInstance.connect('empty', function() {
-        ok = true;
+        expect(emptySpy).toHaveBeenCalled();
+        expect(myInstance.empty_called).toBeTruthy();
     });
-    myInstance.emit_empty();
-
-    JSUnit.assertEquals(true, ok);
-    JSUnit.assertEquals(true, myInstance.empty_called);
 
-    let args = [ ];
-    myInstance.connect('minimal', function(emitter, one, two) {
-        args.push(one);
-        args.push(two);
+    it('passes emitted arguments to signal handlers', function () {
+        let minimalSpy = jasmine.createSpy('minimalSpy');
+        myInstance.connect('minimal', minimalSpy);
+        myInstance.emit_minimal(7, 5);
 
-        return true;
+        expect(minimalSpy).toHaveBeenCalledWith(myInstance, 7, 5);
     });
-    myInstance.emit_minimal(7, 5);
-
-    JSUnit.assertEquals(7, args[0]);
-    JSUnit.assertEquals(5, args[1]);
 
-    ok = true;
-    myInstance.connect('full', function() {
-        ok = true;
+    it('can return values from signals', function () {
+        let fullSpy = jasmine.createSpy('fullSpy').and.returnValue(42);
+        myInstance.connect('full', fullSpy);
+        let result = myInstance.emit_full();
 
-        return 42;
+        expect(fullSpy).toHaveBeenCalled();
+        expect(result).toEqual(42);
     });
-    myInstance.connect('full', function() {
-        // this should never be called
-        ok = false;
 
-        return -1;
+    it('does not call first-wins signal handlers after one returns a value', function () {
+        let neverCalledSpy = jasmine.createSpy('neverCalledSpy');
+        myInstance.connect('full', () => 42);
+        myInstance.connect('full', neverCalledSpy);
+        myInstance.emit_full();
+
+        expect(neverCalledSpy).not.toHaveBeenCalled();
+        expect(myInstance.full_default_handler_called).toBeFalsy();
     });
-    let result = myInstance.emit_full();
 
-    JSUnit.assertEquals(true, ok);
-    JSUnit.assertUndefined(myInstance.full_default_handler_called);
-    JSUnit.assertEquals(42, result);
+    it('gets the return value of the default handler', function () {
+        let result = myInstance.emit_full();
 
-    let stack = [ ];
-    myInstance.connect('run-last', function() {
-        stack.push(1);
-    });
-    myInstance.emit_run_last(function() {
-        stack.push(2);
+        expect(myInstance.full_default_handler_called).toBeTruthy();
+        expect(result).toEqual(79);
     });
 
-    JSUnit.assertEquals(1, stack[0]);
-    JSUnit.assertEquals(2, stack[1]);
-}
+    it('calls run-last default handler last', function () {
+        let stack = [ ];
+        let runLastSpy = jasmine.createSpy('runLastSpy')
+            .and.callFake(() => { stack.push(1); });
+        myInstance.connect('run-last', runLastSpy);
+        myInstance.emit_run_last(() => { stack.push(2); });
 
-function testSubclass() {
-    // test that we can inherit from something that's not
-    // GObject.Object and still get all the goodies of
-    // GObject.Class
+        expect(stack).toEqual([1, 2]);
+    });
 
-    let instance = new MyApplication({ application_id: 'org.gjs.Application' });
-    let v;
+    it("can inherit from something that's not GObject.Object", function () {
+        // ...and still get all the goodies of GObject.Class
+        let instance = new MyApplication({ application_id: 'org.gjs.Application' });
+        let customSpy = jasmine.createSpy('customSpy');
+        instance.connect('custom', customSpy);
 
-    instance.connect('custom', function(app, num) {
-        v = num;
+        instance.emit_custom(73);
+        expect(customSpy).toHaveBeenCalledWith(instance, 73);
     });
 
-    instance.emit_custom(73);
-    JSUnit.assertEquals(73, v);
-}
+    it('can implement an interface', function () {
+        let instance = new MyInitable();
+        expect(instance.constructor.implements(Gio.Initable)).toBeTruthy();
+    });
 
-function testInterface() {
-    let instance = new MyInitable();
-    JSUnit.assertEquals(false, instance.inited);
+    it('can implement interface vfuncs', function () {
+        let instance = new MyInitable();
+        expect(instance.inited).toBeFalsy();
 
-    instance.init(new Gio.Cancellable);
-    JSUnit.assertEquals(true, instance.inited);
+        instance.init(new Gio.Cancellable());
+        expect(instance.inited).toBeTruthy();
+    });
 
-    JSUnit.assertTrue(instance.constructor.implements(Gio.Initable));
-}
+    it('can be a subclass', function () {
+        let derived = new Derived();
 
-function testDerived() {
-    let derived = new Derived();
+        expect(derived instanceof Derived).toBeTruthy();
+        expect(derived instanceof MyObject).toBeTruthy();
 
-    JSUnit.assertTrue(derived instanceof Derived);
-    JSUnit.assertTrue(derived instanceof MyObject);
+        expect(derived.readwrite).toEqual('yes');
+    });
 
-    JSUnit.assertEquals('yes', derived.readwrite);
-}
+    it('calls its _instance_init() function while chaining up in constructor', function () {
+        let instance = new MyCustomInit();
+        expect(instance.foo).toBeTruthy();
+    });
 
-function testInstanceInit() {
-    new MyCustomInit();
-}
+    it('can have an interface-valued property', function () {
+        const InterfacePropObject = new Lang.Class({
+            Name: 'InterfacePropObject',
+            Extends: GObject.Object,
+            Properties: {
+                'file': GObject.ParamSpec.object('file', 'File', 'File',
+                    GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
+                    Gio.File.$gtype)
+            },
+        });
+        let file = Gio.File.new_for_path('dummy');
+        expect(() => new InterfacePropObject({ file: file })).not.toThrow();
+    });
 
-function testClassCanHaveInterfaceProperty() {
-    const InterfacePropObject = new Lang.Class({
-        Name: 'InterfacePropObject',
-        Extends: GObject.Object,
-        Properties: {
-            'file': GObject.ParamSpec.object('file', 'File', 'File',
-                GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | 
GObject.ParamFlags.CONSTRUCT_ONLY,
-                Gio.File.$gtype)
-        }
+    it('can override a property from the parent class', function () {
+        const OverrideObject = new Lang.Class({
+            Name: 'OverrideObject',
+            Extends: MyObject,
+            Properties: {
+                'readwrite': GObject.ParamSpec.override('readwrite', MyObject),
+            },
+            get readwrite() {
+                return this._subclass_readwrite;
+            },
+            set readwrite(val) {
+                this._subclass_readwrite = 'subclass' + val;
+            },
+        });
+        let obj = new OverrideObject();
+        obj.readwrite = 'foo';
+        expect(obj.readwrite).toEqual('subclassfoo');
     });
-    let obj = new InterfacePropObject({ file: Gio.File.new_for_path('dummy') });
-}
-
-function testClassCanOverrideParentClassProperty() {
-    const OverrideObject = new Lang.Class({
-        Name: 'OverrideObject',
-        Extends: MyObject,
-        Properties: {
-            'readwrite': GObject.ParamSpec.override('readwrite', MyObject)
-        },
-        get readwrite() {
-            return this._subclass_readwrite;
-        },
-        set readwrite(val) {
-            this._subclass_readwrite = 'subclass' + val;
-        }
+
+    it('cannot override a non-existent property', function () {
+        expect(() => new Lang.Class({
+            Name: 'BadOverride',
+            Extends: GObject.Object,
+            Properties: {
+                'nonexistent': GObject.ParamSpec.override('nonexistent', GObject.Object),
+            },
+        })).toThrow();
     });
-    let obj = new OverrideObject();
-    obj.readwrite = 'foo';
-    JSUnit.assertEquals(obj.readwrite, 'subclassfoo');
-}
-
-function testClassCannotOverrideNonexistentProperty() {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'BadOverride',
-        Extends: GObject.Object,
-        Properties: {
-            'nonexistent': GObject.ParamSpec.override('nonexistent', GObject.Object)
-        }
-    }));
-}
-
-function testDefaultHandler() {
-    let myInstance = new MyObject();
-    let result = myInstance.emit_full();
-
-    JSUnit.assertEquals(true, myInstance.full_default_handler_called);
-    JSUnit.assertEquals(79, result);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+});
diff --git a/installed-tests/js/testGObjectInterface.js b/installed-tests/js/testGObjectInterface.js
index 1546198..792b611 100644
--- a/installed-tests/js/testGObjectInterface.js
+++ b/installed-tests/js/testGObjectInterface.js
@@ -3,7 +3,6 @@
 const Gio = imports.gi.Gio;
 const GLib = imports.gi.GLib;
 const GObject = imports.gi.GObject;
-const JSUnit = imports.jsUnit;
 const Lang = imports.lang;
 const Mainloop = imports.mainloop;
 
@@ -116,261 +115,275 @@ const ImplementationOfTwoInterfaces = new Lang.Class({
     }
 });
 
-function testGObjectClassCanImplementInterface() {
-    // Test will fail if the constructor throws an exception
-    let obj = new GObjectImplementingLangInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-}
-
-function testRaisesWhenInterfaceRequiresGObjectInterfaceButNotGObject() {
-    JSUnit.assertRaises(() => new Lang.Interface({
-        Name: 'GObjectInterfaceNotRequiringGObject',
-        GTypeName: 'GTypeNameNotRequiringGObject',
-        Requires: [ Gio.Initable ]
-    }));
-}
-
-function testGObjectCanImplementInterfacesFromJSAndC() {
-    // Test will fail if the constructor throws an exception
-    const ObjectImplementingLangInterfaceAndCInterface = new Lang.Class({
-        Name: 'ObjectImplementingLangInterfaceAndCInterface',
-        Extends: GObject.Object,
-        Implements: [ AnInterface, Gio.Initable ],
-
-        _init: function (props={}) {
-            this.parent(props);
-        }
+describe('GObject interface', function () {
+    it('class can implement a Lang.Interface', function () {
+        let obj;
+        expect(() => { obj = new GObjectImplementingLangInterface(); })
+            .not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
     });
-    let obj = new ObjectImplementingLangInterfaceAndCInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-    JSUnit.assertTrue(obj.constructor.implements(Gio.Initable));
-}
-
-function testGObjectInterfaceIsInstanceOfInterfaces() {
-    JSUnit.assertTrue(AGObjectInterface instanceof Lang.Interface);
-    JSUnit.assertTrue(AGObjectInterface instanceof GObject.Interface);
-}
-
-function testGObjectInterfaceCannotBeInstantiated() {
-    JSUnit.assertRaises(() => new AGObjectInterface());
-}
-
-function testGObjectInterfaceTypeName() {
-    JSUnit.assertEquals('ArbitraryGTypeName', AGObjectInterface.$gtype.name);
-}
-
-function testGObjectCanImplementInterface() {
-    // Test will fail if the constructor throws an exception
-    let obj = new GObjectImplementingGObjectInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
-}
-
-function testGObjectImplementingInterfaceHasCorrectClassObject() {
-    JSUnit.assertEquals('[object GObjectClass for GObjectImplementingGObjectInterface]', 
GObjectImplementingGObjectInterface.toString());
-    let obj = new GObjectImplementingGObjectInterface();
-    JSUnit.assertEquals(GObjectImplementingGObjectInterface, obj.constructor);
-    JSUnit.assertEquals('[object GObjectClass for GObjectImplementingGObjectInterface]',
-        obj.constructor.toString());
-}
-
-function testGObjectCanImplementBothGObjectAndNonGObjectInterfaces() {
-    // Test will fail if the constructor throws an exception
-    const GObjectImplementingBothKindsOfInterface = new Lang.Class({
-        Name: 'GObjectImplementingBothKindsOfInterface',
-        Extends: GObject.Object,
-        Implements: [ AnInterface, AGObjectInterface ],
-        Properties: {
-            'interface-prop': GObject.ParamSpec.override('interface-prop',
-                AGObjectInterface)
-        },
-
-        _init: function (props={}) {
-            this.parent(props);
-        },
-        required: function () {},
-        requiredG: function () {}
+
+    it('throws when an interface requires a GObject interface but not GObject.Object', function () {
+        expect(() => new Lang.Interface({
+            Name: 'GObjectInterfaceNotRequiringGObject',
+            GTypeName: 'GTypeNameNotRequiringGObject',
+            Requires: [ Gio.Initable ]
+        })).toThrow();
     });
-    let obj = new GObjectImplementingBothKindsOfInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
-}
-
-function testGObjectCanImplementRequiredFunction() {
-    // Test considered passing if no exception thrown
-    let obj = new GObjectImplementingGObjectInterface();
-    obj.requiredG();
-}
-
-function testGObjectMustImplementRequiredFunction () {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'BadObject',
-        Extends: GObject.Object,
-        Implements: [ AGObjectInterface ],
-        Properties: {
-            'interface-prop': GObject.ParamSpec.override('interface-prop',
-                AGObjectInterface)
-        }
-    }));
-}
-
-function testGObjectDoesntHaveToImplementOptionalFunction() {
-    // Test will fail if the constructor throws an exception
-    let obj = new MinimalImplementationOfAGObjectInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
-}
-
-function testGObjectCanDeferToInterfaceOptionalFunction() {
-    let obj = new MinimalImplementationOfAGObjectInterface();
-    JSUnit.assertEquals('AGObjectInterface.optionalG()', obj.optionalG());
-}
-
-function testGObjectCanChainUpToInterface() {
-    let obj = new GObjectImplementingGObjectInterface();
-    JSUnit.assertEquals('AGObjectInterface.optionalG()', obj.optionalG());
-}
-
-function testGObjectInterfaceCanRequireOtherInterface() {
-    // Test will fail if the constructor throws an exception
-    let obj = new ImplementationOfTwoInterfaces();
-    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
-    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringGObjectInterface));
-}
-
-function testGObjectInterfaceCanChainUpToOtherInterface() {
-    let obj = new ImplementationOfTwoInterfaces();
-    JSUnit.assertEquals('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()',
-        obj.optionalG());
-}
-
-function testGObjectDefersToLastInterfaceOptionalFunction() {
-    const MinimalImplementationOfTwoInterfaces = new Lang.Class({
-        Name: 'MinimalImplementationOfTwoInterfaces',
-        Extends: GObject.Object,
-        Implements: [ AGObjectInterface, InterfaceRequiringGObjectInterface ],
-        Properties: {
-            'interface-prop': GObject.ParamSpec.override('interface-prop',
-                AGObjectInterface)
-        },
-
-        _init: function (props={}) {
-            this.parent(props);
-        },
-        requiredG: function () {}
+
+    it('can be implemented by a GObject class along with a JS interface', function () {
+        const ObjectImplementingLangInterfaceAndCInterface = new Lang.Class({
+            Name: 'ObjectImplementingLangInterfaceAndCInterface',
+            Extends: GObject.Object,
+            Implements: [ AnInterface, Gio.Initable ],
+
+            _init: function (props={}) {
+                this.parent(props);
+            }
+        });
+        let obj;
+        expect(() => { obj = new ObjectImplementingLangInterfaceAndCInterface(); })
+            .not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+        expect(obj.constructor.implements(Gio.Initable)).toBeTruthy();
     });
-    let obj = new MinimalImplementationOfTwoInterfaces();
-    JSUnit.assertEquals('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()',
-        obj.optionalG());
-}
-
-function testGObjectClassMustImplementAllRequiredInterfaces() {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'BadObject',
-        Implements: [ InterfaceRequiringGObjectInterface ],
-        required: function () {}
-    }));
-}
-
-function testGObjectClassMustImplementRequiredInterfacesInCorrectOrder() {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'BadObject',
-        Implements: [ InterfaceRequiringGObjectInterface, AGObjectInterface ],
-        required: function () {}
-    }));
-}
-
-function testGObjectInterfaceCanRequireInterfaceFromC() {
-    const InitableInterface = new Lang.Interface({
-        Name: 'InitableInterface',
-        Requires: [ GObject.Object, Gio.Initable ]
+
+    it('is an instance of the interface classes', function () {
+        expect(AGObjectInterface instanceof Lang.Interface).toBeTruthy();
+        expect(AGObjectInterface instanceof GObject.Interface).toBeTruthy();
     });
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'BadObject',
-        Implements: [ InitableInterface ]
-    }));
-}
-
-function testGObjectHasInterfaceSignalsAndClassSignals() {
-    let obj = new GObjectImplementingGObjectInterface();
-    let interface_signal_emitted = false, class_signal_emitted = false;
-    obj.connect('interface-signal', () => {
-        interface_signal_emitted = true;
-        Mainloop.quit('signal');
+
+    it('cannot be instantiated', function () {
+        expect(() => new AGObjectInterface()).toThrow();
     });
-    obj.connect('class-signal', () => {
-        class_signal_emitted = true;
-        Mainloop.quit('signal');
+
+    it('reports its type name', function () {
+        expect(AGObjectInterface.$gtype.name).toEqual('ArbitraryGTypeName');
     });
-    GLib.idle_add(GLib.PRIORITY_DEFAULT, () => obj.emit('interface-signal'));
-    Mainloop.run('signal');
-    GLib.idle_add(GLib.PRIORITY_DEFAULT, () => obj.emit('class-signal'));
-    Mainloop.run('signal');
-    JSUnit.assertTrue(interface_signal_emitted);
-    JSUnit.assertTrue(class_signal_emitted);
-}
-
-function testGObjectHasInterfacePropertiesAndClassProperties() {
-    let obj = new GObjectImplementingGObjectInterface();
-    JSUnit.assertEquals('foobar', obj.interface_prop);
-    JSUnit.assertEquals('meh', obj.class_prop);
-}
-
-// Failing to override an interface property doesn't raise an error but instead
-// logs a critical warning.
-function testGObjectMustOverrideInterfaceProperties() {
-    GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL,
-        "Object class * doesn't implement property 'interface-prop' from " +
-        "interface 'ArbitraryGTypeName'");
-    new Lang.Class({
-        Name: 'MyNaughtyObject',
-        Extends: GObject.Object,
-        Implements: [ AGObjectInterface ],
-        _init: function (props={}) {
-            this.parent(props);
-        },
-        requiredG: function () {}
+
+    it('can be implemented by a GObject class', function () {
+        let obj;
+        expect(() => { obj = new GObjectImplementingGObjectInterface(); })
+            .not.toThrow();
+        expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy();
     });
-    // g_test_assert_expected_messages() is a macro, not introspectable
-    GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectInterface.js',
-        416, 'testGObjectMustOverrideInterfaceProperties');
-}
-
-// This makes sure that we catch the case where the metaclass (e.g.
-// GtkWidgetClass) doesn't specify a meta-interface. In that case we get the
-// meta-interface from the metaclass's parent.
-function testInterfaceIsOfCorrectTypeForMetaclass() {
-    const MyMeta = new Lang.Class({
-        Name: 'MyMeta',
-        Extends: GObject.Class
+
+    it('is implemented by a GObject class with the correct class object', function () {
+        expect(GObjectImplementingGObjectInterface.toString())
+            .toEqual('[object GObjectClass for GObjectImplementingGObjectInterface]');
+        let obj = new GObjectImplementingGObjectInterface();
+        expect(obj.constructor).toEqual(GObjectImplementingGObjectInterface);
+        expect(obj.constructor.toString())
+            .toEqual('[object GObjectClass for GObjectImplementingGObjectInterface]');
     });
-    const MyMetaObject = new MyMeta({
-        Name: 'MyMetaObject'
+
+    it('can be implemented by a class also implementing a Lang.Interface', function () {
+        const GObjectImplementingBothKindsOfInterface = new Lang.Class({
+            Name: 'GObjectImplementingBothKindsOfInterface',
+            Extends: GObject.Object,
+            Implements: [ AnInterface, AGObjectInterface ],
+            Properties: {
+                'interface-prop': GObject.ParamSpec.override('interface-prop',
+                    AGObjectInterface)
+            },
+
+            _init: function (props={}) {
+                this.parent(props);
+            },
+            required: function () {},
+            requiredG: function () {}
+        });
+        let obj;
+        expect(() => { obj = new GObjectImplementingBothKindsOfInterface(); })
+            .not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+        expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy();
     });
-    const MyMetaInterface = new Lang.Interface({
-        Name: 'MyMetaInterface',
-        Requires: [ MyMetaObject ]
+
+    it('can have its required function implemented', function () {
+        expect(() => {
+            let obj = new GObjectImplementingGObjectInterface();
+            obj.requiredG();
+        }).not.toThrow();
     });
-    JSUnit.assertTrue(MyMetaInterface instanceof GObject.Interface);
-}
 
-function testSubclassImplementsTheSameInterfaceAsItsParent() {
-    const SubObject = new Lang.Class({
-        Name: 'SubObject',
-        Extends: GObjectImplementingGObjectInterface
+    it('must have its required function implemented', function () {
+        expect(() => new Lang.Class({
+            Name: 'BadObject',
+            Extends: GObject.Object,
+            Implements: [ AGObjectInterface ],
+            Properties: {
+                'interface-prop': GObject.ParamSpec.override('interface-prop',
+                    AGObjectInterface)
+            }
+        })).toThrow();
     });
-    let obj = new SubObject();
-    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
-    JSUnit.assertEquals('foobar', obj.interface_prop);  // override not needed
-}
-
-function testSubclassCanReimplementTheSameInterfaceAsItsParent() {
-    const SubImplementer = new Lang.Class({
-        Name: 'SubImplementer',
-        Extends: GObjectImplementingGObjectInterface,
-        Implements: [ AGObjectInterface ]
+
+    it("doesn't have to have its optional function implemented", function () {
+        let obj;
+        expect(() => { obj = new MinimalImplementationOfAGObjectInterface(); })
+            .not.toThrow();
+        expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy();
+    });
+
+    it('can have its optional function deferred to by the implementation', function () {
+        let obj = new MinimalImplementationOfAGObjectInterface();
+        expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()');
+    });
+
+    it('can have its function chained up to', function () {
+        let obj = new GObjectImplementingGObjectInterface();
+        expect(obj.optionalG()).toEqual('AGObjectInterface.optionalG()');
     });
-    let obj = new SubImplementer();
-    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
-    JSUnit.assertEquals('foobar', obj.interface_prop);  // override not needed
-}
 
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('can require another interface', function () {
+        let obj;
+        expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow();
+        expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy();
+        expect(obj.constructor.implements(InterfaceRequiringGObjectInterface))
+            .toBeTruthy();
+    });
+
+    it('can chain up to another interface', function () {
+        let obj = new ImplementationOfTwoInterfaces();
+        expect(obj.optionalG())
+            .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()');
+    });
+
+    it("defers to the last interface's optional function", function () {
+        const MinimalImplementationOfTwoInterfaces = new Lang.Class({
+            Name: 'MinimalImplementationOfTwoInterfaces',
+            Extends: GObject.Object,
+            Implements: [ AGObjectInterface, InterfaceRequiringGObjectInterface ],
+            Properties: {
+                'interface-prop': GObject.ParamSpec.override('interface-prop',
+                    AGObjectInterface)
+            },
+
+            _init: function (props={}) {
+                this.parent(props);
+            },
+            requiredG: function () {}
+        });
+        let obj = new MinimalImplementationOfTwoInterfaces();
+        expect(obj.optionalG())
+            .toEqual('InterfaceRequiringGObjectInterface.optionalG()\nAGObjectInterface.optionalG()');
+    });
+
+    it('must be implemented by a class that implements all required interfaces', function () {
+        expect(() => new Lang.Class({
+            Name: 'BadObject',
+            Implements: [ InterfaceRequiringGObjectInterface ],
+            required: function () {}
+        })).toThrow();
+    });
+
+    it('must be implemented by a class that implements required interfaces in correct order', function () {
+        expect(() => new Lang.Class({
+            Name: 'BadObject',
+            Implements: [ InterfaceRequiringGObjectInterface, AGObjectInterface ],
+            required: function () {}
+        })).toThrow();
+    });
+
+    it('can require an interface from C', function () {
+        const InitableInterface = new Lang.Interface({
+            Name: 'InitableInterface',
+            Requires: [ GObject.Object, Gio.Initable ]
+        });
+        expect(() => new Lang.Class({
+            Name: 'BadObject',
+            Implements: [ InitableInterface ]
+        })).toThrow();
+    });
+
+    it('can define signals on the implementing class', function () {
+        function quitLoop() {
+            Mainloop.quit('signal');
+        }
+        let obj = new GObjectImplementingGObjectInterface();
+        let interfaceSignalSpy = jasmine.createSpy('interfaceSignalSpy')
+            .and.callFake(quitLoop);
+        let classSignalSpy = jasmine.createSpy('classSignalSpy')
+            .and.callFake(quitLoop);
+        obj.connect('interface-signal', interfaceSignalSpy);
+        obj.connect('class-signal', classSignalSpy);
+        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+            obj.emit('interface-signal');
+            return GLib.SOURCE_REMOVE;
+        });
+        Mainloop.run('signal');
+        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+            obj.emit('class-signal');
+            return GLib.SOURCE_REMOVE;
+        });
+        Mainloop.run('signal');
+        expect(interfaceSignalSpy).toHaveBeenCalled();
+        expect(classSignalSpy).toHaveBeenCalled();
+    });
+
+    it('can define properties on the implementing class', function () {
+        let obj = new GObjectImplementingGObjectInterface();
+        expect(obj.interface_prop).toEqual('foobar');
+        expect(obj.class_prop).toEqual('meh');
+    });
+
+    it('must have its properties overridden', function () {
+        // Failing to override an interface property doesn't raise an error but
+        // instead logs a critical warning.
+        GLib.test_expect_message('GLib-GObject', GLib.LogLevelFlags.LEVEL_CRITICAL,
+            "Object class * doesn't implement property 'interface-prop' from " +
+            "interface 'ArbitraryGTypeName'");
+        new Lang.Class({
+            Name: 'MyNaughtyObject',
+            Extends: GObject.Object,
+            Implements: [ AGObjectInterface ],
+            _init: function (props={}) {
+                this.parent(props);
+            },
+            requiredG: function () {}
+        });
+        // g_test_assert_expected_messages() is a macro, not introspectable
+        GLib.test_assert_expected_messages_internal('Gjs', 'testGObjectInterface.js',
+            416, 'testGObjectMustOverrideInterfaceProperties');
+    });
+
+    // This makes sure that we catch the case where the metaclass (e.g.
+    // GtkWidgetClass) doesn't specify a meta-interface. In that case we get the
+    // meta-interface from the metaclass's parent.
+    it('gets the correct type for its metaclass', function () {
+        const MyMeta = new Lang.Class({
+            Name: 'MyMeta',
+            Extends: GObject.Class
+        });
+        const MyMetaObject = new MyMeta({
+            Name: 'MyMetaObject'
+        });
+        const MyMetaInterface = new Lang.Interface({
+            Name: 'MyMetaInterface',
+            Requires: [ MyMetaObject ]
+        });
+        expect(MyMetaInterface instanceof GObject.Interface).toBeTruthy();
+    });
+
+    it('can be implemented by a class as well as its parent class', function () {
+        const SubObject = new Lang.Class({
+            Name: 'SubObject',
+            Extends: GObjectImplementingGObjectInterface
+        });
+        let obj = new SubObject();
+        expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy();
+        expect(obj.interface_prop).toEqual('foobar');  // override not needed
+    });
+
+    it('can be reimplemented by a subclass of a class that already implements it', function () {
+        const SubImplementer = new Lang.Class({
+            Name: 'SubImplementer',
+            Extends: GObjectImplementingGObjectInterface,
+            Implements: [ AGObjectInterface ]
+        });
+        let obj = new SubImplementer();
+        expect(obj.constructor.implements(AGObjectInterface)).toBeTruthy();
+        expect(obj.interface_prop).toEqual('foobar');  // override not needed
+    });
+});
diff --git a/installed-tests/js/testGTypeClass.js b/installed-tests/js/testGTypeClass.js
index 4375966..5bcf748 100644
--- a/installed-tests/js/testGTypeClass.js
+++ b/installed-tests/js/testGTypeClass.js
@@ -1,55 +1,64 @@
-// application/javascript;version=1.8
-
-const JSUnit = imports.jsUnit;
-const Everything = imports.gi.Regress;
-const WarnLib = imports.gi.WarnLib;
-
 // We use Gio to have some objects that we know exist
-const GLib = imports.gi.GLib;
 const Gio = imports.gi.Gio;
 const GObject = imports.gi.GObject;
 
-function testGObjectClass() {
-    let find_property = GObject.Object.find_property;
-
-    let p1 = find_property.call(Gio.ThemedIcon, 'name');
+describe('Looking up param specs', function () {
+    let p1, p2;
+    beforeEach(function () {
+        let find_property = GObject.Object.find_property;
+        p1 = find_property.call(Gio.ThemedIcon, 'name');
+        p2 = find_property.call(Gio.SimpleAction, 'enabled');
+    });
 
-    JSUnit.assert(p1 instanceof GObject.ParamSpec);
-    JSUnit.assertEquals('name', p1.name);
+    it('works', function () {
+        expect(p1 instanceof GObject.ParamSpec).toBeTruthy();
+        expect(p2 instanceof GObject.ParamSpec).toBeTruthy();
+    });
 
-    let p2 = find_property.call(Gio.SimpleAction, 'enabled');
+    it('gives the correct name', function () {
+        expect(p1.name).toEqual('name');
+        expect(p2.name).toEqual('enabled');
+    });
 
-    JSUnit.assert(p2 instanceof GObject.ParamSpec);
-    JSUnit.assertEquals('enabled', p2.name);
-    JSUnit.assertEquals(true, p2.default_value);
-}
+    it('gives the default value if present', function () {
+        expect(p2.default_value).toBeTruthy();
+    });
+});
 
-function testGType() {
-    JSUnit.assertEquals("void", GObject.TYPE_NONE.name);
-    JSUnit.assertEquals("gchararray", GObject.TYPE_STRING.name);
+describe('GType object', function () {
+    it('has a name', function () {
+        expect(GObject.TYPE_NONE.name).toEqual('void');
+        expect(GObject.TYPE_STRING.name).toEqual('gchararray');
+    });
 
-    // Make sure "name" is readonly
-    try {
-        GObject.TYPE_STRING.name = "foo";
-    } catch(e) {
-    }
-    JSUnit.assertEquals("gchararray", GObject.TYPE_STRING.name);
+    it('has a read-only name', function () {
+        try {
+            GObject.TYPE_STRING.name = 'foo';
+        } catch (e) {
+        }
+        expect(GObject.TYPE_STRING.name).toEqual('gchararray');
+    });
 
-    // Make sure "name" is permanent
-    try {
-        delete GObject.TYPE_STRING.name;
-    } catch(e) {
-    }
-    JSUnit.assertEquals("gchararray", GObject.TYPE_STRING.name);
+    it('has an undeletable name', function () {
+        try {
+            delete GObject.TYPE_STRING.name;
+        } catch (e) {
+        }
+        expect(GObject.TYPE_STRING.name).toEqual('gchararray');
+    });
 
-    // Make sure "toString" works
-    JSUnit.assertEquals("[object GType for 'void']", GObject.TYPE_NONE.toString());
-    JSUnit.assertEquals("[object GType for 'gchararray']", GObject.TYPE_STRING.toString());
-}
+    it('has a string representation', function () {
+        expect(GObject.TYPE_NONE.toString()).toEqual("[object GType for 'void']");
+        expect(GObject.TYPE_STRING.toString()).toEqual("[object GType for 'gchararray']");
+    });
+});
 
-function testGTypePrototype() {
-    JSUnit.assertNull(GIRepositoryGType.name);
-    JSUnit.assertEquals("[object GType prototype]", GIRepositoryGType.toString());
-}
+describe('GType prototype object', function () {
+    it('has no name', function () {
+        expect(GIRepositoryGType.name).toBeNull();
+    });
 
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('has a string representation', function () {
+        expect(GIRepositoryGType.toString()).toEqual('[object GType prototype]');
+    });
+});
diff --git a/installed-tests/js/testGettext.js b/installed-tests/js/testGettext.js
index 31f077e..2496292 100644
--- a/installed-tests/js/testGettext.js
+++ b/installed-tests/js/testGettext.js
@@ -1,15 +1,14 @@
 // -*- mode: js; indent-tabs-mode: nil -*-
 
-// Tests for the Gettext module.
 const Gettext = imports.gettext;
-const JSUnit = imports.jsUnit;
 
-function testSetlocale() {
+describe('Gettext module', function () {
     // We don't actually want to mess with the locale, so just use setlocale's
     // query mode. We also don't want to make this test locale-dependent, so
     // just assert that it returns a string with at least length 1 (the shortest
     // locale is "C".)
-    JSUnit.assert(Gettext.setlocale(Gettext.LocaleCategory.ALL, null).length >= 1);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('setlocale returns a locale', function () {
+        let locale = Gettext.setlocale(Gettext.LocaleCategory.ALL, null);
+        expect(locale.length).not.toBeLessThan(1);
+    });
+});
diff --git a/installed-tests/js/testImporter.js b/installed-tests/js/testImporter.js
index ee91160..3e49711 100644
--- a/installed-tests/js/testImporter.js
+++ b/installed-tests/js/testImporter.js
@@ -1,115 +1,110 @@
-const JSUnit = imports.jsUnit;
-
-function testImporter() {
-    JSUnit.assertNotUndefined(imports);
-
-    JSUnit.assertRaises(() => imports.nonexistentModuleName);
-    JSUnit.assertRaises(() => imports.alwaysThrows);
-
-    // Try again to make sure that we properly discarded the module object
-    JSUnit.assertRaises(() => imports.alwaysThrows);
-
-    // Import a non-broken module
-    const foobar = imports.foobar;
-    JSUnit.assertNotUndefined(foobar);
-    JSUnit.assertNotUndefined(foobar.foo);
-    JSUnit.assertNotUndefined(foobar.bar);
-    JSUnit.assertEquals(foobar.foo, "This is foo");
-    JSUnit.assertEquals(foobar.bar, "This is bar");
-
-    // Check that deleting the import is a no-op (imported properties are
-    // permanent)
-    JSUnit.assertFalse(delete imports.foobar);
-    JSUnit.assert(imports.foobar == foobar);
-
-    // check that importing a second time gets the same object
-    foobar.somethingElse = "Should remain";
-    const foobar2 = imports.foobar;
-    JSUnit.assertNotUndefined(foobar2.somethingElse);
-    JSUnit.assertEquals(foobar2.somethingElse, "Should remain");
-
-    // Try a sub-module
-    const subB = imports.subA.subB;
-    JSUnit.assertNotUndefined(subB);
-    const subFoobar = subB.foobar;
-    JSUnit.assertNotUndefined(subFoobar);
-    JSUnit.assertNotUndefined(subFoobar.foo);
-    JSUnit.assertNotUndefined(subFoobar.bar);
-    JSUnit.assertEquals(subFoobar.foo, "This is foo");
-    JSUnit.assertEquals(subFoobar.bar, "This is bar");
-    // subFoobar should not be the same as foobar, even though
-    // they have the same basename.
-    JSUnit.assertUndefined(subFoobar.somethingElse);
-    // importing subFoobar a second time should get the same module
-    subFoobar.someProp = "Should be here";
-    const subFoobar2 = imports.subA.subB.foobar;
-    JSUnit.assertNotUndefined(subFoobar2.someProp);
-    JSUnit.assertEquals(subFoobar2.someProp, "Should be here");
-}
-
-function testImporterMetaProps() {
-    const subA = imports.subA;
-    const subB = imports.subA.subB;
-
-    JSUnit.assertNull('imports module name', imports.__moduleName__);
-    JSUnit.assertNull('imports has no parent', imports.__parentModule__);
-    JSUnit.assertEquals('imports.subA module name', 'subA', subA.__moduleName__);
-    JSUnit.assertEquals('imports.subA parent module', imports, subA.__parentModule__);
-    JSUnit.assertEquals('imports.subA.subB module name', 'subB', subB.__moduleName__);
-    JSUnit.assertEquals('imports.subA.subB parent module', subA, subB.__parentModule__);
-}
-
-function testMutualImport() {
+describe('Importer', function () {
+    let oldSearchPath;
+    let foobar, subA, subB, subFoobar;
+
+    beforeAll(function () {
+        oldSearchPath = imports.searchPath.slice();
+        imports.searchPath = ['resource:///org/gjs/jsunit/modules'];
+
+        foobar = imports.foobar;
+        subA = imports.subA;
+        subB = imports.subA.subB;
+        subFoobar = subB.foobar;
+    });
+
+    afterAll(function () {
+        imports.searchPath = oldSearchPath;
+    });
+
+    it('exists', function () {
+        expect(imports).toBeDefined();
+    });
+
+    it('throws an error when trying to import a nonexistent module', function () {
+        expect(() => imports.nonexistentModuleName).toThrow();
+    });
+
+    it('throws an error when evaluating the module file throws an error', function () {
+        expect(() => imports.alwaysThrows).toThrow();
+        // Try again to make sure that we properly discarded the module object
+        expect(() => imports.alwaysThrows).toThrow();
+    });
+
+    it('can import a module', function () {
+        expect(foobar).toBeDefined();
+        expect(foobar.foo).toEqual('This is foo');
+        expect(foobar.bar).toEqual('This is bar');
+    });
+
+    it('makes deleting the import a no-op', function () {
+        expect(delete imports.foobar).toBeFalsy();
+        expect(imports.foobar).toBe(foobar);
+    });
+
+    it('gives the same object when importing a second time', function () {
+        foobar.somethingElse = 'Should remain';
+        const foobar2 = imports.foobar;
+        expect(foobar2.somethingElse).toEqual('Should remain');
+    });
+
+    it('can import a submodule', function () {
+        expect(subB).toBeDefined();
+        expect(subFoobar).toBeDefined();
+        expect(subFoobar.foo).toEqual('This is foo');
+        expect(subFoobar.bar).toEqual('This is bar');
+    });
+
+    it('does not share the same object for a module on a different path', function () {
+        foobar.somethingElse = 'Should remain';
+        expect(subFoobar.somethingElse).not.toBeDefined();
+    });
+
+    it('gives the same object when importing a submodule a second time', function () {
+        subFoobar.someProp = 'Should be here';
+        const subFoobar2 = imports.subA.subB.foobar;
+        expect(subFoobar2.someProp).toEqual('Should be here');
+    });
+
+    it('has no meta properties on the toplevel importer', function () {
+        expect(imports.__moduleName__).toBeNull();
+        expect(imports.__parentModule__).toBeNull();
+    });
+
+    it('sets the names of imported modules', function () {
+        expect(subA.__moduleName__).toEqual('subA');
+        expect(subB.__moduleName__).toEqual('subB');
+    });
+
+    it('gives a module the importer object as parent module', function () {
+        expect(subA.__parentModule__).toBe(imports);
+    });
+
+    it('gives a submodule the module as parent module', function () {
+        expect(subB.__parentModule__).toBe(subA);
+    });
+
     // We want to check that the copy of the 'a' module imported directly
     // is the same as the copy that 'b' imports, and that we don't have two
     // copies because of the A imports B imports A loop.
-
-    let A = imports.mutualImport.a;
-    A.incrementCount();
-    JSUnit.assertEquals(1, A.getCount());
-    JSUnit.assertEquals(1, A.getCountViaB());
-}
-
-function testImporterFunctionFromInitFile() {
-    const subB = imports.subA.subB;
-
-    JSUnit.assertNotUndefined(subB.testImporterFunction);
-
-    let result = subB.testImporterFunction();
-
-    JSUnit.assertEquals(result, "__init__ function tested");
-}
-
-function testImporterClassFromInitFile() {
-    const subB = imports.subA.subB;
-
-    JSUnit.assertNotUndefined(subB.ImporterClass);
-
-    let o = new subB.ImporterClass();
-
-    JSUnit.assertNotNull(o);
-
-    let result = o.testMethod();
-
-    JSUnit.assertEquals(result, "__init__ class tested");
-}
-
-function testImporterUTF8() {
-    const ModUnicode = imports.modunicode;
-    JSUnit.assertEquals(ModUnicode.uval, "const \u2665 utf8");
-}
-
-let oldSearchPath;
-
-function setUp() {
-    JSUnit.setUp();
-    oldSearchPath = imports.searchPath.slice();
-    imports.searchPath = ['resource:///org/gjs/jsunit/modules'];
-}
-
-function tearDown() {
-    JSUnit.tearDown();
-    imports.searchPath = oldSearchPath;
-}
-
-JSUnit.gjstestRun(this, setUp, tearDown);
+    it('does not make a separate copy of a module imported in two places', function () {
+        let A = imports.mutualImport.a;
+        A.incrementCount();
+        expect(A.getCount()).toEqual(1);
+        expect(A.getCountViaB()).toEqual(1);
+    });
+
+    it('evaluates an __init__.js file in an imported directory', function () {
+        expect(subB.testImporterFunction()).toEqual('__init__ function tested');
+    });
+
+    it('accesses a class defined in an __init__.js file', function () {
+        let o = new subB.ImporterClass();
+        expect(o).not.toBeNull();
+        expect(o.testMethod()).toEqual('__init__ class tested');
+    });
+
+    it('can import a file encoded in UTF-8', function () {
+        const ModUnicode = imports.modunicode;
+        expect(ModUnicode.uval).toEqual('const \u2665 utf8');
+    });
+});
diff --git a/installed-tests/js/testInterface.js b/installed-tests/js/testInterface.js
index 2af1171..08f3b94 100644
--- a/installed-tests/js/testInterface.js
+++ b/installed-tests/js/testInterface.js
@@ -1,6 +1,5 @@
 // -*- mode: js; indent-tabs-mode: nil -*-
 
-const JSUnit = imports.jsUnit;
 const Lang = imports.lang;
 
 const AnInterface = new Lang.Interface({
@@ -102,239 +101,237 @@ const ImplementationOfTwoInterfaces = new Lang.Class({
     }
 });
 
-function testInterfaceIsInstanceOfLangInterface() {
-    JSUnit.assertTrue(AnInterface instanceof Lang.Interface);
-    JSUnit.assertTrue(InterfaceRequiringOtherInterface instanceof Lang.Interface);
-}
-
-function testInterfaceCannotBeInstantiated() {
-    JSUnit.assertRaises(() => new AnInterface());
-}
-
-function testObjectCanImplementInterface() {
-    // Test will fail if the constructor throws an exception
-    let obj = new ObjectImplementingAnInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-}
-
-function testSuperclassCanImplementInterface() {
-    const ChildWhoseParentImplementsAnInterface = new Lang.Class({
-        Name: "ChildWhoseParentImplementsAnInterface",
-        Extends: ObjectImplementingAnInterface
+describe('An interface', function () {
+    it('is an instance of Lang.Interface', function () {
+        expect(AnInterface instanceof Lang.Interface).toBeTruthy();
+        expect(InterfaceRequiringOtherInterface instanceof Lang.Interface).toBeTruthy();
     });
-    let obj = new ChildWhoseParentImplementsAnInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-}
-
-function testObjectImplementingInterfaceHasCorrectConstructor() {
-    let obj = new ObjectImplementingAnInterface();
-    JSUnit.assertEquals(ObjectImplementingAnInterface, obj.constructor);
-}
-
-function testObjectCanImplementRequiredFunction() {
-    // Test considered passing if no exception thrown
-    let implementer = new ObjectImplementingAnInterface();
-    implementer.required();
-}
-
-function testInterfaceMustHaveName() {
-    JSUnit.assertRaises(function () {
-        const AnInterfaceWithoutAName = new Lang.Interface({
-            required: Lang.Interface.UNIMPLEMENTED
+
+    it('cannot be instantiated', function () {
+        expect(() => new AnInterface()).toThrow();
+    });
+
+    it('can be implemented by a class', function () {
+        let obj;
+        expect(() => { obj = new ObjectImplementingAnInterface(); }).not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+    });
+
+    it("can be implemented by a class's superclass", function () {
+        const ChildWhoseParentImplementsAnInterface = new Lang.Class({
+            Name: "ChildWhoseParentImplementsAnInterface",
+            Extends: ObjectImplementingAnInterface
+        });
+        let obj = new ChildWhoseParentImplementsAnInterface();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+    });
+
+    it("doesn't disturb a class's constructor", function () {
+        let obj = new ObjectImplementingAnInterface();
+        expect(obj.constructor).toEqual(ObjectImplementingAnInterface);
+    });
+
+    it('can have its required method implemented', function () {
+        let implementer = new ObjectImplementingAnInterface();
+        expect(() => implementer.required()).not.toThrow();
+    });
+
+    it('must have a name', function () {
+        expect(() => new Lang.Interface({
+            required: Lang.Interface.UNIMPLEMENTED,
+        })).toThrow();
+    });
+
+    it('must have its required methods implemented', function () {
+        expect(() => new Lang.Class({
+            Name: 'MyBadObject',
+            Implements: [AnInterface],
+        })).toThrow();
+    });
+
+    it('does not have to have its optional methods implemented', function () {
+        let obj;
+        expect(() => obj = new MinimalImplementationOfAnInterface()).not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+    });
+
+    it('can have its optional method deferred to by the implementation', function () {
+        let obj = new MinimalImplementationOfAnInterface();
+        expect(obj.optional()).toEqual('AnInterface.optional()');
+    });
+
+    it('can be chained up to by a class', function () {
+        let obj = new ObjectImplementingAnInterface();
+        expect(obj.optional()).toEqual('AnInterface.optional()');
+    });
+
+    it('can include arguments when being chained up to by a class', function () {
+        let obj = new ObjectImplementingAnInterface();
+        expect(obj.argumentGeneric('arg'))
+            .toEqual('AnInterface.argumentGeneric(arg (hello from class))');
+    });
+
+    it('can have its property getter deferred to', function () {
+        let obj = new ObjectImplementingAnInterface();
+        expect(obj.some_prop).toEqual('AnInterface.some_prop getter');
+    });
+
+    it('can have its property setter deferred to', function () {
+        let obj = new ObjectImplementingAnInterface();
+        obj.some_prop = 'foobar';
+        expect(obj.some_prop_setter_called).toBeTruthy();
+    });
+
+    it('can have its property getter overridden', function () {
+        const ObjectWithGetter = new Lang.Class({
+            Name: 'ObjectWithGetter',
+            Implements: [ AnInterface ],
+            required: function () {},
+            get some_prop() {
+                return 'ObjectWithGetter.some_prop getter';
+            }
         });
+        let obj = new ObjectWithGetter();
+        expect(obj.some_prop).toEqual('ObjectWithGetter.some_prop getter');
     });
-}
-
-function testClassMustImplementRequiredFunction() {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'MyBadObject',
-        Implements: [ AnInterface ]
-    }));
-}
-
-function testClassDoesntHaveToImplementOptionalFunction() {
-    // Test will fail if the constructor throws an exception
-    let obj = new MinimalImplementationOfAnInterface();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-}
-
-function testObjectCanDeferToInterfaceOptionalFunction() {
-    let obj = new MinimalImplementationOfAnInterface();
-    JSUnit.assertEquals('AnInterface.optional()', obj.optional());
-}
-
-function testObjectCanChainUpToInterface() {
-    let obj = new ObjectImplementingAnInterface();
-    JSUnit.assertEquals('AnInterface.optional()', obj.optional());
-}
-
-function testObjectCanChainUpToInterfaceWithArguments() {
-    let obj = new ObjectImplementingAnInterface();
-    JSUnit.assertEquals('AnInterface.argumentGeneric(arg (hello from class))',
-        obj.argumentGeneric('arg'));
-}
-
-function testObjectCanDeferToInterfaceGetter() {
-    let obj = new ObjectImplementingAnInterface();
-    JSUnit.assertEquals('AnInterface.some_prop getter', obj.some_prop);
-}
-
-function testObjectCanDeferToInterfaceSetter() {
-    let obj = new ObjectImplementingAnInterface();
-    obj.some_prop = 'foobar';
-    JSUnit.assertTrue(obj.some_prop_setter_called);
-}
-
-function testObjectCanOverrideInterfaceGetter() {
-    const ObjectWithGetter = new Lang.Class({
-        Name: 'ObjectWithGetter',
-        Implements: [ AnInterface ],
-        required: function () {},
-        get some_prop() {
-            return 'ObjectWithGetter.some_prop getter';
-        }
+
+    it('can have its property setter overridden', function () {
+        const ObjectWithSetter = new Lang.Class({
+            Name: 'ObjectWithSetter',
+            Implements: [ AnInterface ],
+            required: function () {},
+            set some_prop(value) {  /* setter without getter */// jshint ignore:line
+                this.overridden_some_prop_setter_called = true;
+            }
+        });
+        let obj = new ObjectWithSetter();
+        obj.some_prop = 'foobar';
+        expect(obj.overridden_some_prop_setter_called).toBeTruthy();
+        expect(obj.some_prop_setter_called).not.toBeDefined();
+    });
+
+    it('can require another interface', function () {
+        let obj;
+        expect(() => { obj = new ImplementationOfTwoInterfaces(); }).not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+        expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy();
+    });
+
+    it('can have empty requires', function () {
+        expect(() => new Lang.Interface({
+            Name: 'InterfaceWithEmptyRequires',
+            Requires: []
+        })).not.toThrow();
+    });
+
+    it('can chain up to another interface', function () {
+        let obj = new ImplementationOfTwoInterfaces();
+        expect(obj.optional())
+            .toEqual('InterfaceRequiringOtherInterface.optional()\nAnInterface.optional()');
+    });
+
+    it('can be chained up to with a generic', function () {
+        let obj = new ObjectImplementingAnInterface();
+        expect(obj.optionalGeneric()).toEqual('AnInterface.optionalGeneric()');
+    });
+
+    it('can chain up to another interface with a generic', function () {
+        let obj = new ImplementationOfTwoInterfaces();
+        expect(obj.optionalGeneric())
+            .toEqual('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()');
+    });
+
+    it('has its optional function defer to that of the last interface', function () {
+        const MinimalImplementationOfTwoInterfaces = new Lang.Class({
+            Name: 'MinimalImplementationOfTwoInterfaces',
+            Implements: [ AnInterface, InterfaceRequiringOtherInterface ],
+
+            required: function () {}
+        });
+        let obj = new MinimalImplementationOfTwoInterfaces();
+        expect(obj.optionalGeneric())
+            .toEqual('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()');
     });
-    let obj = new ObjectWithGetter();
-    JSUnit.assertEquals('ObjectWithGetter.some_prop getter', obj.some_prop);
-}
-
-function testObjectCanOverrideInterfaceSetter() {
-    const ObjectWithSetter = new Lang.Class({
-        Name: 'ObjectWithSetter',
-        Implements: [ AnInterface ],
-        required: function () {},
-        set some_prop(value) {  /* setter without getter */// jshint ignore:line
-            this.overridden_some_prop_setter_called = true;
-        }
+
+    it('must have all its required interfaces implemented', function () {
+        expect(() => new Lang.Class({
+            Name: 'ObjectWithNotEnoughInterfaces',
+            Implements: [ InterfaceRequiringOtherInterface ],
+            required: function () {}
+        })).toThrow();
     });
-    let obj = new ObjectWithSetter();
-    obj.some_prop = 'foobar';
-    JSUnit.assertTrue(obj.overridden_some_prop_setter_called);
-    JSUnit.assertUndefined(obj.some_prop_setter_called);
-}
-
-function testInterfaceCanRequireOtherInterface() {
-    // Test will fail if the constructor throws an exception
-    let obj = new ImplementationOfTwoInterfaces();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringOtherInterface));
-}
-
-function testRequiresCanBeEmpty() {
-    // Test considered passing if no exception thrown
-    const InterfaceWithEmptyRequires = new Lang.Interface({
-        Name: 'InterfaceWithEmptyRequires',
-        Requires: []
+
+    it('must have all its required interfaces implemented in the correct order', function () {
+        expect(() => new Lang.Class({
+            Name: 'ObjectWithInterfacesInWrongOrder',
+            Implements: [ InterfaceRequiringOtherInterface, AnInterface ],
+            required: function () {}
+        })).toThrow();
     });
-}
-
-function testInterfaceCanChainUpToOtherInterface() {
-    let obj = new ImplementationOfTwoInterfaces();
-    JSUnit.assertEquals('InterfaceRequiringOtherInterface.optional()\nAnInterface.optional()',
-        obj.optional());
-}
-
-function testObjectCanChainUpToInterfaceWithGeneric() {
-    let obj = new ObjectImplementingAnInterface();
-    JSUnit.assertEquals('AnInterface.optionalGeneric()',
-        obj.optionalGeneric());
-}
-
-function testInterfaceCanChainUpToOtherInterfaceWithGeneric() {
-    let obj = new ImplementationOfTwoInterfaces();
-    JSUnit.assertEquals('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()',
-        obj.optionalGeneric());
-}
-
-function testObjectDefersToLastInterfaceOptionalFunction() {
-    const MinimalImplementationOfTwoInterfaces = new Lang.Class({
-        Name: 'MinimalImplementationOfTwoInterfaces',
-        Implements: [ AnInterface, InterfaceRequiringOtherInterface ],
-
-        required: function () {}
+
+    it('can have its implementation on a parent class', function () {
+        let obj;
+        expect(() => {
+            const ObjectInheritingFromInterfaceImplementation = new Lang.Class({
+                Name: 'ObjectInheritingFromInterfaceImplementation',
+                Extends: ObjectImplementingAnInterface,
+                Implements: [ InterfaceRequiringOtherInterface ],
+            });
+            obj = new ObjectInheritingFromInterfaceImplementation();
+        }).not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+        expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy();
     });
-    let obj = new MinimalImplementationOfTwoInterfaces();
-    JSUnit.assertEquals('InterfaceRequiringOtherInterface.optionalGeneric()\nAnInterface.optionalGeneric()',
-        obj.optionalGeneric());
-}
-
-function testClassMustImplementAllRequiredInterfaces() {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'ObjectWithNotEnoughInterfaces',
-        Implements: [ InterfaceRequiringOtherInterface ],
-        required: function () {}
-    }));
-}
-
-function testClassMustImplementRequiredInterfacesInCorrectOrder() {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'ObjectWithInterfacesInWrongOrder',
-        Implements: [ InterfaceRequiringOtherInterface, AnInterface ],
-        required: function () {}
-    }));
-}
-
-function testInterfacesCanBeImplementedOnAParentClass() {
-    // Test will fail if the constructor throws an exception
-    const ObjectInheritingFromInterfaceImplementation = new Lang.Class({
-        Name: 'ObjectInheritingFromInterfaceImplementation',
-        Extends: ObjectImplementingAnInterface,
-        Implements: [ InterfaceRequiringOtherInterface ],
+
+    it('can require its implementor to be a subclass of some class', function () {
+        let obj;
+        expect(() => {
+            const ObjectImplementingInterfaceRequiringParentObject = new Lang.Class({
+                Name: 'ObjectImplementingInterfaceRequiringParentObject',
+                Extends: ObjectImplementingAnInterface,
+                Implements: [ InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface ]
+            });
+            obj = new ObjectImplementingInterfaceRequiringParentObject();
+        }).not.toThrow();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+        expect(obj.constructor.implements(InterfaceRequiringOtherInterface)).toBeTruthy();
+        expect(obj.constructor.implements(InterfaceRequiringClassAndInterface)).toBeTruthy();
     });
-    let obj = new ObjectInheritingFromInterfaceImplementation();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringOtherInterface));
-}
-
-function testInterfacesCanRequireBeingImplementedOnASubclass() {
-    // Test will fail if the constructor throws an exception
-    const ObjectImplementingInterfaceRequiringParentObject = new Lang.Class({
-        Name: 'ObjectImplementingInterfaceRequiringParentObject',
-        Extends: ObjectImplementingAnInterface,
-        Implements: [ InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface ]
+
+    it('must be implemented by an object which subclasses the required class', function () {
+        expect(() => new Lang.Class({
+            Name: 'ObjectWithoutRequiredParent',
+            Implements: [ AnInterface, InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface 
],
+            required: function () {},
+        })).toThrow();
     });
-    let obj = new ObjectImplementingInterfaceRequiringParentObject();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringOtherInterface));
-    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringClassAndInterface));
-}
-
-function testObjectsMustSubclassIfRequired() {
-    JSUnit.assertRaises(() => new Lang.Class({
-        Name: 'ObjectWithoutRequiredParent',
-        Implements: [ AnInterface, InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface ],
-        required: function () {},
-    }));
-}
-
-function testInterfaceMethodsCanCallOtherInterfaceMethods() {
-    let obj = new ObjectImplementingAnInterface();
-    JSUnit.assertEquals('interface private method', obj.usesThis());
-}
-
-function testSubclassImplementsTheSameInterfaceAsItsParent() {
-    const SubObject = new Lang.Class({
-        Name: 'SubObject',
-        Extends: ObjectImplementingAnInterface
+
+    it('can have methods that call others of its methods', function () {
+        let obj = new ObjectImplementingAnInterface();
+        expect(obj.usesThis()).toEqual('interface private method');
     });
-    let obj = new SubObject();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-}
-
-function testSubclassCanReimplementTheSameInterfaceAsItsParent() {
-    const SubImplementer = new Lang.Class({
-        Name: 'SubImplementer',
-        Extends: ObjectImplementingAnInterface,
-        Implements: [ AnInterface ]
+
+    it('is implemented by a subclass of a class that implements it', function () {
+        const SubObject = new Lang.Class({
+            Name: 'SubObject',
+            Extends: ObjectImplementingAnInterface
+        });
+        let obj = new SubObject();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
     });
-    let obj = new SubImplementer();
-    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
-    obj.required();  // should not throw NotImplemented
-}
 
-function testToString() {
-    JSUnit.assertEquals('[interface Interface for AnInterface]',
-                        AnInterface.toString());
-}
+    it('can be reimplemented by a subclass of a class that implements it', function () {
+        const SubImplementer = new Lang.Class({
+            Name: 'SubImplementer',
+            Extends: ObjectImplementingAnInterface,
+            Implements: [ AnInterface ]
+        });
+        let obj = new SubImplementer();
+        expect(obj.constructor.implements(AnInterface)).toBeTruthy();
+        expect(() => obj.required()).not.toThrow();
+    });
 
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('tells what it is with toString()', function () {
+        expect(AnInterface.toString()).toEqual('[interface Interface for AnInterface]');
+    });
+});
diff --git a/installed-tests/js/testLang.js b/installed-tests/js/testLang.js
index 342a599..51874bd 100644
--- a/installed-tests/js/testLang.js
+++ b/installed-tests/js/testLang.js
@@ -1,112 +1,99 @@
 // tests for imports.lang module
+// except for Lang.Class and Lang.Interface, which are tested in separate files
 
-const JSUnit = imports.jsUnit;
 const Lang = imports.lang;
 
-function testCountProperties() {
-    var foo = { 'a' : 10, 'b' : 11 };
-    JSUnit.assertEquals("number of props", 2, Lang.countProperties(foo));
-}
-
-function testCopyProperties() {
-    var foo = { 'a' : 10, 'b' : 11 };
-    var bar = {};
-
-    Lang.copyProperties(foo, bar);
-
-    JSUnit.assertTrue("a in bar", ('a' in bar));
-    JSUnit.assertTrue("b in bar", ('b' in bar));
-    JSUnit.assertEquals("a is 10", 10, bar.a);
-    JSUnit.assertEquals("b is 11", 11, bar.b);
-    JSUnit.assertEquals("2 items in bar", 2, Lang.countProperties(bar));
-}
-
-function testCopyPublicProperties() {
-    var foo = { 'a' : 10, 'b' : 11, '_c' : 12 };
-    var bar = {};
-
-    Lang.copyPublicProperties(foo, bar);
-
-    JSUnit.assertTrue("a in bar", ('a' in bar));
-    JSUnit.assertTrue("b in bar", ('b' in bar));
-    JSUnit.assertFalse("_c in bar", ('_c' in bar));
-    JSUnit.assertEquals("a is 10", 10, bar.a);
-    JSUnit.assertEquals("b is 11", 11, bar.b);
-    JSUnit.assertEquals("2 items in bar", 2, Lang.countProperties(bar));
-}
-
-function testCopyGetterSetterProperties() {
-    var foo = {
-        'a' : 10,
-        'b' : 11,
-        get c() {
-            return this.a;
-        },
-        set c(n) {
-            this.a = n;
-        }};
-    var bar = {};
-
-    Lang.copyProperties(foo, bar);
-
-    let getterFunc = bar.__lookupGetter__("c");
-    let setterFunc = bar.__lookupSetter__("c");
-
-    // this should return the value of 'a'
-    let c = bar.c;
-
-    // this should set 'a' value
-    bar.c = 13;
-
-    JSUnit.assertTrue("bar has 'c' getter", (getterFunc != null));
-    JSUnit.assertTrue("bar has 'c' setter", (setterFunc != null));
-    JSUnit.assertTrue("bar 'c' value is 10", (c == 10));
-    JSUnit.assertTrue("bar 'a' new value is 13", (bar.a == 13));
-}
-
-function testBind() {
-
-    function Obj() {
-    }
-
-    Obj.prototype = {
-        callback: function() {
-            this.obj = this;
-            this.args = arguments;
-            return true;
-        }
-    };
-
-    let callback;
-
-    let o = new Obj();
-    callback = Lang.bind(o, o.callback);
-    JSUnit.assertEquals(callback(), true);
-    JSUnit.assertNotEquals("o.obj in callback", undefined, o.obj);
-    JSUnit.assertEquals("o.obj in callback", o, o.obj);
-    JSUnit.assertEquals("o.args in callback", 0, o.args.length);
-    JSUnit.assertRaises(function() { return Lang.bind(o, undefined); });
-    JSUnit.assertRaises(function() { return Lang.bind(undefined, function() {}); });
-
-    let o2 = new Obj();
-    callback = Lang.bind(o2, o2.callback, 42, 1138);
-    JSUnit.assertEquals(callback(), true);
-    JSUnit.assertNotEquals("o2.args in callback", undefined, o2.args);
-    JSUnit.assertEquals("o2.args.length in callback", 2, o2.args.length);
-    JSUnit.assertEquals("o2.args[0] in callback", 42, o2.args[0]);
-    JSUnit.assertEquals("o2.args[1] in callback", 1138, o2.args[1]);
-
-    let o3 = new Obj();
-    callback = Lang.bind(o3, o3.callback, 42, 1138);
-    JSUnit.assertEquals(callback(1, 2, 3), true);
-    JSUnit.assertNotEquals("o3.args in callback", undefined, o3.args);
-    JSUnit.assertEquals("o3.args.length in callback", 5, o3.args.length);
-    JSUnit.assertEquals("o3.args[0] in callback", 1, o3.args[0]);
-    JSUnit.assertEquals("o3.args[1] in callback", 2, o3.args[1]);
-    JSUnit.assertEquals("o3.args[2] in callback", 3, o3.args[2]);
-    JSUnit.assertEquals("o3.args[3] in callback", 42, o3.args[3]);
-    JSUnit.assertEquals("o3.args[4] in callback", 1138, o3.args[4]);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
-
+describe('Lang module', function () {
+    it('counts properties with Lang.countProperties()', function () {
+        var foo = { 'a' : 10, 'b' : 11 };
+        expect(Lang.countProperties(foo)).toEqual(2);
+    });
+
+    it('copies properties from one object to another with Lang.copyProperties()', function () {
+        var foo = { 'a' : 10, 'b' : 11 };
+        var bar = {};
+
+        Lang.copyProperties(foo, bar);
+        expect(bar).toEqual(foo);
+    });
+
+    it('copies properties without an underscore with Lang.copyPublicProperties()', function () {
+        var foo = { 'a' : 10, 'b' : 11, '_c' : 12 };
+        var bar = {};
+
+        Lang.copyPublicProperties(foo, bar);
+        expect(bar).toEqual({ 'a': 10, 'b': 11 });
+    });
+
+    it('copies property getters and setters', function () {
+        var foo = {
+            'a' : 10,
+            'b' : 11,
+            get c() {
+                return this.a;
+            },
+            set c(n) {
+                this.a = n;
+            }
+        };
+        var bar = {};
+
+        Lang.copyProperties(foo, bar);
+
+        expect(bar.__lookupGetter__('c')).not.toBeNull();
+        expect(bar.__lookupSetter__('c')).not.toBeNull();
+
+        // this should return the value of 'a'
+        expect(bar.c).toEqual(10);
+
+        // this should set 'a' value
+        bar.c = 13;
+        expect(bar.a).toEqual(13);
+    });
+
+
+    describe('bind()', function () {
+        const Obj = new Lang.Class({
+            Name: 'Obj',
+            callback: function () {
+                return true;
+            },
+        });
+        let o;
+
+        beforeEach(function () {
+            o = new Obj();
+            spyOn(o, 'callback').and.callThrough();
+        });
+
+        it('calls the bound function with the supplied this-object', function () {
+            let callback = Lang.bind(o, o.callback);
+            callback();
+            expect(o.callback.calls.mostRecent()).toEqual({
+                object: o,
+                args: [],
+                returnValue: true,
+            });
+        });
+
+        it('throws an error when no function supplied', function () {
+            expect(() => Lang.bind(o, undefined)).toThrow();
+        });
+
+        it('throws an error when this-object undefined', function () {
+            expect(() => Lang.bind(undefined, function () {})).toThrow();
+        });
+
+        it('supplies extra arguments to the function', function () {
+            let callback = Lang.bind(o, o.callback, 42, 1138);
+            callback();
+            expect(o.callback).toHaveBeenCalledWith(42, 1138);
+        });
+
+        it('appends the extra arguments to any arguments passed', function () {
+            let callback = Lang.bind(o, o.callback, 42, 1138);
+            callback(1, 2, 3);
+            expect(o.callback).toHaveBeenCalledWith(1, 2, 3, 42, 1138);
+        });
+    });
+});
diff --git a/installed-tests/js/testLocale.js b/installed-tests/js/testLocale.js
index b95c8e7..16b17ac 100644
--- a/installed-tests/js/testLocale.js
+++ b/installed-tests/js/testLocale.js
@@ -1,36 +1,37 @@
-// tests for JS_SetLocaleCallbacks().
-const JSUnit = imports.jsUnit;
-
-function testToLocaleDateString() {
-    let date = new Date('12/15/1981');
+describe('JS_SetLocaleCallbacks', function () {
     // Requesting the weekday name tests locale_to_unicode
-    let datestr = date.toLocaleDateString('pt-BR', { weekday: 'long' });
-    JSUnit.assertEquals('terça-feira', datestr);
-}
+    it('toLocaleDateString() works', function () {
+        let date = new Date('12/15/1981');
+        let datestr = date.toLocaleDateString('pt-BR', { weekday: 'long' });
+        expect(datestr).toEqual('terça-feira');
+    });
 
-function testToLocaleLowerCase() {
-    JSUnit.assertEquals("aaa", "AAA".toLocaleLowerCase());
+    it('toLocaleLowerCase() works', function () {
+        expect('AAA'.toLocaleLowerCase()).toEqual('aaa');
+    });
 
     // String conversion is implemented internally to GLib,
     // and is more-or-less independent of locale. (A few
     // characters are handled specially for a few locales,
     // like i in Turkish. But not A WITH ACUTE)
-    JSUnit.assertEquals("\u00e1", "\u00c1".toLocaleLowerCase());
-}
+    it('toLocaleLowerCase() works for Unicode', function () {
+        expect('\u00c1'.toLocaleLowerCase()).toEqual('\u00e1');
+    });
+
+    it('toLocaleUpperCase() works', function () {
+        expect('aaa'.toLocaleUpperCase()).toEqual('AAA');
+    });
 
-function testToLocaleUpperCase() {
-    JSUnit.assertEquals("AAA", "aaa".toLocaleUpperCase());
-    JSUnit.assertEquals("\u00c1", "\u00e1".toLocaleUpperCase());
-}
+    it('toLocaleUpperCase() works for Unicode', function () {
+        expect('\u00e1'.toLocaleUpperCase()).toEqual('\u00c1');
+    });
 
-function testToLocaleCompare() {
     // GLib calls out to libc for collation, so we can't really
     // assume anything - we could even be running in the
     // C locale. The below is pretty safe.
-    JSUnit.assertTrue("a".localeCompare("b") < 0);
-    JSUnit.assertEquals( 0, "a".localeCompare("a"));
-    JSUnit.assertTrue("b".localeCompare("a") > 0);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
-
+    it('localeCompare() works', function () {
+        expect('a'.localeCompare('b')).toBeLessThan(0);
+        expect('a'.localeCompare('a')).toEqual(0);
+        expect('b'.localeCompare('a')).toBeGreaterThan(0);
+    });
+});
diff --git a/installed-tests/js/testMainloop.js b/installed-tests/js/testMainloop.js
index 4d9579d..0ffcf37 100644
--- a/installed-tests/js/testMainloop.js
+++ b/installed-tests/js/testMainloop.js
@@ -1,106 +1,91 @@
-const JSUnit = imports.jsUnit;
 const Mainloop = imports.mainloop;
 
-function testTimeout() {
-    var trackTimeout = {
-        runTenTimes : 0,
-        runOnlyOnce: 0,
-        neverRun: 0
-    };
-
-    Mainloop.timeout_add(10,
-                         function() {
-                             if (trackTimeout.runTenTimes == 10) {
-                                 Mainloop.quit('testtimeout');
-                                 return false;
-                             }
-
-                             trackTimeout.runTenTimes += 1;
-                             return true;
-                         });
-
-    Mainloop.timeout_add(10,
-                         function () {
-                             trackTimeout.runOnlyOnce += 1;
-                             return false;
-                         });
-
-    Mainloop.timeout_add(15000,
-                       function() {
-                           trackTimeout.neverRun += 1;
-                           return false;
-                       });
-
-    Mainloop.run('testtimeout');
-
-    with (trackTimeout) {
-        JSUnit.assertEquals("run ten times", 10, runTenTimes);
-        JSUnit.assertEquals("run only once", 1, runOnlyOnce);
-        JSUnit.assertEquals("never run", 0, neverRun);
-    }
-}
-
-function testIdle() {
-    var trackIdles = {
-        runTwiceCount : 0,
-        runOnceCount : 0,
-        neverRunsCount : 0,
-        quitAfterManyRunsCount : 0
-    };
-    Mainloop.idle_add(function() {
-                          trackIdles.runTwiceCount += 1;
-                          if (trackIdles.runTwiceCount == 2)
-                              return false;
-                          else
-                              return true;
-                      });
-    Mainloop.idle_add(function() {
-                          trackIdles.runOnceCount += 1;
-                          return false;
-                      });
-    var neverRunsId =
-        Mainloop.idle_add(function() {
-                              trackIdles.neverRunsCount += 1;
-                              return false;
-                          });
-    Mainloop.idle_add(function() {
-                          trackIdles.quitAfterManyRunsCount += 1;
-                          if (trackIdles.quitAfterManyRunsCount > 10) {
-                              Mainloop.quit('foobar');
-                              return false;
-                          } else {
-                              return true;
-                          }
-                      });
-
-    Mainloop.source_remove(neverRunsId);
-
-    Mainloop.run('foobar');
-
-    JSUnit.assertEquals("one-shot ran once", 1, trackIdles.runOnceCount);
-    JSUnit.assertEquals("two-shot ran twice", 2, trackIdles.runTwiceCount);
-    JSUnit.assertEquals("removed never ran", 0, trackIdles.neverRunsCount);
-    JSUnit.assertEquals("quit after many ran 11", 11, trackIdles.quitAfterManyRunsCount);
-
-    // check re-entrancy of removing closures while they
-    // are being invoked
-
-    trackIdles.removeId = Mainloop.idle_add(function() {
-                                                Mainloop.source_remove(trackIdles.removeId);
-                                                Mainloop.quit('foobar');
-                                                return false;
-                                            });
-    Mainloop.run('foobar');
+describe('Mainloop.timeout_add()', function () {
+    let runTenTimes, runOnlyOnce, neverRun;
+    beforeAll(function (done) {
+        let count = 0;
+        runTenTimes = jasmine.createSpy('runTenTimes').and.callFake(() => {
+            if (count === 10) {
+                done();
+                return false;
+            }
+            count += 1;
+            return true;
+        });
+        runOnlyOnce = jasmine.createSpy('runOnlyOnce').and.returnValue(false);
+        neverRun = jasmine.createSpy('neverRun').and.throwError();
+
+        Mainloop.timeout_add(10, runTenTimes);
+        Mainloop.timeout_add(10, runOnlyOnce);
+        Mainloop.timeout_add(15000, neverRun);
+    });
+
+    it('runs a timeout function', function () {
+        expect(runOnlyOnce).toHaveBeenCalledTimes(1);
+    });
+
+    it('runs a timeout function until it returns false', function () {
+        expect(runTenTimes).toHaveBeenCalledTimes(11);
+    });
+
+    it('runs a timeout function after an initial timeout', function () {
+        expect(neverRun).not.toHaveBeenCalled();
+    });
+});
+
+describe('Mainloop.idle_add()', function () {
+    let runOnce, runTwice, neverRuns, quitAfterManyRuns;
+    beforeAll(function (done) {
+        runOnce = jasmine.createSpy('runOnce').and.returnValue(false);
+        runTwice = jasmine.createSpy('runTwice').and.returnValues([true, false]);
+        neverRuns = jasmine.createSpy('neverRuns').and.throwError();
+        let count = 0;
+        quitAfterManyRuns = jasmine.createSpy('quitAfterManyRuns').and.callFake(() => {
+            count += 1;
+            if (count > 10) {
+                done();
+                return false;
+            }
+            return true;
+        });
+
+        Mainloop.idle_add(runOnce);
+        Mainloop.idle_add(runTwice);
+        let neverRunsId = Mainloop.idle_add(neverRuns);
+        Mainloop.idle_add(quitAfterManyRuns);
+
+        Mainloop.source_remove(neverRunsId);
+    });
+
+    it('runs an idle function', function () {
+        expect(runOnce).toHaveBeenCalledTimes(1);
+    });
+
+    it('continues to run idle functions that return true', function () {
+        expect(runTwice).toHaveBeenCalledTimes(2);
+        expect(quitAfterManyRuns).toHaveBeenCalledTimes(11);
+    });
+
+    it('does not run idle functions if removed', function () {
+        expect(neverRuns).not.toHaveBeenCalled();
+    });
+
+    it('can remove idle functions while they are being invoked', function (done) {
+        let removeId = Mainloop.idle_add(() => {
+            Mainloop.source_remove(removeId);
+            done();
+            return false;
+        });
+    });
 
     // Add an idle before exit, then never run main loop again.
     // This is to test that we remove idle callbacks when the associated
-    // JSContext is blown away. The leak check in gjs-unit will
+    // JSContext is blown away. The leak check in minijasmine will
     // fail if the idle function is not garbage collected.
-    Mainloop.idle_add(function() {
-                          fail("This should never have been called");
-                          return true;
-                      });
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
-
+    it('does not leak idle callbacks', function () {
+        Mainloop.idle_add(() => {
+            fail('This should never have been called');
+            return true;
+        });
+    });
+});
diff --git a/installed-tests/js/testMetaClass.js b/installed-tests/js/testMetaClass.js
index e570f35..48c92a7 100644
--- a/installed-tests/js/testMetaClass.js
+++ b/installed-tests/js/testMetaClass.js
@@ -1,20 +1,7 @@
 // -*- mode: js; indent-tabs-mode: nil -*-
-const JSUnit = imports.jsUnit;
-
-if (!('assertEquals' in this)) { /* allow running this test standalone */
-    imports.lang.copyPublicProperties(imports.jsUnit, this);
-    gjstestRun = function() { return imports.jsUnit.gjstestRun(window); };
-}
 
 const Lang = imports.lang;
 
-function assertArrayEquals(expected, got) {
-    JSUnit.assertEquals(expected.length, got.length);
-    for (let i = 0; i < expected.length; i ++) {
-        JSUnit.assertEquals(expected[i], got[i]);
-    }
-}
-
 const NormalClass = new Lang.Class({
     Name: 'NormalClass',
 
@@ -80,49 +67,51 @@ const CustomMetaSubclass = new Lang.Class({
     }
 });
 
-function testMetaClass() {
-    assertArrayEquals(['CustomMetaOne',
-                       'CustomMetaTwo',
-                       'CustomMetaSubclass'], Subclassed);
-
-    JSUnit.assertTrue(NormalClass instanceof Lang.Class);
-    JSUnit.assertTrue(MetaClass instanceof Lang.Class);
-
-    JSUnit.assertTrue(CustomMetaOne instanceof Lang.Class);
-    JSUnit.assertTrue(CustomMetaOne instanceof MetaClass);
-
-    JSUnit.assertEquals(2, CustomMetaTwo.DYNAMIC_CONSTANT);
-    JSUnit.assertUndefined(CustomMetaOne.DYNAMIC_CONSTANT);
-}
-
-function testMetaInstance() {
-    let instanceOne = new CustomMetaOne();
-
-    JSUnit.assertEquals(1, instanceOne.one);
-    JSUnit.assertEquals(2, instanceOne.two);
-
-    JSUnit.assertRaises(function() {
-        instanceOne.dynamic_method();
+describe('A metaclass', function () {
+    it('has its constructor called each time a class is created with it', function () {
+        expect(Subclassed).toEqual(['CustomMetaOne', 'CustomMetaTwo',
+            'CustomMetaSubclass']);
     });
 
-    let instanceTwo = new CustomMetaTwo();
-    JSUnit.assertEquals(1, instanceTwo.one);
-    JSUnit.assertEquals(2, instanceTwo.two);
-    JSUnit.assertEquals(73, instanceTwo.dynamic_method());
-}
-
-function testMetaSubclass() {
-    JSUnit.assertTrue(CustomMetaSubclass instanceof MetaClass);
+    it('is an instance of Lang.Class', function () {
+        expect(NormalClass instanceof Lang.Class).toBeTruthy();
+        expect(MetaClass instanceof Lang.Class).toBeTruthy();
+    });
 
-    let instance = new CustomMetaSubclass();
+    it('produces instances that are instances of itself and Lang.Class', function () {
+        expect(CustomMetaOne instanceof Lang.Class).toBeTruthy();
+        expect(CustomMetaOne instanceof MetaClass).toBeTruthy();
+    });
 
-    JSUnit.assertEquals(1, instance.one);
-    JSUnit.assertEquals(2, instance.two);
-    JSUnit.assertEquals(3, instance.three);
+    it('can dynamically define properties in its constructor', function () {
+        expect(CustomMetaTwo.DYNAMIC_CONSTANT).toEqual(2);
+        expect(CustomMetaOne.DYNAMIC_CONSTANT).not.toBeDefined();
+    });
 
-    JSUnit.assertEquals(73, instance.dynamic_method());
-    JSUnit.assertEquals(2, CustomMetaSubclass.DYNAMIC_CONSTANT);
-}
+    describe('instance', function () {
+        let instanceOne, instanceTwo;
+        beforeEach(function () {
+            instanceOne = new CustomMetaOne();
+            instanceTwo = new CustomMetaTwo();
+        });
+
+        it('gets all the properties from its class and metaclass', function () {
+            expect(instanceOne).toEqual(jasmine.objectContaining({ one: 1, two: 2 }));
+            expect(instanceTwo).toEqual(jasmine.objectContaining({ one: 1, two: 2 }));
+        });
+
+        it('gets dynamically defined properties from metaclass', function () {
+            expect(() => instanceOne.dynamic_method()).toThrow();
+            expect(instanceTwo.dynamic_method()).toEqual(73);
+        });
+    });
 
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('can be instantiated with Lang.Class but still get the appropriate metaclass', function () {
+        expect(CustomMetaSubclass instanceof MetaClass).toBeTruthy();
+        expect(CustomMetaSubclass.DYNAMIC_CONSTANT).toEqual(2);
 
+        let instance = new CustomMetaSubclass();
+        expect(instance).toEqual(jasmine.objectContaining({ one: 1, two: 2, three: 3 }));
+        expect(instance.dynamic_method()).toEqual(73);
+    });
+});
diff --git a/installed-tests/js/testNamespace.js b/installed-tests/js/testNamespace.js
index bcca986..49c6426 100644
--- a/installed-tests/js/testNamespace.js
+++ b/installed-tests/js/testNamespace.js
@@ -1,8 +1,7 @@
-const JSUnit = imports.jsUnit;
-const Everything = imports.gi.Regress;
+const Regress = imports.gi.Regress;
 
-function testName() {
-    JSUnit.assertEquals(Everything.__name__, "Regress");
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+describe('GI repository namespace', function () {
+    it('supplies a name', function () {
+        expect(Regress.__name__).toEqual('Regress');
+    });
+});
diff --git a/installed-tests/js/testParamSpec.js b/installed-tests/js/testParamSpec.js
index 1b7013c..0620e0c 100644
--- a/installed-tests/js/testParamSpec.js
+++ b/installed-tests/js/testParamSpec.js
@@ -1,5 +1,3 @@
-const JSUnit = imports.jsUnit;
-
 const Regress = imports.gi.Regress;
 const GObject = imports.gi.GObject;
 
@@ -8,96 +6,37 @@ let nick = 'Foo property';
 let blurb = 'This is the foo property';
 let flags = GObject.ParamFlags.READABLE;
 
-function testStringParamSpec() {
-    let stringSpec = GObject.ParamSpec.string(name, nick, blurb, flags,
-                                              'Default Value');
-
-    JSUnit.assertEquals(name, stringSpec.name);
-    JSUnit.assertEquals(nick, stringSpec._nick);
-    JSUnit.assertEquals(blurb, stringSpec._blurb);
-    JSUnit.assertEquals(flags, stringSpec.flags);
-    JSUnit.assertEquals('Default Value', stringSpec.default_value);
-}
-
-function testIntParamSpec() {
-    let intSpec = GObject.ParamSpec.int(name, nick, blurb, flags,
-                                        -100, 100, -42);
-
-    JSUnit.assertEquals(name, intSpec.name);
-    JSUnit.assertEquals(nick, intSpec._nick);
-    JSUnit.assertEquals(blurb, intSpec._blurb);
-    JSUnit.assertEquals(flags, intSpec.flags);
-    JSUnit.assertEquals(-42, intSpec.default_value);
-}
-
-function testUIntParamSpec() {
-    let uintSpec = GObject.ParamSpec.uint(name, nick, blurb, flags,
-                                          20, 100, 42);
-
-    JSUnit.assertEquals(name, uintSpec.name);
-    JSUnit.assertEquals(nick, uintSpec._nick);
-    JSUnit.assertEquals(blurb, uintSpec._blurb);
-    JSUnit.assertEquals(flags, uintSpec.flags);
-    JSUnit.assertEquals(42, uintSpec.default_value);
-}
-
-function testInt64ParamSpec() {
-    let int64Spec = GObject.ParamSpec.int64(name, nick, blurb, flags,
-                                            0x4000,
-                                            0xffffffff,
-                                            0x2266bbff);
-
-    JSUnit.assertEquals(name, int64Spec.name);
-    JSUnit.assertEquals(nick, int64Spec._nick);
-    JSUnit.assertEquals(blurb, int64Spec._blurb);
-    JSUnit.assertEquals(flags, int64Spec.flags);
-    JSUnit.assertEquals(0x2266bbff, int64Spec.default_value);
-}
-
-function testUInt64ParamSpec() {
-    let uint64Spec = GObject.ParamSpec.uint64(name, nick, blurb, flags,
-                                              0,
-                                              0xffffffff,
-                                              0x2266bbff);
-
-    JSUnit.assertEquals(name, uint64Spec.name);
-    JSUnit.assertEquals(nick, uint64Spec._nick);
-    JSUnit.assertEquals(blurb, uint64Spec._blurb);
-    JSUnit.assertEquals(flags, uint64Spec.flags);
-    JSUnit.assertEquals(0x2266bbff, uint64Spec.default_value);
-}
-
-function testEnumParamSpec() {
-    let enumSpec = GObject.ParamSpec.enum(name, nick, blurb, flags,
-                                          Regress.TestEnum,
-                                          Regress.TestEnum.VALUE2);
-
-    JSUnit.assertEquals(name, enumSpec.name);
-    JSUnit.assertEquals(nick, enumSpec._nick);
-    JSUnit.assertEquals(blurb, enumSpec._blurb);
-    JSUnit.assertEquals(flags, enumSpec.flags);
-    JSUnit.assertEquals(Regress.TestEnum.VALUE2, enumSpec.default_value);
-}
-
-function testFlagsParamSpec() {
-    let flagsSpec = GObject.ParamSpec.flags(name, nick, blurb, flags,
-                                            Regress.TestFlags,
-                                            Regress.TestFlags.FLAG2);
-
-    JSUnit.assertEquals(name, flagsSpec.name);
-    JSUnit.assertEquals(nick, flagsSpec._nick);
-    JSUnit.assertEquals(blurb, flagsSpec._blurb);
-    JSUnit.assertEquals(flags, flagsSpec.flags);
-    JSUnit.assertEquals(Regress.TestFlags.FLAG2, flagsSpec.default_value);
-}
-
-function testParamSpecMethod() {
-    let objectSpec = GObject.ParamSpec.object(name, nick, blurb, flags, GObject.Object);
-
-    JSUnit.assertEquals(name, objectSpec.get_name());
-    JSUnit.assertEquals(nick, objectSpec.get_nick());
-    JSUnit.assertEquals(blurb, objectSpec.get_blurb());
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
-
+function testParamSpec(type, params, defaultValue) {
+    describe('GObject.ParamSpec.' + type, function () {
+        let paramSpec;
+        beforeEach(function () {
+            paramSpec = GObject.ParamSpec[type].apply(GObject.ParamSpec,
+                [name, nick, blurb, flags, ...params]);
+        });
+
+        it('has the correct name strings', function () {
+            expect(paramSpec.name).toEqual(name);
+            expect(paramSpec._nick).toEqual(nick);
+            expect(paramSpec._blurb).toEqual(blurb);
+        });
+
+        it('has the correct flags', function () {
+            expect(paramSpec.flags).toEqual(flags);
+        });
+
+        it('has the correct default value', function () {
+            expect(paramSpec.default_value).toEqual(defaultValue);
+        });
+    });
+}
+
+testParamSpec('string', ['Default Value'], 'Default Value');
+testParamSpec('int', [-100, 100, -42], -42);
+testParamSpec('uint', [20, 100, 42], 42);
+testParamSpec('int64', [0x4000, 0xffffffff, 0x2266bbff], 0x2266bbff);
+testParamSpec('uint64', [0, 0xffffffff, 0x2266bbff], 0x2266bbff);
+testParamSpec('enum', [Regress.TestEnum, Regress.TestEnum.VALUE2],
+    Regress.TestEnum.VALUE2);
+testParamSpec('flags', [Regress.TestFlags, Regress.TestFlags.FLAG2],
+    Regress.TestFlags.FLAG2);
+testParamSpec('object', [GObject.Object], null);
diff --git a/installed-tests/js/testSignals.js b/installed-tests/js/testSignals.js
index e3104d8..1ea4d27 100644
--- a/installed-tests/js/testSignals.js
+++ b/installed-tests/js/testSignals.js
@@ -1,139 +1,109 @@
-const JSUnit = imports.jsUnit;
 const GLib = imports.gi.GLib;
-
+const Lang = imports.lang;
 const Signals = imports.signals;
 
-function Foo() {
-    this._init();
-}
-
-Foo.prototype = {
-    _init : function() {
-    }
-};
-
+const Foo = new Lang.Class({
+    Name: 'Foo',
+    _init: function () {},
+});
 Signals.addSignalMethods(Foo.prototype);
 
-function testSimple() {
-    var foo = new Foo();
-    var id = foo.connect('bar',
-                         function(theFoo, a, b) {
-                             theFoo.a = a;
-                             theFoo.b = b;
-                         });
-    foo.emit('bar', "This is a", "This is b");
-    JSUnit.assertEquals("This is a", foo.a);
-    JSUnit.assertEquals("This is b", foo.b);
-    foo.disconnect(id);
-    // this emission should do nothing
-    foo.emit('bar', "Another a", "Another b");
-    // so these values should be unchanged
-    JSUnit.assertEquals("This is a", foo.a);
-    JSUnit.assertEquals("This is b", foo.b);
-}
-
-function testDisconnectDuringEmit() {
-    var foo = new Foo();
-    var toRemove = [];
-    var firstId = foo.connect('bar',
-                         function(theFoo) {
-                             theFoo.disconnect(toRemove[0]);
-                             theFoo.disconnect(toRemove[1]);
-                         });
-    var id = foo.connect('bar',
-                     function(theFoo) {
-                         throw new Error("This should not have been called 1");
-                     });
-    toRemove.push(id);
-
-    id = foo.connect('bar',
-                     function(theFoo) {
-                         throw new Error("This should not have been called 2");
-                     });
-    toRemove.push(id);
-
-    // emit signal; what should happen is that the second two handlers are
-    // disconnected before they get invoked
-    foo.emit('bar');
-
-    // clean up the last handler
-    foo.disconnect(firstId);
-
-    // poke in private implementation to sanity-check
-    JSUnit.assertEquals('no handlers left', 0, foo._signalConnections.length);
-}
-
-function testMultipleSignals() {
-    var foo = new Foo();
-
-    foo.barHandlersCalled = 0;
-    foo.bonkHandlersCalled = 0;
-    foo.connect('bar',
-                function(theFoo) {
-                    theFoo.barHandlersCalled += 1;
-                });
-    foo.connect('bonk',
-                function(theFoo) {
-                    theFoo.bonkHandlersCalled += 1;
-                });
-    foo.connect('bar',
-                function(theFoo) {
-                    theFoo.barHandlersCalled += 1;
-                });
-    foo.emit('bar');
-
-    JSUnit.assertEquals(2, foo.barHandlersCalled);
-    JSUnit.assertEquals(0, foo.bonkHandlersCalled);
-
-    foo.emit('bonk');
-
-    JSUnit.assertEquals(2, foo.barHandlersCalled);
-    JSUnit.assertEquals(1, foo.bonkHandlersCalled);
-
-    foo.emit('bar');
-
-    JSUnit.assertEquals(4, foo.barHandlersCalled);
-    JSUnit.assertEquals(1, foo.bonkHandlersCalled);
-
-    foo.disconnectAll();
-
-    // these post-disconnect emissions should do nothing
-    foo.emit('bar');
-    foo.emit('bonk');
-
-    JSUnit.assertEquals(4, foo.barHandlersCalled);
-    JSUnit.assertEquals(1, foo.bonkHandlersCalled);
-}
-
-function testExceptionInCallback() {
-    let foo = new Foo();
-
-    foo.bar1Called = 0;
-    foo.bar2Called = 0;
-    foo.connect('bar',
-                function(theFoo) {
-                    theFoo.bar1Called += 1;
-                    throw new Error("Exception we are throwing on purpose");
-                });
-    foo.connect('bar',
-                function(theFoo) {
-                    theFoo.bar2Called += 1;
-                });
-
-    // exception in callback does not effect other callbacks
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-                             'JS ERROR: Exception in callback for signal: *');
-    foo.emit('bar');
-    JSUnit.assertEquals(1, foo.bar1Called);
-    JSUnit.assertEquals(1, foo.bar2Called);
-
-    // exception in callback does not disconnect the callback
-    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
-                             'JS ERROR: Exception in callback for signal: *');
-    foo.emit('bar');
-    JSUnit.assertEquals(2, foo.bar1Called);
-    JSUnit.assertEquals(2, foo.bar2Called);
-}
-
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
-
+describe('Object with signals', function () {
+    let foo, bar;
+    beforeEach(function () {
+        foo = new Foo();
+        bar = jasmine.createSpy('bar');
+    });
+
+    it('calls a signal handler when a signal is emitted', function () {
+        foo.connect('bar', bar);
+        foo.emit('bar', "This is a", "This is b");
+        expect(bar).toHaveBeenCalledWith(foo, 'This is a', 'This is b');
+    });
+
+    it('does not call a signal handler after the signal is disconnected', function () {
+        let id = foo.connect('bar', bar);
+        foo.emit('bar', "This is a", "This is b");
+        bar.calls.reset();
+        foo.disconnect(id);
+        // this emission should do nothing
+        foo.emit('bar', "Another a", "Another b");
+        expect(bar).not.toHaveBeenCalled();
+    });
+
+    it('can disconnect a signal handler during signal emission', function () {
+        var toRemove = [];
+        let firstId = foo.connect('bar', function (theFoo) {
+            theFoo.disconnect(toRemove[0]);
+            theFoo.disconnect(toRemove[1]);
+        });
+        toRemove.push(foo.connect('bar', bar));
+        toRemove.push(foo.connect('bar', bar));
+
+        // emit signal; what should happen is that the second two handlers are
+        // disconnected before they get invoked
+        foo.emit('bar');
+        expect(bar).not.toHaveBeenCalled();
+
+        // clean up the last handler
+        foo.disconnect(firstId);
+
+        // poke in private implementation to sanity-check no handlers left
+        expect(foo._signalConnections.length).toEqual(0);
+    });
+
+    it('distinguishes multiple signals', function () {
+        let bonk = jasmine.createSpy('bonk');
+        foo.connect('bar', bar);
+        foo.connect('bonk', bonk);
+        foo.connect('bar', bar);
+
+        foo.emit('bar');
+        expect(bar).toHaveBeenCalledTimes(2);
+        expect(bonk).not.toHaveBeenCalled();
+
+        foo.emit('bonk');
+        expect(bar).toHaveBeenCalledTimes(2);
+        expect(bonk).toHaveBeenCalledTimes(1);
+
+        foo.emit('bar');
+        expect(bar).toHaveBeenCalledTimes(4);
+        expect(bonk).toHaveBeenCalledTimes(1);
+
+        foo.disconnectAll();
+        bar.calls.reset();
+        bonk.calls.reset();
+
+        // these post-disconnect emissions should do nothing
+        foo.emit('bar');
+        foo.emit('bonk');
+        expect(bar).not.toHaveBeenCalled();
+        expect(bonk).not.toHaveBeenCalled();
+    });
+
+    describe('with exception in signal handler', function () {
+        let bar2;
+        beforeEach(function () {
+            bar.and.throwError('Exception we are throwing on purpose');
+            bar2 = jasmine.createSpy('bar');
+            foo.connect('bar', bar);
+            foo.connect('bar', bar2);
+            GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+                                     'JS ERROR: Exception in callback for signal: *');
+            foo.emit('bar');
+        });
+
+        it('does not affect other callbacks', function () {
+            expect(bar).toHaveBeenCalledTimes(1);
+            expect(bar2).toHaveBeenCalledTimes(1);
+        });
+
+        it('does not disconnect the callback', function () {
+            GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+                                     'JS ERROR: Exception in callback for signal: *');
+            foo.emit('bar');
+            expect(bar).toHaveBeenCalledTimes(2);
+            expect(bar2).toHaveBeenCalledTimes(2);
+        });
+    });
+});
diff --git a/installed-tests/js/testSystem.js b/installed-tests/js/testSystem.js
index 9940d6c..927c715 100644
--- a/installed-tests/js/testSystem.js
+++ b/installed-tests/js/testSystem.js
@@ -1,18 +1,18 @@
-
-const JSUnit = imports.jsUnit;
 const System = imports.system;
 
-function testAddressOf() {
-    let o1 = new Object();
-    let o2 = new Object();
-
-    JSUnit.assert(System.addressOf(o1) == System.addressOf(o1));
-    JSUnit.assert(System.addressOf(o1) != System.addressOf(o2));
-}
-
-function testVersion() {
-    JSUnit.assert(System.version >= 13600);
-}
+describe('System.addressOf()', function () {
+    it('gives the same result for the same object', function () {
+        let o = {};
+        expect(System.addressOf(o)).toEqual(System.addressOf(o));
+    });
 
-JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
+    it('gives different results for different objects', function () {
+        expect(System.addressOf({})).not.toEqual(System.addressOf({}));
+    });
+});
 
+describe('System.version', function () {
+    it('gives a plausible number', function () {
+        expect(System.version).not.toBeLessThan(13600);
+    });
+});
diff --git a/installed-tests/js/testself.js b/installed-tests/js/testself.js
index a07de52..5899ab5 100644
--- a/installed-tests/js/testself.js
+++ b/installed-tests/js/testself.js
@@ -1,37 +1,30 @@
-const JSUnit = imports.jsUnit;
+describe('Test harness internal consistency', function () {
+    it('', function () {
+        var someUndefined;
+        var someNumber = 1;
+        var someOtherNumber = 42;
+        var someString = "hello";
+        var someOtherString = "world";
 
-var someUndefined;
-var someNumber = 1;
-var someOtherNumber = 42;
-var someString = "hello";
-var someOtherString = "world";
+        expect(true).toBeTruthy();
+        expect(false).toBeFalsy();
 
-JSUnit.assert(true);
-JSUnit.assertTrue(true);
-JSUnit.assertFalse(false);
+        expect(someNumber).toEqual(someNumber);
+        expect(someString).toEqual(someString);
 
-JSUnit.assertEquals(someNumber, someNumber);
-JSUnit.assertEquals(someString, someString);
+        expect(someNumber).not.toEqual(someOtherNumber);
+        expect(someString).not.toEqual(someOtherString);
 
-JSUnit.assertNotEquals(someNumber, someOtherNumber);
-JSUnit.assertNotEquals(someString, someOtherString);
+        expect(null).toBeNull();
+        expect(someNumber).not.toBeNull();
+        expect(someNumber).toBeDefined();
+        expect(someUndefined).not.toBeDefined();
+        expect(0 / 0).toBeNaN();
+        expect(someNumber).not.toBeNaN();
 
-JSUnit.assertNull(null);
-JSUnit.assertNotNull(someNumber);
-JSUnit.assertUndefined(someUndefined);
-JSUnit.assertNotUndefined(someNumber);
-JSUnit.assertNaN(0/0);
-JSUnit.assertNotNaN(someNumber);
+        expect(() => { throw {}; }).toThrow();
 
-// test assertRaises()
-JSUnit.assertRaises(function() { throw new Object(); });
-try {   // calling assertRaises with non-function is an error, not assertion failure
-    JSUnit.assertRaises(true);
-} catch(e) {
-    JSUnit.assertUndefined(e.isJsUnitException);
-}
-try {   // function not throwing an exception is assertion failure
-    JSUnit.assertRaises(function() { return true; });
-} catch(e) {
-    JSUnit.assertTrue(e.isJsUnitException);
-}
+        expect(() => expect(true).toThrow()).toThrow();
+        expect(() => true).not.toThrow();
+    });
+});
diff --git a/installed-tests/minijasmine.cpp b/installed-tests/minijasmine.cpp
new file mode 100644
index 0000000..27e6a3d
--- /dev/null
+++ b/installed-tests/minijasmine.cpp
@@ -0,0 +1,143 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2016 Philip Chimento
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <locale.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "gjs/coverage.h"
+#include "gjs/gjs.h"
+#include "gjs/mem.h"
+
+G_GNUC_NORETURN
+void
+bail_out(const char *msg)
+{
+    g_print("Bail out! %s\n", msg);
+    exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+    if (argc < 2)
+        g_error("Need a test file");
+
+    /* The tests are known to fail in the presence of the JIT;
+     * we leak objects.
+     * https://bugzilla.gnome.org/show_bug.cgi?id=616193
+     */
+    g_setenv("GJS_DISABLE_JIT", "1", false);
+    /* The fact that this isn't the default is kind of lame... */
+    g_setenv("GJS_DEBUG_OUTPUT", "stderr", false);
+    /* Jasmine library has some code style nits that trip this */
+    g_setenv("GJS_DISABLE_EXTRA_WARNINGS", "1", false);
+
+    setlocale(LC_ALL, "");
+
+    if (g_getenv ("GJS_USE_UNINSTALLED_FILES") != NULL) {
+        g_irepository_prepend_search_path(g_getenv("TOP_BUILDDIR"));
+    } else {
+        g_irepository_prepend_search_path(INSTTESTDIR);
+    }
+
+    const char *coverage_prefix = g_getenv("GJS_UNIT_COVERAGE_PREFIX");
+    const char *coverage_output_path = g_getenv("GJS_UNIT_COVERAGE_OUTPUT");
+    const char *search_path[] = { "resource:///org/gjs/jsunit", NULL };
+
+    GjsContext *cx = gjs_context_new_with_search_path((char **)search_path);
+    GjsCoverage *coverage = NULL;
+
+    if (coverage_prefix) {
+        const char *coverage_prefixes[2] = { coverage_prefix, NULL };
+
+        if (coverage_output_path) {
+            bail_out("GJS_UNIT_COVERAGE_OUTPUT is required when using GJS_UNIT_COVERAGE_PREFIX");
+        }
+
+        char *path_to_cache_file = g_build_filename(coverage_output_path,
+                                                    ".internal-coverage-cache",
+                                                    NULL);
+        coverage = gjs_coverage_new_from_cache((const char **) coverage_prefixes,
+                                               cx, path_to_cache_file);
+        g_free(path_to_cache_file);
+    }
+
+    GError *error = NULL;
+    bool success;
+    int code;
+
+    success = gjs_context_eval(cx, "imports.minijasmine;", -1,
+                               "<jasmine>", &code, &error);
+    if (!success)
+        bail_out(error->message);
+
+    success = gjs_context_eval_file(cx, argv[1], &code, &error);
+    if (!success)
+        bail_out(error->message);
+
+    /* jasmineEnv.execute() queues up all the tests and runs them
+     * asynchronously. This should start after the main loop starts, otherwise
+     * we will hit the main loop only after several tests have already run. For
+     * consistency we should guarantee that there is a main loop running during
+     * all tests. */
+    const char *start_suite_script =
+        "const GLib = imports.gi.GLib;\n"
+        "GLib.idle_add(GLib.PRIORITY_DEFAULT, function () {\n"
+        "    try {\n"
+        "        window._jasmineEnv.execute();\n"
+        "    } catch (e) {\n"
+        "        print('Bail out! Exception occurred inside Jasmine:', e);\n"
+        "        window._jasmineRetval = 1;\n"
+        "        window._jasmineMain.quit();\n"
+        "    }\n"
+        "    return GLib.SOURCE_REMOVE;\n"
+        "});\n"
+        "window._jasmineMain.run();\n"
+        "window._jasmineRetval;";
+    success = gjs_context_eval(cx, start_suite_script, -1, "<jasmine-start>",
+                               &code, &error);
+    if (!success)
+        bail_out(error->message);
+
+    if (code != 0)
+        g_print("# Test script failed; assertions will be in gjs.log\n");
+
+    if (coverage) {
+        gjs_coverage_write_statistics(coverage, coverage_output_path);
+        g_clear_object(&coverage);
+    }
+
+    gjs_memory_report("before destroying context", false);
+    g_object_unref(cx);
+    gjs_memory_report("after destroying context", true);
+
+    /* For TAP, should actually be return 0; as a nonzero return code would
+     * indicate an error in the test harness. But that would be quite silly
+     * when running the tests outside of the TAP driver. */
+    return code;
+}



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