[gjs/wip/ptomato/tests: 12/12] WIP - minijasmine
- From: Philip Chimento <pchimento src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/wip/ptomato/tests: 12/12] WIP - minijasmine
- Date: Wed, 26 Oct 2016 08:02:13 +0000 (UTC)
commit 732c7a35815d9c645ae225fad4cdf81b5f2cf305
Author: Philip Chimento <philip chimento gmail com>
Date: Wed Oct 26 00:59:41 2016 -0700
WIP - minijasmine
Makefile-insttest.am | 2 +-
Makefile-test.am | 35 +-
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/test0010basic.js | 7 -
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/testself.js | 59 +-
installed-tests/minijasmine.cpp | 143 ++
15 files changed, 5304 insertions(+), 1765 deletions(-)
---
diff --git a/Makefile-insttest.am b/Makefile-insttest.am
index cd53df7..2c179bb 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 testSystemExit.test
jstests_DATA += $(common_jstests_files)
diff --git a/Makefile-test.am b/Makefile-test.am
index 7b7f042..efcc2ba 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -107,7 +107,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) \
@@ -134,23 +134,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 ##########################################################
@@ -243,7 +240,6 @@ CLEANFILES += $(TEST_INTROSPECTION_GIRS) $(TEST_INTROSPECTION_TYPELIBS)
### JAVASCRIPT TESTS ###################################################
common_jstests_files = \
- installed-tests/js/test0010basic.js \
installed-tests/js/test0020importer.js \
installed-tests/js/test0030basicBoxed.js \
installed-tests/js/test0040mainloop.js \
@@ -307,7 +303,13 @@ AM_TESTS_ENVIRONMENT = \
simple_tests = \
test/testCommandLine.sh \
- installed-tests/scripts/testSystemExit.js \
+ installed-tests/js/test0020importer.js \
+ installed-tests/js/test0030basicBoxed.js \
+ installed-tests/js/test0040mainloop.js \
+ installed-tests/js/testself.js \
+ installed-tests/js/testByteArray.js \
+ installed-tests/js/testClass.js \
+ installed-tests/js/testCoverage.js \
$(NULL)
EXTRA_DIST += $(simple_tests)
TESTS += $(simple_tests)
@@ -316,7 +318,8 @@ LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
LOG_COMPILER = $(top_srcdir)/test/run-test
TEST_EXTENSIONS = .js
-JS_LOG_COMPILER = $(top_builddir)/gjs-console
+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 += \
diff --git a/Makefile.am b/Makefile.am
index f0cee34..332196f 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, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+ };
+
+ 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/testself.js b/installed-tests/js/testself.js
index a07de52..91f89a1 100644
--- a/installed-tests/js/testself.js
+++ b/installed-tests/js/testself.js
@@ -1,37 +1,34 @@
-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(function () { 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(function () {
+ // expecting toThrow with non-function is an error, not assertion failure
+ expect(true).toThrow();
+ }).toThrow();
+
+ expect(function () { return 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]