[gjs/ewlsh/implicit-mainloop: 1/2] Implement implicit mainloop.
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/implicit-mainloop: 1/2] Implement implicit mainloop.
- Date: Sat, 30 Jan 2021 20:25:40 +0000 (UTC)
commit d1e28963ee1e6d8827503a9564bc6c5dd22fedda
Author: Evan Welsh <contact evanwelsh com>
Date: Sat Jan 30 12:23:24 2021 -0800
Implement implicit mainloop.
- Upgrade Jasmine for compat
examples/timers.js | 14 +
gjs/context-private.h | 2 +-
gjs/context.cpp | 50 +-
gjs/promise.cpp | 131 +
gjs/promise.h | 12 +
installed-tests/js/jasmine.js | 9035 +++++++++++++++++++++++++++-------
installed-tests/js/meson.build | 1 +
installed-tests/js/minijasmine.js | 21 +-
installed-tests/js/testImporter.js | 12 +
installed-tests/js/testLang.js | 9 +-
installed-tests/js/testMainloop.js | 20 +-
installed-tests/js/testTimers.js | 317 ++
js.gresource.xml | 1 +
meson.build | 1 +
modules/core/_timers.js | 149 +
modules/print.cpp | 31 +
modules/script/_bootstrap/default.js | 31 +
nest.js | 25 +
promise.js | 6 +
19 files changed, 7956 insertions(+), 1912 deletions(-)
---
diff --git a/examples/timers.js b/examples/timers.js
new file mode 100644
index 00000000..62bd82fe
--- /dev/null
+++ b/examples/timers.js
@@ -0,0 +1,14 @@
+const promise = new Promise(r => {
+ let i = 100
+ while (i--) { }
+ r()
+})
+
+setTimeout(() => {
+ promise.then(() => log('no'))
+})
+
+setTimeout(() =>
+{
+ log('de')
+})
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 4f5adc8a..3df22d03 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -82,7 +82,7 @@ class GjsContextPrivate : public JS::JobQueue {
GjsAtoms* m_atoms;
JobQueueStorage m_job_queue;
- unsigned m_idle_drain_handler;
+ GSource* m_promise_queue_source;
std::unordered_map<uint64_t, GjsAutoChar> m_unhandled_rejection_stacks;
diff --git a/gjs/context.cpp b/gjs/context.cpp
index f302409c..79c4171e 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -67,6 +67,7 @@
#include "gjs/native.h"
#include "gjs/profiler-private.h"
#include "gjs/profiler.h"
+#include "gjs/promise.h"
#include "modules/modules.h"
#include "util/log.h"
@@ -286,6 +287,8 @@ gjs_context_class_init(GjsContextClass *klass)
g_free (priv_typelib_dir);
}
+ gjs_register_native_module("_promiseNative",
+ gjs_define_native_promise_stuff);
gjs_register_native_module("_byteArrayNative", gjs_define_byte_array_stuff);
gjs_register_native_module("_gi", gjs_define_private_gi_stuff);
gjs_register_native_module("gi", gjs_define_repo);
@@ -644,16 +647,17 @@ bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const {
}
void GjsContextPrivate::start_draining_job_queue(void) {
- if (!m_idle_drain_handler)
- m_idle_drain_handler = g_idle_add_full(
- G_PRIORITY_DEFAULT, drain_job_queue_idle_handler, this, nullptr);
+ if (!m_promise_queue_source) {
+ m_promise_queue_source =
+ gjs_promise_queue_source_new(this, nullptr, nullptr);
+ g_source_attach(m_promise_queue_source, nullptr);
+ }
}
void GjsContextPrivate::stop_draining_job_queue(void) {
- m_draining_job_queue = false;
- if (m_idle_drain_handler) {
- g_source_remove(m_idle_drain_handler);
- m_idle_drain_handler = 0;
+ if (m_promise_queue_source) {
+ g_source_destroy(m_promise_queue_source);
+ m_promise_queue_source = nullptr;
}
}
@@ -662,7 +666,7 @@ gboolean GjsContextPrivate::drain_job_queue_idle_handler(void* data) {
gjs->runJobs(gjs->context());
/* Uncatchable exceptions are swallowed here - no way to get a handle on
* the main loop to exit it from this idle handler */
- g_assert(gjs->empty() && gjs->m_idle_drain_handler == 0 &&
+ g_assert(gjs->empty() && gjs->m_promise_queue_source != nullptr &&
"GjsContextPrivate::runJobs() should have emptied queue");
return G_SOURCE_REMOVE;
}
@@ -672,6 +676,12 @@ JSObject* GjsContextPrivate::getIncumbentGlobal(JSContext* cx) {
return JS::CurrentGlobalOrNull(cx);
}
+typedef struct {
+ GSource parent;
+ GAsyncQueue* queue; /* owned */
+ GDestroyNotify destroy_message;
+} PromiseQueueSource;
+
/* See engine.cpp and JS::SetEnqueuePromiseJobCallback(). */
bool GjsContextPrivate::enqueuePromiseJob(
JSContext* cx [[maybe_unused]], JS::HandleObject promise [[maybe_unused]],
@@ -680,17 +690,16 @@ bool GjsContextPrivate::enqueuePromiseJob(
g_assert(cx == m_cx);
g_assert(from_cx(cx) == this);
- if (m_idle_drain_handler)
- g_assert(!empty());
- else
- g_assert(empty());
+ // if (m_promise_queue_handle)
+ // g_assert(!empty());
+ // else
+ // g_assert(empty());
if (!m_job_queue.append(job)) {
JS_ReportOutOfMemory(m_cx);
return false;
}
- start_draining_job_queue();
return true;
}
@@ -766,8 +775,8 @@ bool GjsContextPrivate::run_jobs_fallible(void) {
}
}
+ m_draining_job_queue = false;
m_job_queue.clear();
- stop_draining_job_queue();
return retval;
}
@@ -928,15 +937,22 @@ bool GjsContextPrivate::eval(const char* script, ssize_t script_len,
if (auto_profile)
gjs_profiler_start(m_profiler);
+ start_draining_job_queue();
+
JS::RootedValue retval(m_cx);
bool ok = eval_with_scope(nullptr, script, script_len, filename, &retval);
+ while (!m_should_exit && (g_main_context_iteration(nullptr, false))) {
+ }
+
/* The promise job queue should be drained even on error, to finish
* outstanding async tasks before the context is torn down. Drain after
* uncaught exceptions have been reported since draining runs callbacks. */
{
JS::AutoSaveExceptionState saved_exc(m_cx);
ok = run_jobs_fallible() && ok;
+
+ stop_draining_job_queue();
}
if (auto_profile)
@@ -971,13 +987,17 @@ bool GjsContextPrivate::eval(const char* script, ssize_t script_len,
}
if (exit_status_p) {
+ uint8_t code;
if (retval.isInt32()) {
int code = retval.toInt32();
gjs_debug(GJS_DEBUG_CONTEXT,
"Script returned integer code %d", code);
*exit_status_p = code;
+ } else if (should_exit(&code)) {
+ *exit_status_p = code;
} else {
- /* Assume success if no integer was returned */
+ /* Assume success if no integer was returned and should exit isn't
+ * set */
*exit_status_p = 0;
}
}
diff --git a/gjs/promise.cpp b/gjs/promise.cpp
new file mode 100644
index 00000000..e26b918f
--- /dev/null
+++ b/gjs/promise.cpp
@@ -0,0 +1,131 @@
+#include "promise.h"
+#include "gjs/context-private.h"
+
+#include <js/ArrayBuffer.h>
+#include <js/CallArgs.h>
+#include <js/GCAPI.h> // for AutoCheckCannotGC
+#include <js/PropertySpec.h>
+#include <js/RootingAPI.h>
+#include <js/TypeDecls.h>
+#include <js/Utility.h> // for UniqueChars
+#include <jsapi.h> // for JS_DefineFunctionById, JS_DefineFun...
+#include <jsfriendapi.h> // for JS_NewUint8ArrayWithBuffer, GetUint...
+
+typedef struct {
+ GSource parent;
+ bool dispatching;
+ GjsContextPrivate* cx;
+ GDestroyNotify destroy_message;
+} PromiseQueueSource;
+
+static gboolean promise_queue_source_prepare(GSource* source,
+ gint* timeout [[maybe_unused]]) {
+ PromiseQueueSource* promise_queue_source = (PromiseQueueSource*)source;
+
+ auto* cx = promise_queue_source->cx;
+
+ if (!promise_queue_source->dispatching && cx != nullptr && !cx->empty()) {
+ promise_queue_source->dispatching = true;
+ return true;
+ }
+
+ return false;
+}
+
+static gboolean promise_queue_source_dispatch(GSource* source,
+ GSourceFunc callback
+ [[maybe_unused]],
+ gpointer data [[maybe_unused]]) {
+ PromiseQueueSource* promise_queue_source = (PromiseQueueSource*)source;
+
+ auto* cx = promise_queue_source->cx;
+
+ if (!cx || cx->empty()) {
+ printf("emptied!\n");
+ promise_queue_source->dispatching = false;
+ // TODO: This will keep us "always" update next - not ideal.
+ // g_source_set_ready_time(source, 0);
+
+ return true;
+ }
+
+ if (cx->context()) {
+ cx->runJobs(cx->context());
+ }
+
+ promise_queue_source->dispatching = false;
+
+ return true;
+}
+
+static void promise_queue_source_finalize(GSource* source) {
+ PromiseQueueSource* promise_queue_source = (PromiseQueueSource*)source;
+
+ promise_queue_source->cx = nullptr;
+}
+
+static GSourceFuncs promise_queue_source_funcs = {
+ promise_queue_source_prepare,
+ nullptr, /* check */
+ promise_queue_source_dispatch,
+ promise_queue_source_finalize,
+ nullptr,
+ nullptr,
+};
+
+GSource* gjs_promise_queue_source_new(GjsContextPrivate* cx,
+ GDestroyNotify destroy_message,
+ GCancellable* cancellable) {
+ GSource* source;
+ PromiseQueueSource* promise_queue_source;
+
+ g_return_val_if_fail(cx != nullptr, nullptr);
+ g_return_val_if_fail(
+ cancellable == nullptr || G_IS_CANCELLABLE(cancellable), nullptr);
+
+ source =
+ g_source_new(&promise_queue_source_funcs, sizeof(PromiseQueueSource));
+ g_source_set_priority(source, -1000);
+ // TODO: Do we need this?
+ // g_source_set_can_recurse(source, true);
+ promise_queue_source = (PromiseQueueSource*)source;
+
+ g_source_set_name(source, "PromiseQueueSource");
+
+ promise_queue_source->cx = cx;
+ promise_queue_source->dispatching = false;
+ promise_queue_source->destroy_message = destroy_message;
+
+ /* Add a cancellable source. */
+ if (cancellable != nullptr) {
+ GSource* cancellable_source;
+
+ cancellable_source = g_cancellable_source_new(cancellable);
+ g_source_set_dummy_callback(cancellable_source);
+ g_source_add_child_source(source, cancellable_source);
+ g_source_unref(cancellable_source);
+ }
+
+ return source;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool run_func(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
+
+ gjs->runJobs(cx);
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static JSFunctionSpec gjs_native_promise_module_funcs[] = {
+ JS_FN("run", run_func, 2, 0), JS_FS_END};
+
+bool gjs_define_native_promise_stuff(JSContext* cx,
+ JS::MutableHandleObject module) {
+ module.set(JS_NewPlainObject(cx));
+ return JS_DefineFunctions(cx, module, gjs_native_promise_module_funcs);
+}
diff --git a/gjs/promise.h b/gjs/promise.h
new file mode 100644
index 00000000..67bc8d1c
--- /dev/null
+++ b/gjs/promise.h
@@ -0,0 +1,12 @@
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "gjs/context-private.h"
+#include <js/TypeDecls.h>
+
+GSource* gjs_promise_queue_source_new(GjsContextPrivate* cx,
+ GDestroyNotify destroy_message,
+ GCancellable* cancellable);
+
+bool
+gjs_define_native_promise_stuff(JSContext *cx, JS::MutableHandleObject module);
\ No newline at end of file
diff --git a/installed-tests/js/jasmine.js b/installed-tests/js/jasmine.js
index 35bcf39c..557a0460 100644
--- a/installed-tests/js/jasmine.js
+++ b/installed-tests/js/jasmine.js
@@ -1,10 +1,14 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2008-2016 Pivotal Labs
-var getJasmineRequireObj = (function (jasmineGlobal) {
+var getJasmineRequireObj = (function(jasmineGlobal) {
var jasmineRequire;
- if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined') {
+ if (
+ typeof module !== 'undefined' &&
+ module.exports &&
+ typeof exports !== 'undefined'
+ ) {
if (typeof global !== 'undefined') {
jasmineGlobal = global;
} else {
@@ -12,10 +16,14 @@ var getJasmineRequireObj = (function (jasmineGlobal) {
}
jasmineRequire = exports;
} else {
- if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() ===
'[object GjsGlobal]') {
+ if (
+ typeof window !== 'undefined' &&
+ typeof window.toString === 'function' &&
+ window.toString() === '[object GjsGlobal]'
+ ) {
jasmineGlobal = window;
}
- jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
+ jasmineRequire = jasmineGlobal.jasmineRequire = {};
}
function getJasmineRequire() {
@@ -26,37 +34,67 @@ var getJasmineRequireObj = (function (jasmineGlobal) {
var j$ = {};
jRequire.base(j$, jasmineGlobal);
- j$.util = jRequire.util();
+ j$.util = jRequire.util(j$);
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$.getClearStack = jRequire.clearStack(j$);
j$.Clock = jRequire.Clock();
- j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+ j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(j$);
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$.StackTrace = jRequire.StackTrace(j$);
+ j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$);
+ j$.ExpectationFilterChain = jRequire.ExpectationFilterChain();
+ j$.Expector = jRequire.Expector(j$);
+ j$.Expectation = jRequire.Expectation(j$);
+ j$.buildExpectationResult = jRequire.buildExpectationResult(j$);
+ j$.JsApiReporter = jRequire.JsApiReporter(j$);
+ j$.asymmetricEqualityTesterArgCompatShim = jRequire.asymmetricEqualityTesterArgCompatShim(
+ j$
+ );
+ j$.makePrettyPrinter = jRequire.makePrettyPrinter(j$);
+ j$.pp = j$.makePrettyPrinter();
+ j$.MatchersUtil = jRequire.MatchersUtil(j$);
+ j$.matchersUtil = new j$.MatchersUtil({
+ customTesters: [],
+ pp: j$.pp
+ });
+
j$.ObjectContaining = jRequire.ObjectContaining(j$);
j$.ArrayContaining = jRequire.ArrayContaining(j$);
- j$.pp = jRequire.pp(j$);
+ j$.ArrayWithExactContents = jRequire.ArrayWithExactContents(j$);
+ j$.MapContaining = jRequire.MapContaining(j$);
+ j$.SetContaining = jRequire.SetContaining(j$);
j$.QueueRunner = jRequire.QueueRunner(j$);
- j$.ReportDispatcher = jRequire.ReportDispatcher();
+ j$.ReportDispatcher = jRequire.ReportDispatcher(j$);
j$.Spec = jRequire.Spec(j$);
+ j$.Spy = jRequire.Spy(j$);
+ j$.SpyFactory = jRequire.SpyFactory(j$);
j$.SpyRegistry = jRequire.SpyRegistry(j$);
j$.SpyStrategy = jRequire.SpyStrategy(j$);
j$.StringMatching = jRequire.StringMatching(j$);
+ j$.UserContext = jRequire.UserContext(j$);
j$.Suite = jRequire.Suite(j$);
j$.Timer = jRequire.Timer();
j$.TreeProcessor = jRequire.TreeProcessor();
j$.version = jRequire.version();
j$.Order = jRequire.Order();
+ j$.DiffBuilder = jRequire.DiffBuilder(j$);
+ j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$);
+ j$.ObjectPath = jRequire.ObjectPath(j$);
+ j$.MismatchTree = jRequire.MismatchTree(j$);
+ j$.GlobalErrors = jRequire.GlobalErrors(j$);
+
+ j$.Truthy = jRequire.Truthy(j$);
+ j$.Falsy = jRequire.Falsy(j$);
+ j$.Empty = jRequire.Empty(j$);
+ j$.NotEmpty = jRequire.NotEmpty(j$);
j$.matchers = jRequire.requireMatchers(jRequire, j$);
+ j$.asyncMatchers = jRequire.requireAsyncMatchers(jRequire, j$);
return j$;
};
@@ -66,26 +104,37 @@ var getJasmineRequireObj = (function (jasmineGlobal) {
getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
var availableMatchers = [
+ 'nothing',
'toBe',
'toBeCloseTo',
'toBeDefined',
+ 'toBeInstanceOf',
+ 'toBeFalse',
'toBeFalsy',
'toBeGreaterThan',
'toBeGreaterThanOrEqual',
- 'toBeLessThanOrEqual',
'toBeLessThan',
+ 'toBeLessThanOrEqual',
'toBeNaN',
+ 'toBeNegativeInfinity',
'toBeNull',
+ 'toBePositiveInfinity',
+ 'toBeTrue',
'toBeTruthy',
'toBeUndefined',
'toContain',
'toEqual',
+ 'toHaveSize',
'toHaveBeenCalled',
- 'toHaveBeenCalledWith',
+ 'toHaveBeenCalledBefore',
+ 'toHaveBeenCalledOnceWith',
'toHaveBeenCalledTimes',
+ 'toHaveBeenCalledWith',
+ 'toHaveClass',
'toMatch',
'toThrow',
- 'toThrowError'
+ 'toThrowError',
+ 'toThrowMatching'
],
matchers = {};
@@ -102,16 +151,49 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
throw new Error('unimplemented method');
};
- j$.MAX_PRETTY_PRINT_DEPTH = 40;
- j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
+ /**
+ * Maximum object depth the pretty printer will print to.
+ * Set this to a lower value to speed up pretty printing if you have large objects.
+ * @name jasmine.MAX_PRETTY_PRINT_DEPTH
+ * @since 1.3.0
+ */
+ j$.MAX_PRETTY_PRINT_DEPTH = 8;
+ /**
+ * Maximum number of array elements to display when pretty printing objects.
+ * This will also limit the number of keys and values displayed for an object.
+ * Elements past this number will be ellipised.
+ * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH
+ * @since 2.7.0
+ */
+ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 50;
+ /**
+ * Maximum number of characters to display when pretty printing objects.
+ * Characters past this number will be ellipised.
+ * @name jasmine.MAX_PRETTY_PRINT_CHARS
+ * @since 2.9.0
+ */
+ j$.MAX_PRETTY_PRINT_CHARS = 1000;
+ /**
+ * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete.
+ * @name jasmine.DEFAULT_TIMEOUT_INTERVAL
+ * @since 1.3.0
+ */
j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
j$.getGlobal = function() {
return jasmineGlobal;
};
+ /**
+ * Get the currently booted Jasmine Environment.
+ *
+ * @name jasmine.getEnv
+ * @since 1.3.0
+ * @function
+ * @return {Env}
+ */
j$.getEnv = function(options) {
- var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+ var env = (j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options));
//jasmine. singletons in here (setTimeout blah blah).
return env;
};
@@ -120,6 +202,12 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
return j$.isA_('Array', value);
};
+ j$.isObject_ = function(value) {
+ return (
+ !j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value)
+ );
+ };
+
j$.isString_ = function(value) {
return j$.isA_('String', value);
};
@@ -132,12 +220,110 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
return j$.isA_('Function', value);
};
+ j$.isAsyncFunction_ = function(value) {
+ return j$.isA_('AsyncFunction', value);
+ };
+
+ j$.isTypedArray_ = function(value) {
+ return (
+ j$.isA_('Float32Array', value) ||
+ j$.isA_('Float64Array', value) ||
+ j$.isA_('Int16Array', value) ||
+ j$.isA_('Int32Array', value) ||
+ j$.isA_('Int8Array', value) ||
+ j$.isA_('Uint16Array', value) ||
+ j$.isA_('Uint32Array', value) ||
+ j$.isA_('Uint8Array', value) ||
+ j$.isA_('Uint8ClampedArray', value)
+ );
+ };
+
j$.isA_ = function(typeName, value) {
- return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+ return j$.getType_(value) === '[object ' + typeName + ']';
+ };
+
+ j$.isError_ = function(value) {
+ if (value instanceof Error) {
+ return true;
+ }
+ if (value && value.constructor && value.constructor.constructor) {
+ var valueGlobal = value.constructor.constructor('return this');
+ if (j$.isFunction_(valueGlobal)) {
+ valueGlobal = valueGlobal();
+ }
+
+ if (valueGlobal.Error && value instanceof valueGlobal.Error) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ j$.isAsymmetricEqualityTester_ = function(obj) {
+ return obj ? j$.isA_('Function', obj.asymmetricMatch) : false;
+ };
+
+ j$.getType_ = function(value) {
+ return Object.prototype.toString.apply(value);
};
j$.isDomNode = function(obj) {
- return obj.nodeType > 0;
+ // Node is a function, because constructors
+ return typeof jasmineGlobal.Node !== 'undefined'
+ ? obj instanceof jasmineGlobal.Node
+ : obj !== null &&
+ typeof obj === 'object' &&
+ typeof obj.nodeType === 'number' &&
+ typeof obj.nodeName === 'string';
+ // return obj.nodeType > 0;
+ };
+
+ j$.isMap = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ typeof jasmineGlobal.Map !== 'undefined' &&
+ obj.constructor === jasmineGlobal.Map
+ );
+ };
+
+ j$.isSet = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ typeof jasmineGlobal.Set !== 'undefined' &&
+ obj.constructor === jasmineGlobal.Set
+ );
+ };
+
+ j$.isWeakMap = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ typeof jasmineGlobal.WeakMap !== 'undefined' &&
+ obj.constructor === jasmineGlobal.WeakMap
+ );
+ };
+
+ j$.isDataView = function(obj) {
+ return (
+ obj !== null &&
+ typeof obj !== 'undefined' &&
+ typeof jasmineGlobal.DataView !== 'undefined' &&
+ obj.constructor === jasmineGlobal.DataView
+ );
+ };
+
+ j$.isPromise = function(obj) {
+ return (
+ typeof jasmineGlobal.Promise !== 'undefined' &&
+ !!obj &&
+ obj.constructor === jasmineGlobal.Promise
+ );
+ };
+
+ j$.isPromiseLike = function(obj) {
+ return !!obj && j$.isFunction_(obj.then);
};
j$.fnNameFor = function(func) {
@@ -145,97 +331,170 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
return func.name;
}
- var matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/);
+ var matches =
+ func.toString().match(/^\s*function\s*(\w+)\s*\(/) ||
+ func.toString().match(/^\s*\[object\s*(\w+)Constructor\]/);
+
return matches ? matches[1] : '<anonymous>';
};
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value being compared is an instance of the specified class/constructor.
+ * @name jasmine.any
+ * @since 1.3.0
+ * @function
+ * @param {Constructor} clazz - The constructor to check against.
+ */
j$.any = function(clazz) {
return new j$.Any(clazz);
};
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value being compared is not `null` and not `undefined`.
+ * @name jasmine.anything
+ * @since 2.2.0
+ * @function
+ */
j$.anything = function() {
return new j$.Anything();
};
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value being compared is `true` or anything truthy.
+ * @name jasmine.truthy
+ * @since 3.1.0
+ * @function
+ */
+ j$.truthy = function() {
+ return new j$.Truthy();
+ };
+
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything
falsey.
+ * @name jasmine.falsy
+ * @since 3.1.0
+ * @function
+ */
+ j$.falsy = function() {
+ return new j$.Falsy();
+ };
+
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value being compared is empty.
+ * @name jasmine.empty
+ * @since 3.1.0
+ * @function
+ */
+ j$.empty = function() {
+ return new j$.Empty();
+ };
+
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value being compared is not empty.
+ * @name jasmine.notEmpty
+ * @since 3.1.0
+ * @function
+ */
+ j$.notEmpty = function() {
+ return new j$.NotEmpty();
+ };
+
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value being compared contains at least the keys and values.
+ * @name jasmine.objectContaining
+ * @since 1.3.0
+ * @function
+ * @param {Object} sample - The subset of properties that _must_ be in the actual.
+ */
j$.objectContaining = function(sample) {
return new j$.ObjectContaining(sample);
};
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value is a `String` that matches the `RegExp` or `String`.
+ * @name jasmine.stringMatching
+ * @since 2.2.0
+ * @function
+ * @param {RegExp|String} expected
+ */
j$.stringMatching = function(expected) {
return new j$.StringMatching(expected);
};
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value is an `Array` that contains at least the elements in the sample.
+ * @name jasmine.arrayContaining
+ * @since 2.2.0
+ * @function
+ * @param {Array} sample
+ */
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];
- }
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if the actual value is an `Array` that contains all of the elements in the sample in
any order.
+ * @name jasmine.arrayWithExactContents
+ * @since 2.8.0
+ * @function
+ * @param {Array} sample
+ */
+ j$.arrayWithExactContents = function(sample) {
+ return new j$.ArrayWithExactContents(sample);
+ };
- spy.and = spyStrategy;
- spy.calls = callTracker;
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if every key/value pair in the sample passes the deep equality comparison
+ * with at least one key/value pair in the actual value being compared
+ * @name jasmine.mapContaining
+ * @since 3.5.0
+ * @function
+ * @param {Map} sample - The subset of items that _must_ be in the actual.
+ */
+ j$.mapContaining = function(sample) {
+ return new j$.MapContaining(sample);
+ };
- return spy;
+ /**
+ * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link
matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link
matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}),
+ * that will succeed if every item in the sample passes the deep equality comparison
+ * with at least one item in the actual value being compared
+ * @name jasmine.setContaining
+ * @since 3.5.0
+ * @function
+ * @param {Set} sample - The subset of items that _must_ be in the actual.
+ */
+ j$.setContaining = function(sample) {
+ return new j$.SetContaining(sample);
};
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;
+ return (
+ putativeSpy.and instanceof j$.SpyStrategy &&
+ putativeSpy.calls instanceof j$.CallTracker
+ );
};
};
-getJasmineRequireObj().util = function() {
-
+getJasmineRequireObj().util = function(j$) {
var util = {};
util.inherit = function(childClass, parentClass) {
- var Subclass = function() {
- };
+ var Subclass = function() {};
Subclass.prototype = parentClass.prototype;
childClass.prototype = new Subclass();
};
@@ -244,7 +503,8 @@ getJasmineRequireObj().util = function() {
if (!str) {
return str;
}
- return str.replace(/&/g, '&')
+ return str
+ .replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
};
@@ -286,36 +546,169 @@ getJasmineRequireObj().util = function() {
return cloned;
};
+ util.cloneArgs = function(args) {
+ var clonedArgs = [];
+ var argsAsArray = j$.util.argsToArray(args);
+ for (var i = 0; i < argsAsArray.length; i++) {
+ var str = Object.prototype.toString.apply(argsAsArray[i]),
+ primitives = /^\[object (Boolean|String|RegExp|Number)/;
+
+ // All falsey values are either primitives, `null`, or `undefined.
+ if (!argsAsArray[i] || str.match(primitives)) {
+ clonedArgs.push(argsAsArray[i]);
+ } else {
+ clonedArgs.push(j$.util.clone(argsAsArray[i]));
+ }
+ }
+ return clonedArgs;
+ };
+
+ util.getPropertyDescriptor = function(obj, methodName) {
+ var descriptor,
+ proto = obj;
+
+ do {
+ descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
+ proto = Object.getPrototypeOf(proto);
+ } while (!descriptor && proto);
+
+ return descriptor;
+ };
+
+ util.objectDifference = function(obj, toRemove) {
+ var diff = {};
+
+ for (var key in obj) {
+ if (util.has(obj, key) && !util.has(toRemove, key)) {
+ diff[key] = obj[key];
+ }
+ }
+
+ return diff;
+ };
+
+ util.has = function(obj, key) {
+ return Object.prototype.hasOwnProperty.call(obj, key);
+ };
+
+ util.errorWithStack = function errorWithStack() {
+ // Don't throw and catch if we don't have to, because it makes it harder
+ // for users to debug their code with exception breakpoints.
+ var error = new Error();
+
+ if (error.stack) {
+ return error;
+ }
+
+ // But some browsers (e.g. Phantom) only provide a stack trace if we throw.
+ try {
+ throw new Error();
+ } catch (e) {
+ return e;
+ }
+ };
+
+ function callerFile() {
+ var trace = new j$.StackTrace(util.errorWithStack());
+ return trace.frames[2].file;
+ }
+
+ util.jasmineFile = (function() {
+ var result;
+
+ return function() {
+ if (!result) {
+ result = callerFile();
+ }
+
+ return result;
+ };
+ })();
+
+ function StopIteration() {}
+ StopIteration.prototype = Object.create(Error.prototype);
+ StopIteration.prototype.constructor = StopIteration;
+
+ // useful for maps and sets since `forEach` is the only IE11-compatible way to iterate them
+ util.forEachBreakable = function(iterable, iteratee) {
+ function breakLoop() {
+ throw new StopIteration();
+ }
+
+ try {
+ iterable.forEach(function(value, key) {
+ iteratee(breakLoop, value, key, iterable);
+ });
+ } catch (error) {
+ if (!(error instanceof StopIteration)) throw error;
+ }
+ };
+
return util;
};
getJasmineRequireObj().Spec = function(j$) {
function Spec(attrs) {
this.expectationFactory = attrs.expectationFactory;
+ this.asyncExpectationFactory = attrs.asyncExpectationFactory;
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.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.getSpecName =
+ attrs.getSpecName ||
+ function() {
+ return '';
+ };
+ this.expectationResultFactory =
+ attrs.expectationResultFactory || function() {};
this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
- this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+ this.catchingExceptions =
+ attrs.catchingExceptions ||
+ function() {
+ return true;
+ };
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+ this.timer = attrs.timer || new j$.Timer();
if (!this.queueableFn.fn) {
this.pend();
}
+ /**
+ * @typedef SpecResult
+ * @property {Int} id - The unique id of this spec.
+ * @property {String} description - The description passed to the {@link it} that created this spec.
+ * @property {String} fullName - The full description including all ancestors of this spec.
+ * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution
of this spec.
+ * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution
of this spec.
+ * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred during
execution this spec.
+ * @property {String} pendingReason - If the spec is {@link pending}, this will be the reason.
+ * @property {String} status - Once the spec has completed, this string represents the pass/fail status
of this spec.
+ * @property {number} duration - The time in ms used by the spec execution, including any
before/afterEach.
+ * @property {Object} properties - User-supplied properties, if any, that were set using {@link
Env#setSpecProperty}
+ */
this.result = {
id: this.id,
description: this.description,
fullName: this.getFullName(),
failedExpectations: [],
passedExpectations: [],
- pendingReason: ''
+ deprecationWarnings: [],
+ pendingReason: '',
+ duration: null,
+ properties: null
};
}
@@ -332,38 +725,66 @@ getJasmineRequireObj().Spec = function(j$) {
}
};
+ Spec.prototype.setSpecProperty = function(key, value) {
+ this.result.properties = this.result.properties || {};
+ this.result.properties[key] = value;
+ };
+
Spec.prototype.expect = function(actual) {
return this.expectationFactory(actual, this);
};
- Spec.prototype.execute = function(onComplete, enabled) {
+ Spec.prototype.expectAsync = function(actual) {
+ return this.asyncExpectationFactory(actual, this);
+ };
+
+ Spec.prototype.execute = function(onComplete, excluded, failSpecWithNoExp) {
var self = this;
- this.onStart(this);
+ var onStart = {
+ fn: function(done) {
+ self.timer.start();
+ self.onStart(self, done);
+ }
+ };
- if (!this.isExecutable() || this.markedPending || enabled === false) {
- complete(enabled);
- return;
- }
+ var complete = {
+ fn: function(done) {
+ self.queueableFn.fn = null;
+ self.result.status = self.status(excluded, failSpecWithNoExp);
+ self.result.duration = self.timer.elapsed();
+ self.resultCallback(self.result, done);
+ }
+ };
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,
+ var regularFns = fns.befores.concat(this.queueableFn);
+
+ var runnerConfig = {
+ isLeaf: true,
+ queueableFns: regularFns,
+ cleanupFns: fns.afters,
+ onException: function() {
+ self.onException.apply(self, arguments);
+ },
+ onComplete: function() {
+ onComplete(
+ self.result.status === 'failed' &&
+ new j$.StopExecutionError('spec failed')
+ );
+ },
userContext: this.userContext()
- });
-
- function complete(enabledAgain) {
- self.result.status = self.status(enabledAgain);
- self.resultCallback(self.result);
+ };
- if (onComplete) {
- onComplete();
- }
+ if (this.markedPending || excluded === true) {
+ runnerConfig.queueableFns = [];
+ runnerConfig.cleanupFns = [];
}
+
+ runnerConfig.queueableFns.unshift(onStart);
+ runnerConfig.cleanupFns.push(complete);
+
+ this.queueRunnerFactory(runnerConfig);
};
Spec.prototype.onException = function onException(e) {
@@ -376,17 +797,17 @@ getJasmineRequireObj().Spec = function(j$) {
return;
}
- this.addExpectationResult(false, {
- matcherName: '',
- passed: false,
- expected: '',
- actual: '',
- error: e
- }, true);
- };
-
- Spec.prototype.disable = function() {
- this.disabled = true;
+ this.addExpectationResult(
+ false,
+ {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: e
+ },
+ true
+ );
};
Spec.prototype.pend = function(message) {
@@ -401,34 +822,46 @@ getJasmineRequireObj().Spec = function(j$) {
return this.result;
};
- Spec.prototype.status = function(enabled) {
- if (this.disabled || enabled === false) {
- return 'disabled';
+ Spec.prototype.status = function(excluded, failSpecWithNoExpectations) {
+ if (excluded === true) {
+ return 'excluded';
}
if (this.markedPending) {
return 'pending';
}
- if (this.result.failedExpectations.length > 0) {
+ if (
+ this.result.failedExpectations.length > 0 ||
+ (failSpecWithNoExpectations &&
+ this.result.failedExpectations.length +
+ this.result.passedExpectations.length ===
+ 0)
+ ) {
return 'failed';
- } else {
- return 'passed';
}
- };
- Spec.prototype.isExecutable = function() {
- return !this.disabled;
+ return 'passed';
};
Spec.prototype.getFullName = function() {
return this.getSpecName(this);
};
+ Spec.prototype.addDeprecationWarning = function(deprecation) {
+ if (typeof deprecation === 'string') {
+ deprecation = { message: deprecation };
+ }
+ this.result.deprecationWarnings.push(
+ this.expectationResultFactory(deprecation)
+ );
+ };
+
var extractCustomPendingMessage = function(e) {
var fullMessage = e.toString(),
- boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
- boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length;
+ boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
+ boilerplateEnd =
+ boilerplateStart + Spec.pendingSpecExceptionMessage.length;
return fullMessage.substr(boilerplateEnd);
};
@@ -436,13 +869,18 @@ getJasmineRequireObj().Spec = function(j$) {
Spec.pendingSpecExceptionMessage = '=> marked Pending';
Spec.isPendingSpecException = function(e) {
- return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1);
+ return !!(
+ e &&
+ e.toString &&
+ e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1
+ );
};
return Spec;
};
if (typeof window == void 0 && typeof exports == 'object') {
+ /* globals exports */
exports.Spec = jasmineRequire.Spec;
}
@@ -451,7 +889,7 @@ if (typeof window == void 0 && typeof exports == 'object') {
getJasmineRequireObj().Order = function() {
function Order(options) {
this.random = 'random' in options ? options.random : true;
- var seed = this.seed = options.seed || generateSeed();
+ var seed = (this.seed = options.seed || generateSeed());
this.sort = this.random ? randomOrder : naturalOrder;
function naturalOrder(items) {
@@ -477,45 +915,136 @@ getJasmineRequireObj().Order = function() {
function jenkinsHash(key) {
var hash, i;
- for(hash = i = 0; i < key.length; ++i) {
+ for (hash = i = 0; i < key.length; ++i) {
hash += key.charCodeAt(i);
- hash += (hash << 10);
- hash ^= (hash >> 6);
+ hash += hash << 10;
+ hash ^= hash >> 6;
}
- hash += (hash << 3);
- hash ^= (hash >> 11);
- hash += (hash << 15);
+ hash += hash << 3;
+ hash ^= hash >> 11;
+ hash += hash << 15;
return hash;
}
-
}
return Order;
};
getJasmineRequireObj().Env = function(j$) {
+ /**
+ * _Note:_ Do not construct this directly, Jasmine will make one during booting.
+ * @name Env
+ * @since 2.0.0
+ * @classdesc The Jasmine environment
+ * @constructor
+ */
function Env(options) {
options = options || {};
var self = this;
var global = options.global || j$.getGlobal();
+ var customPromise;
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 realSetTimeout = global.setTimeout;
+ var realClearTimeout = global.clearTimeout;
+ var clearStack = j$.getClearStack(global);
+ 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 hasFailures = false;
+
+ /**
+ * This represents the available options to configure Jasmine.
+ * Options that are not provided will use their default values
+ * @interface Configuration
+ * @since 3.3.0
+ */
+ var config = {
+ /**
+ * Whether to randomize spec execution order
+ * @name Configuration#random
+ * @since 3.3.0
+ * @type Boolean
+ * @default true
+ */
+ random: true,
+ /**
+ * Seed to use as the basis of randomization.
+ * Null causes the seed to be determined randomly at the start of execution.
+ * @name Configuration#seed
+ * @since 3.3.0
+ * @type function
+ * @default null
+ */
+ seed: null,
+ /**
+ * Whether to stop execution of the suite after the first spec failure
+ * @name Configuration#failFast
+ * @since 3.3.0
+ * @type Boolean
+ * @default false
+ */
+ failFast: false,
+ /**
+ * Whether to fail the spec if it ran no expectations. By default
+ * a spec that ran no expectations is reported as passed. Setting this
+ * to true will report such spec as a failure.
+ * @name Configuration#failSpecWithNoExpectations
+ * @since 3.5.0
+ * @type Boolean
+ * @default false
+ */
+ failSpecWithNoExpectations: false,
+ /**
+ * Whether to cause specs to only have one expectation failure.
+ * @name Configuration#oneFailurePerSpec
+ * @since 3.3.0
+ * @type Boolean
+ * @default false
+ */
+ oneFailurePerSpec: false,
+ /**
+ * Function to use to filter specs
+ * @name Configuration#specFilter
+ * @since 3.3.0
+ * @type function
+ * @default true
+ */
+ specFilter: function() {
+ return true;
+ },
+ /**
+ * Whether or not reporters should hide disabled specs from their output.
+ * Currently only supported by Jasmine's HTMLReporter
+ * @name Configuration#hideDisabled
+ * @since 3.3.0
+ * @type Boolean
+ * @default false
+ */
+ hideDisabled: false,
+ /**
+ * Set to provide a custom promise library that Jasmine will use if it needs
+ * to create a promise. If not set, it will default to whatever global Promise
+ * library is available (if any).
+ * @name Configuration#Promise
+ * @since 3.5.0
+ * @type function
+ * @default undefined
+ */
+ Promise: undefined
+ };
var currentSuite = function() {
return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
@@ -525,37 +1054,194 @@ getJasmineRequireObj().Env = function(j$) {
return currentSpec || currentSuite();
};
- var reporter = new j$.ReportDispatcher([
- 'jasmineStarted',
- 'jasmineDone',
- 'suiteStarted',
- 'suiteDone',
- 'specStarted',
- 'specDone'
- ]);
+ var globalErrors = null;
- this.specFilter = function() {
- return true;
+ var installGlobalErrors = function() {
+ if (globalErrors) {
+ return;
+ }
+
+ globalErrors = new j$.GlobalErrors();
+ globalErrors.install();
+ };
+
+ if (!options.suppressLoadErrors) {
+ installGlobalErrors();
+ globalErrors.pushListener(function(
+ message,
+ filename,
+ lineno,
+ colNo,
+ err
+ ) {
+ topSuite.result.failedExpectations.push({
+ passed: false,
+ globalErrorType: 'load',
+ message: message,
+ stack: err && err.stack,
+ filename: filename,
+ lineno: lineno
+ });
+ });
+ }
+
+ /**
+ * Configure your jasmine environment
+ * @name Env#configure
+ * @since 3.3.0
+ * @argument {Configuration} configuration
+ * @function
+ */
+ this.configure = function(configuration) {
+ if (configuration.specFilter) {
+ config.specFilter = configuration.specFilter;
+ }
+
+ if (configuration.hasOwnProperty('random')) {
+ config.random = !!configuration.random;
+ }
+
+ if (configuration.hasOwnProperty('seed')) {
+ config.seed = configuration.seed;
+ }
+
+ if (configuration.hasOwnProperty('failFast')) {
+ config.failFast = configuration.failFast;
+ }
+
+ if (configuration.hasOwnProperty('failSpecWithNoExpectations')) {
+ config.failSpecWithNoExpectations =
+ configuration.failSpecWithNoExpectations;
+ }
+
+ if (configuration.hasOwnProperty('oneFailurePerSpec')) {
+ config.oneFailurePerSpec = configuration.oneFailurePerSpec;
+ }
+
+ if (configuration.hasOwnProperty('hideDisabled')) {
+ config.hideDisabled = configuration.hideDisabled;
+ }
+
+ // Don't use hasOwnProperty to check for Promise existence because Promise
+ // can be initialized to undefined, either explicitly or by using the
+ // object returned from Env#configuration. In particular, Karma does this.
+ if (configuration.Promise) {
+ if (
+ typeof configuration.Promise.resolve === 'function' &&
+ typeof configuration.Promise.reject === 'function'
+ ) {
+ customPromise = configuration.Promise;
+ } else {
+ throw new Error(
+ 'Custom promise library missing `resolve`/`reject` functions'
+ );
+ }
+ }
+ };
+
+ /**
+ * Get the current configuration for your jasmine environment
+ * @name Env#configuration
+ * @since 3.3.0
+ * @function
+ * @returns {Configuration}
+ */
+ this.configuration = function() {
+ var result = {};
+ for (var property in config) {
+ result[property] = config[property];
+ }
+ return result;
+ };
+
+ Object.defineProperty(this, 'specFilter', {
+ get: function() {
+ self.deprecated(
+ 'Getting specFilter directly from Env is deprecated and will be removed in a future version of
Jasmine, please check the specFilter option from `configuration`'
+ );
+ return config.specFilter;
+ },
+ set: function(val) {
+ self.deprecated(
+ 'Setting specFilter directly on Env is deprecated and will be removed in a future version of
Jasmine, please use the specFilter option in `configure`'
+ );
+ config.specFilter = val;
+ }
+ });
+
+ this.setDefaultSpyStrategy = function(defaultStrategyFn) {
+ if (!currentRunnable()) {
+ throw new Error(
+ 'Default spy strategy must be set in a before function or a spec'
+ );
+ }
+ runnableResources[
+ currentRunnable().id
+ ].defaultStrategyFn = defaultStrategyFn;
+ };
+
+ this.addSpyStrategy = function(name, fn) {
+ if (!currentRunnable()) {
+ throw new Error(
+ 'Custom spy strategies must be added in a before function or a spec'
+ );
+ }
+ runnableResources[currentRunnable().id].customSpyStrategies[name] = fn;
};
this.addCustomEqualityTester = function(tester) {
- if(!currentRunnable()) {
- throw new Error('Custom Equalities must be added in a before function or a spec');
+ if (!currentRunnable()) {
+ throw new Error(
+ 'Custom Equalities must be added in a before function or a spec'
+ );
}
- runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
+ 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');
+ if (!currentRunnable()) {
+ throw new Error(
+ 'Matchers must be added in a before function or a spec'
+ );
}
- var customMatchers = runnableResources[currentRunnable().id].customMatchers;
+ var customMatchers =
+ runnableResources[currentRunnable().id].customMatchers;
+
for (var matcherName in matchersToAdd) {
customMatchers[matcherName] = matchersToAdd[matcherName];
}
};
+ this.addAsyncMatchers = function(matchersToAdd) {
+ if (!currentRunnable()) {
+ throw new Error(
+ 'Async Matchers must be added in a before function or a spec'
+ );
+ }
+ var customAsyncMatchers =
+ runnableResources[currentRunnable().id].customAsyncMatchers;
+
+ for (var matcherName in matchersToAdd) {
+ customAsyncMatchers[matcherName] = matchersToAdd[matcherName];
+ }
+ };
+
+ this.addCustomObjectFormatter = function(formatter) {
+ if (!currentRunnable()) {
+ throw new Error(
+ 'Custom object formatters must be added in a before function or a spec'
+ );
+ }
+
+ runnableResources[currentRunnable().id].customObjectFormatters.push(
+ formatter
+ );
+ };
+
j$.Expectation.addCoreMatchers(j$.matchers);
+ j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers);
var nextSpecId = 0;
var getNextSpecId = function() {
@@ -567,10 +1253,28 @@ getJasmineRequireObj().Env = function(j$) {
return 'suite' + nextSuiteId++;
};
+ var makePrettyPrinter = function() {
+ var customObjectFormatters =
+ runnableResources[currentRunnable().id].customObjectFormatters;
+ return j$.makePrettyPrinter(customObjectFormatters);
+ };
+
+ var makeMatchersUtil = function() {
+ var customEqualityTesters =
+ runnableResources[currentRunnable().id].customEqualityTesters;
+ return new j$.MatchersUtil({
+ customTesters: customEqualityTesters,
+ pp: makePrettyPrinter()
+ });
+ };
+
var expectationFactory = function(actual, spec) {
- return j$.Expectation.Factory({
- util: j$.matchersUtil,
- customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+ var customEqualityTesters =
+ runnableResources[spec.id].customEqualityTesters;
+
+ return j$.Expectation.factory({
+ matchersUtil: makeMatchersUtil(),
+ customEqualityTesters: customEqualityTesters,
customMatchers: runnableResources[spec.id].customMatchers,
actual: actual,
addExpectationResult: addExpectationResult
@@ -581,28 +1285,95 @@ getJasmineRequireObj().Env = function(j$) {
}
};
- var defaultResourcesForRunnable = function(id, parentRunnableId) {
- var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
+ function recordLateExpectation(runable, runableType, result) {
+ var delayedExpectationResult = {};
+ Object.keys(result).forEach(function(k) {
+ delayedExpectationResult[k] = result[k];
+ });
+ delayedExpectationResult.passed = false;
+ delayedExpectationResult.globalErrorType = 'lateExpectation';
+ delayedExpectationResult.message =
+ runableType +
+ ' "' +
+ runable.getFullName() +
+ '" ran a "' +
+ result.matcherName +
+ '" expectation after it finished.\n';
- if(runnableResources[parentRunnableId]){
- resources.customEqualityTesters =
j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
- resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
+ if (result.message) {
+ delayedExpectationResult.message +=
+ 'Message: "' + result.message + '"\n';
}
- runnableResources[id] = resources;
+ delayedExpectationResult.message +=
+ 'Did you forget to return or await the result of expectAsync?';
+
+ topSuite.result.failedExpectations.push(delayedExpectationResult);
+ }
+
+ var asyncExpectationFactory = function(actual, spec, runableType) {
+ return j$.Expectation.asyncFactory({
+ matchersUtil: makeMatchersUtil(),
+ customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+ customAsyncMatchers: runnableResources[spec.id].customAsyncMatchers,
+ actual: actual,
+ addExpectationResult: addExpectationResult
+ });
+
+ function addExpectationResult(passed, result) {
+ if (currentRunnable() !== spec) {
+ recordLateExpectation(spec, runableType, result);
+ }
+ return spec.addExpectationResult(passed, result);
+ }
+ };
+ var suiteAsyncExpectationFactory = function(actual, suite) {
+ return asyncExpectationFactory(actual, suite, 'Suite');
};
- var clearResourcesForRunnable = function(id) {
- spyRegistry.clearSpies();
- delete runnableResources[id];
+ var specAsyncExpectationFactory = function(actual, suite) {
+ return asyncExpectationFactory(actual, suite, 'Spec');
};
- var beforeAndAfterFns = function(suite) {
- return function() {
+ var defaultResourcesForRunnable = function(id, parentRunnableId) {
+ var resources = {
+ spies: [],
+ customEqualityTesters: [],
+ customMatchers: {},
+ customAsyncMatchers: {},
+ customSpyStrategies: {},
+ defaultStrategyFn: undefined,
+ customObjectFormatters: []
+ };
+
+ if (runnableResources[parentRunnableId]) {
+ resources.customEqualityTesters = j$.util.clone(
+ runnableResources[parentRunnableId].customEqualityTesters
+ );
+ resources.customMatchers = j$.util.clone(
+ runnableResources[parentRunnableId].customMatchers
+ );
+ resources.customAsyncMatchers = j$.util.clone(
+ runnableResources[parentRunnableId].customAsyncMatchers
+ );
+ resources.defaultStrategyFn =
+ runnableResources[parentRunnableId].defaultStrategyFn;
+ }
+
+ runnableResources[id] = resources;
+ };
+
+ var clearResourcesForRunnable = function(id) {
+ spyRegistry.clearSpies();
+ delete runnableResources[id];
+ };
+
+ var beforeAndAfterFns = function(suite) {
+ return function() {
var befores = [],
afters = [];
- while(suite) {
+ while (suite) {
befores = befores.concat(suite.beforeFns);
afters = afters.concat(suite.afterFns);
@@ -618,7 +1389,7 @@ getJasmineRequireObj().Env = function(j$) {
var getSpecName = function(spec, suite) {
var fullName = [spec.description],
- suiteFullName = suite.getFullName();
+ suiteFullName = suite.getFullName();
if (suiteFullName !== '') {
fullName.unshift(suiteFullName);
@@ -628,71 +1399,152 @@ getJasmineRequireObj().Env = function(j$) {
// 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);
- };
+ exceptionFormatter = new j$.ExceptionFormatter(),
+ expectationResultFactory = function(attrs) {
+ attrs.messageFormatter = exceptionFormatter.message;
+ attrs.stackFormatter = exceptionFormatter.stack;
- // TODO: fix this naming, and here's where the value comes in
- this.catchExceptions = function(value) {
- catchExceptions = !!value;
- return catchExceptions;
- };
+ return buildExpectationResult(attrs);
+ };
- this.catchingExceptions = function() {
- return catchExceptions;
+ /**
+ * Sets whether Jasmine should throw an Error when an expectation fails.
+ * This causes a spec to only have one expectation failure.
+ * @name Env#throwOnExpectationFailure
+ * @since 2.3.0
+ * @function
+ * @param {Boolean} value Whether to throw when a expectation fails
+ * @deprecated Use the `oneFailurePerSpec` option with {@link Env#configure}
+ */
+ this.throwOnExpectationFailure = function(value) {
+ this.deprecated(
+ 'Setting throwOnExpectationFailure directly on Env is deprecated and will be removed in a future
version of Jasmine, please use the oneFailurePerSpec option in `configure`'
+ );
+ this.configure({ oneFailurePerSpec: !!value });
};
- 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.throwingExpectationFailures = function() {
+ this.deprecated(
+ 'Getting throwingExpectationFailures directly from Env is deprecated and will be removed in a future
version of Jasmine, please check the oneFailurePerSpec option from `configuration`'
+ );
+ return config.oneFailurePerSpec;
};
- this.throwOnExpectationFailure = function(value) {
- throwOnExpectationFailure = !!value;
+ /**
+ * Set whether to stop suite execution when a spec fails
+ * @name Env#stopOnSpecFailure
+ * @since 2.7.0
+ * @function
+ * @param {Boolean} value Whether to stop suite execution when a spec fails
+ * @deprecated Use the `failFast` option with {@link Env#configure}
+ */
+ this.stopOnSpecFailure = function(value) {
+ this.deprecated(
+ 'Setting stopOnSpecFailure directly is deprecated and will be removed in a future version of
Jasmine, please use the failFast option in `configure`'
+ );
+ this.configure({ failFast: !!value });
};
- this.throwingExpectationFailures = function() {
- return throwOnExpectationFailure;
+ this.stoppingOnSpecFailure = function() {
+ this.deprecated(
+ 'Getting stoppingOnSpecFailure directly from Env is deprecated and will be removed in a future
version of Jasmine, please check the failFast option from `configuration`'
+ );
+ return config.failFast;
};
+ /**
+ * Set whether to randomize test execution order
+ * @name Env#randomizeTests
+ * @since 2.4.0
+ * @function
+ * @param {Boolean} value Whether to randomize execution order
+ * @deprecated Use the `random` option with {@link Env#configure}
+ */
this.randomizeTests = function(value) {
- random = !!value;
+ this.deprecated(
+ 'Setting randomizeTests directly is deprecated and will be removed in a future version of Jasmine,
please use the random option in `configure`'
+ );
+ config.random = !!value;
};
this.randomTests = function() {
- return random;
+ this.deprecated(
+ 'Getting randomTests directly from Env is deprecated and will be removed in a future version of
Jasmine, please check the random option from `configuration`'
+ );
+ return config.random;
};
+ /**
+ * Set the random number seed for spec randomization
+ * @name Env#seed
+ * @since 2.4.0
+ * @function
+ * @param {Number} value The seed value
+ * @deprecated Use the `seed` option with {@link Env#configure}
+ */
this.seed = function(value) {
+ this.deprecated(
+ 'Setting seed directly is deprecated and will be removed in a future version of Jasmine, please use
the seed option in `configure`'
+ );
if (value) {
- seed = value;
+ config.seed = value;
+ }
+ return config.seed;
+ };
+
+ this.hidingDisabled = function(value) {
+ this.deprecated(
+ 'Getting hidingDisabled directly from Env is deprecated and will be removed in a future version of
Jasmine, please check the hideDisabled option from `configuration`'
+ );
+ return config.hideDisabled;
+ };
+
+ /**
+ * @name Env#hideDisabled
+ * @since 3.2.0
+ * @function
+ */
+ this.hideDisabled = function(value) {
+ this.deprecated(
+ 'Setting hideDisabled directly is deprecated and will be removed in a future version of Jasmine,
please use the hideDisabled option in `configure`'
+ );
+ config.hideDisabled = !!value;
+ };
+
+ this.deprecated = function(deprecation) {
+ var runnable = currentRunnable() || topSuite;
+ runnable.addDeprecationWarning(deprecation);
+ if (
+ typeof console !== 'undefined' &&
+ typeof console.error === 'function'
+ ) {
+ console.error('DEPRECATION:', deprecation);
}
- return seed;
};
- var queueRunnerFactory = function(options) {
- options.catchException = catchException;
+ var queueRunnerFactory = function(options, args) {
+ var failFast = false;
+ if (options.isLeaf) {
+ failFast = config.oneFailurePerSpec;
+ } else if (!options.isReporter) {
+ failFast = config.failFast;
+ }
options.clearStack = options.clearStack || clearStack;
- options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+ options.timeout = {
+ setTimeout: realSetTimeout,
+ clearTimeout: realClearTimeout
+ };
options.fail = self.fail;
+ options.globalErrors = globalErrors;
+ options.completeOnFirstError = failFast;
+ options.onException =
+ options.onException ||
+ function(e) {
+ (currentRunnable() || topSuite).onException(e);
+ };
+ options.deprecated = self.deprecated;
- new j$.QueueRunner(options).execute();
+ new j$.QueueRunner(options).execute(args);
};
var topSuite = new j$.Suite({
@@ -700,6 +1552,7 @@ getJasmineRequireObj().Env = function(j$) {
id: getNextSuiteId(),
description: 'Jasmine__TopLevel__Suite',
expectationFactory: expectationFactory,
+ asyncExpectationFactory: suiteAsyncExpectationFactory,
expectationResultFactory: expectationResultFactory
});
defaultResourcesForRunnable(topSuite.id);
@@ -709,8 +1562,85 @@ getJasmineRequireObj().Env = function(j$) {
return topSuite;
};
+ /**
+ * This represents the available reporter callback for an object passed to {@link Env#addReporter}.
+ * @interface Reporter
+ * @see custom_reporter
+ */
+ var reporter = new j$.ReportDispatcher(
+ [
+ /**
+ * `jasmineStarted` is called after all of the specs have been loaded, but just before execution
starts.
+ * @function
+ * @name Reporter#jasmineStarted
+ * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
+ * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and
Jasmine should wait until it has been called before moving on.
+ * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for
completion.
+ * @see async
+ */
+ 'jasmineStarted',
+ /**
+ * When the entire suite has finished execution `jasmineDone` is called
+ * @function
+ * @name Reporter#jasmineDone
+ * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished
running.
+ * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and
Jasmine should wait until it has been called before moving on.
+ * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for
completion.
+ * @see async
+ */
+ 'jasmineDone',
+ /**
+ * `suiteStarted` is invoked when a `describe` starts to run
+ * @function
+ * @name Reporter#suiteStarted
+ * @param {SuiteResult} result Information about the individual {@link describe} being run
+ * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and
Jasmine should wait until it has been called before moving on.
+ * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for
completion.
+ * @see async
+ */
+ 'suiteStarted',
+ /**
+ * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
+ *
+ * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it
impossible for a reporter to know when a suite has failures in an `afterAll`.
+ * @function
+ * @name Reporter#suiteDone
+ * @param {SuiteResult} result
+ * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and
Jasmine should wait until it has been called before moving on.
+ * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for
completion.
+ * @see async
+ */
+ 'suiteDone',
+ /**
+ * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
+ * @function
+ * @name Reporter#specStarted
+ * @param {SpecResult} result Information about the individual {@link it} being run
+ * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and
Jasmine should wait until it has been called before moving on.
+ * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for
completion.
+ * @see async
+ */
+ 'specStarted',
+ /**
+ * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have
been run.
+ *
+ * While jasmine doesn't require any specific functions, not defining a `specDone` will make it
impossible for a reporter to know when a spec has failed.
+ * @function
+ * @name Reporter#specDone
+ * @param {SpecResult} result
+ * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and
Jasmine should wait until it has been called before moving on.
+ * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for
completion.
+ * @see async
+ */
+ 'specDone'
+ ],
+ queueRunnerFactory
+ );
+
this.execute = function(runnablesToRun) {
- if(!runnablesToRun) {
+ installGlobalErrors();
+
+ if (!runnablesToRun) {
if (focusedRunnables.length) {
runnablesToRun = focusedRunnables;
} else {
@@ -719,72 +1649,182 @@ getJasmineRequireObj().Env = function(j$) {
}
var order = new j$.Order({
- random: random,
- seed: seed
+ random: config.random,
+ seed: config.seed
});
var processor = new j$.TreeProcessor({
tree: topSuite,
runnableIds: runnablesToRun,
queueRunnerFactory: queueRunnerFactory,
- nodeStart: function(suite) {
+ failSpecWithNoExpectations: config.failSpecWithNoExpectations,
+ nodeStart: function(suite, next) {
currentlyExecutingSuites.push(suite);
defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
- reporter.suiteStarted(suite.result);
+ reporter.suiteStarted(suite.result, next);
+ suite.startTimer();
},
- nodeComplete: function(suite, result) {
- if (!suite.disabled) {
- clearResourcesForRunnable(suite.id);
+ nodeComplete: function(suite, result, next) {
+ if (suite !== currentSuite()) {
+ throw new Error('Tried to complete the wrong suite');
}
+
+ clearResourcesForRunnable(suite.id);
currentlyExecutingSuites.pop();
- reporter.suiteDone(result);
+
+ if (result.status === 'failed') {
+ hasFailures = true;
+ }
+ suite.endTimer();
+ reporter.suiteDone(result, next);
},
orderChildren: function(node) {
return order.sort(node.children);
+ },
+ excludeNode: function(spec) {
+ return !config.specFilter(spec);
}
});
- if(!processor.processTree().valid) {
- throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times');
+ 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);
+ var jasmineTimer = new j$.Timer();
+ jasmineTimer.start();
- processor.execute(function() {
- clearResourcesForRunnable(topSuite.id);
- currentlyExecutingSuites.pop();
+ /**
+ * Information passed to the {@link Reporter#jasmineStarted} event.
+ * @typedef JasmineStartedInfo
+ * @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
+ * @property {Order} order - Information about the ordering (random or not) of this execution of the
suite.
+ */
+ reporter.jasmineStarted(
+ {
+ totalSpecsDefined: totalSpecsDefined,
+ order: order
+ },
+ function() {
+ currentlyExecutingSuites.push(topSuite);
+
+ processor.execute(function() {
+ clearResourcesForRunnable(topSuite.id);
+ currentlyExecutingSuites.pop();
+ var overallStatus, incompleteReason;
+
+ if (hasFailures || topSuite.result.failedExpectations.length > 0) {
+ overallStatus = 'failed';
+ } else if (focusedRunnables.length > 0) {
+ overallStatus = 'incomplete';
+ incompleteReason = 'fit() or fdescribe() was found';
+ } else if (totalSpecsDefined === 0) {
+ overallStatus = 'incomplete';
+ incompleteReason = 'No specs found';
+ } else {
+ overallStatus = 'passed';
+ }
- reporter.jasmineDone({
- order: order,
- failedExpectations: topSuite.result.failedExpectations
- });
- });
+ /**
+ * Information passed to the {@link Reporter#jasmineDone} event.
+ * @typedef JasmineDoneInfo
+ * @property {OverallStatus} overallStatus - The overall result of the suite: 'passed',
'failed', or 'incomplete'.
+ * @property {Int} totalTime - The total time (in ms) that it took to execute the suite
+ * @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
+ * @property {Order} order - Information about the ordering (random or not) of this execution of
the suite.
+ * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link
afterAll} at the global level.
+ * @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at
the global level.
+ */
+ reporter.jasmineDone(
+ {
+ overallStatus: overallStatus,
+ totalTime: jasmineTimer.elapsed(),
+ incompleteReason: incompleteReason,
+ order: order,
+ failedExpectations: topSuite.result.failedExpectations,
+ deprecationWarnings: topSuite.result.deprecationWarnings
+ },
+ function() {}
+ );
+ });
+ }
+ );
};
+ /**
+ * Add a custom reporter to the Jasmine environment.
+ * @name Env#addReporter
+ * @since 2.0.0
+ * @function
+ * @param {Reporter} reporterToAdd The reporter to be added.
+ * @see custom_reporter
+ */
this.addReporter = function(reporterToAdd) {
reporter.addReporter(reporterToAdd);
};
+ /**
+ * Provide a fallback reporter if no other reporters have been specified.
+ * @name Env#provideFallbackReporter
+ * @since 2.5.0
+ * @function
+ * @param {Reporter} reporterToAdd The reporter
+ * @see custom_reporter
+ */
this.provideFallbackReporter = function(reporterToAdd) {
reporter.provideFallbackReporter(reporterToAdd);
};
+ /**
+ * Clear all registered reporters
+ * @name Env#clearReporters
+ * @since 2.5.2
+ * @function
+ */
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');
+ var spyFactory = new j$.SpyFactory(
+ function getCustomStrategies() {
+ var runnable = currentRunnable();
+
+ if (runnable) {
+ return runnableResources[runnable.id].customSpyStrategies;
+ }
+
+ return {};
+ },
+ function getDefaultStrategyFn() {
+ var runnable = currentRunnable();
+
+ if (runnable) {
+ return runnableResources[runnable.id].defaultStrategyFn;
+ }
+
+ return undefined;
+ },
+ function getPromise() {
+ return customPromise || global.Promise;
+ }
+ );
+
+ 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;
+ },
+ createSpy: function(name, originalFn) {
+ return self.createSpy(name, originalFn);
}
- return runnableResources[currentRunnable().id].spies;
- }});
+ });
- this.allowRespy = function(allow){
+ this.allowRespy = function(allow) {
spyRegistry.allowRespy(allow);
};
@@ -792,21 +1832,71 @@ getJasmineRequireObj().Env = function(j$) {
return spyRegistry.spyOn.apply(spyRegistry, arguments);
};
+ this.spyOnProperty = function() {
+ return spyRegistry.spyOnProperty.apply(spyRegistry, arguments);
+ };
+
+ this.spyOnAllFunctions = function() {
+ return spyRegistry.spyOnAllFunctions.apply(spyRegistry, arguments);
+ };
+
+ this.createSpy = function(name, originalFn) {
+ if (arguments.length === 1 && j$.isFunction_(name)) {
+ originalFn = name;
+ name = originalFn.name;
+ }
+
+ return spyFactory.createSpy(name, originalFn);
+ };
+
+ this.createSpyObj = function(baseName, methodNames, propertyNames) {
+ return spyFactory.createSpyObj(baseName, methodNames, propertyNames);
+ };
+
+ var ensureIsFunction = function(fn, caller) {
+ if (!j$.isFunction_(fn)) {
+ throw new Error(
+ caller + ' expects a function argument; received ' + j$.getType_(fn)
+ );
+ }
+ };
+
+ var ensureIsFunctionOrAsync = function(fn, caller) {
+ if (!j$.isFunction_(fn) && !j$.isAsyncFunction_(fn)) {
+ throw new Error(
+ caller + ' expects a function argument; received ' + j$.getType_(fn)
+ );
+ }
+ };
+
+ function ensureIsNotNested(method) {
+ var runnable = currentRunnable();
+ if (runnable !== null && runnable !== undefined) {
+ throw new Error(
+ "'" + method + "' should only be used in 'describe' function"
+ );
+ }
+ }
+
var suiteFactory = function(description) {
var suite = new j$.Suite({
env: self,
id: getNextSuiteId(),
description: description,
parentSuite: currentDeclarationSuite,
+ timer: new j$.Timer(),
expectationFactory: expectationFactory,
+ asyncExpectationFactory: suiteAsyncExpectationFactory,
expectationResultFactory: expectationResultFactory,
- throwOnExpectationFailure: throwOnExpectationFailure
+ throwOnExpectationFailure: config.oneFailurePerSpec
});
return suite;
};
this.describe = function(description, specDefinitions) {
+ ensureIsNotNested('describe');
+ ensureIsFunction(specDefinitions, 'describe');
var suite = suiteFactory(description);
if (specDefinitions.length > 0) {
throw new Error('describe does not expect any arguments');
@@ -819,6 +1909,8 @@ getJasmineRequireObj().Env = function(j$) {
};
this.xdescribe = function(description, specDefinitions) {
+ ensureIsNotNested('xdescribe');
+ ensureIsFunction(specDefinitions, 'xdescribe');
var suite = suiteFactory(description);
suite.pend();
addSpecsToSuite(suite, specDefinitions);
@@ -828,6 +1920,8 @@ getJasmineRequireObj().Env = function(j$) {
var focusedRunnables = [];
this.fdescribe = function(description, specDefinitions) {
+ ensureIsNotNested('fdescribe');
+ ensureIsFunction(specDefinitions, 'fdescribe');
var suite = suiteFactory(description);
suite.isFocused = true;
@@ -851,9 +1945,7 @@ getJasmineRequireObj().Env = function(j$) {
}
if (declarationError) {
- self.it('encountered a declaration exception', function() {
- throw declarationError;
- });
+ suite.onException(declarationError);
}
currentDeclarationSuite = parentSuite;
@@ -888,6 +1980,7 @@ getJasmineRequireObj().Env = function(j$) {
id: getNextSpecId(),
beforeAndAfterFns: beforeAndAfterFns(suite),
expectationFactory: expectationFactory,
+ asyncExpectationFactory: specAsyncExpectationFactory,
resultCallback: specResultCallback,
getSpecName: function(spec) {
return getSpecName(spec, suite);
@@ -896,34 +1989,43 @@ getJasmineRequireObj().Env = function(j$) {
description: description,
expectationResultFactory: expectationResultFactory,
queueRunnerFactory: queueRunnerFactory,
- userContext: function() { return suite.clonedSharedUserContext(); },
+ userContext: function() {
+ return suite.clonedSharedUserContext();
+ },
queueableFn: {
fn: fn,
- timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ timeout: timeout || 0
},
- throwOnExpectationFailure: throwOnExpectationFailure
+ throwOnExpectationFailure: config.oneFailurePerSpec,
+ timer: new j$.Timer()
});
-
- if (!self.specFilter(spec)) {
- spec.disable();
- }
-
return spec;
- function specResultCallback(result) {
+ function specResultCallback(result, next) {
clearResourcesForRunnable(spec.id);
currentSpec = null;
- reporter.specDone(result);
+
+ if (result.status === 'failed') {
+ hasFailures = true;
+ }
+
+ reporter.specDone(result, next);
}
- function specStarted(spec) {
+ function specStarted(spec, next) {
currentSpec = spec;
defaultResourcesForRunnable(spec.id, suite.id);
- reporter.specStarted(spec.result);
+ reporter.specStarted(spec.result, next);
}
};
this.it = function(description, fn, timeout) {
+ ensureIsNotNested('it');
+ // it() sometimes doesn't have a fn argument, so only check the type if
+ // it's given.
+ if (arguments.length > 1 && typeof fn !== 'undefined') {
+ ensureIsFunctionOrAsync(fn, 'it');
+ }
var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
if (currentDeclarationSuite.markedPending) {
spec.pend();
@@ -932,13 +2034,21 @@ getJasmineRequireObj().Env = function(j$) {
return spec;
};
- this.xit = function() {
+ this.xit = function(description, fn, timeout) {
+ ensureIsNotNested('xit');
+ // xit(), like it(), doesn't always have a fn argument, so only check the
+ // type when needed.
+ if (arguments.length > 1 && typeof fn !== 'undefined') {
+ ensureIsFunctionOrAsync(fn, 'xit');
+ }
var spec = this.it.apply(this, arguments);
spec.pend('Temporarily disabled with xit');
return spec;
};
- this.fit = function(description, fn, timeout){
+ this.fit = function(description, fn, timeout) {
+ ensureIsNotNested('fit');
+ ensureIsFunctionOrAsync(fn, 'fit');
var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
currentDeclarationSuite.addChild(spec);
focusedRunnables.push(spec.id);
@@ -946,55 +2056,123 @@ getJasmineRequireObj().Env = function(j$) {
return spec;
};
+ /**
+ * Sets a user-defined property that will be provided to reporters as part of the properties field of
{@link SpecResult}
+ * @name Env#setSpecProperty
+ * @since 3.6.0
+ * @function
+ * @param {String} key The name of the property
+ * @param {*} value The value of the property
+ */
+ this.setSpecProperty = function(key, value) {
+ if (!currentRunnable() || currentRunnable() == currentSuite()) {
+ throw new Error(
+ "'setSpecProperty' was used when there was no current spec"
+ );
+ }
+ currentRunnable().setSpecProperty(key, value);
+ };
+
+ /**
+ * Sets a user-defined property that will be provided to reporters as part of the properties field of
{@link SuiteResult}
+ * @name Env#setSuiteProperty
+ * @since 3.6.0
+ * @function
+ * @param {String} key The name of the property
+ * @param {*} value The value of the property
+ */
+ this.setSuiteProperty = function(key, value) {
+ if (!currentSuite()) {
+ throw new Error(
+ "'setSuiteProperty' was used when there was no current suite"
+ );
+ }
+ currentSuite().setSuiteProperty(key, value);
+ };
+
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');
+ 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.expectAsync = function(actual) {
+ if (!currentRunnable()) {
+ throw new Error(
+ "'expectAsync' was used when there was no current spec, this could be because an asynchronous test
timed out"
+ );
+ }
+
+ return currentRunnable().expectAsync(actual);
+ };
+
this.beforeEach = function(beforeEachFunction, timeout) {
+ ensureIsNotNested('beforeEach');
+ ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach');
currentDeclarationSuite.beforeEach({
fn: beforeEachFunction,
- timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ timeout: timeout || 0
});
};
this.beforeAll = function(beforeAllFunction, timeout) {
+ ensureIsNotNested('beforeAll');
+ ensureIsFunctionOrAsync(beforeAllFunction, 'beforeAll');
currentDeclarationSuite.beforeAll({
fn: beforeAllFunction,
- timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ timeout: timeout || 0
});
};
this.afterEach = function(afterEachFunction, timeout) {
+ ensureIsNotNested('afterEach');
+ ensureIsFunctionOrAsync(afterEachFunction, 'afterEach');
+ afterEachFunction.isCleanup = true;
currentDeclarationSuite.afterEach({
fn: afterEachFunction,
- timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ timeout: timeout || 0
});
};
this.afterAll = function(afterAllFunction, timeout) {
+ ensureIsNotNested('afterAll');
+ ensureIsFunctionOrAsync(afterAllFunction, 'afterAll');
currentDeclarationSuite.afterAll({
fn: afterAllFunction,
- timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ timeout: timeout || 0
});
};
this.pending = function(message) {
var fullMessage = j$.Spec.pendingSpecExceptionMessage;
- if(message) {
+ if (message) {
fullMessage += message;
}
throw fullMessage;
};
this.fail = function(error) {
+ if (!currentRunnable()) {
+ throw new Error(
+ "'fail' was used when there was no current spec, this could be because an asynchronous test timed
out"
+ );
+ }
+
var message = 'Failed';
if (error) {
message += ': ';
- message += error.message || error;
+ if (error.message) {
+ message += error.message;
+ } else if (j$.isString_(error)) {
+ message += error;
+ } else {
+ // pretty print all kind of objects. This includes arrays.
+ message += makePrettyPrinter()(error);
+ }
}
currentRunnable().addExpectationResult(false, {
@@ -1005,22 +2183,32 @@ getJasmineRequireObj().Env = function(j$) {
message: message,
error: error && error.message ? error : null
});
+
+ if (config.oneFailurePerSpec) {
+ throw new Error(message);
+ }
+ };
+
+ this.cleanup_ = function() {
+ if (globalErrors) {
+ globalErrors.uninstall();
+ }
};
}
return Env;
};
-getJasmineRequireObj().JsApiReporter = function() {
-
- var noopTimer = {
- start: function(){},
- elapsed: function(){ return 0; }
- };
-
+getJasmineRequireObj().JsApiReporter = function(j$) {
+ /**
+ * @name jsApiReporter
+ * @classdesc {@link Reporter} added by default in `boot.js` to record results for retrieval in javascript
code. An instance is made available as `jsApiReporter` on the global object.
+ * @class
+ * @hideconstructor
+ */
function JsApiReporter(options) {
- var timer = options.timer || noopTimer,
- status = 'loaded';
+ var timer = options.timer || new j$.Timer(),
+ status = 'loaded';
this.started = false;
this.finished = false;
@@ -1041,6 +2229,13 @@ getJasmineRequireObj().JsApiReporter = function() {
status = 'done';
};
+ /**
+ * Get the current status for the Jasmine environment.
+ * @name jsApiReporter#status
+ * @since 2.0.0
+ * @function
+ * @return {String} - One of `loaded`, `started`, or `done`
+ */
this.status = function() {
return status;
};
@@ -1056,6 +2251,17 @@ getJasmineRequireObj().JsApiReporter = function() {
storeSuite(result);
};
+ /**
+ * Get the results for a set of suites.
+ *
+ * Retrievable in slices for easier serialization.
+ * @name jsApiReporter#suiteResults
+ * @since 2.1.0
+ * @function
+ * @param {Number} index - The position in the suites list to start from.
+ * @param {Number} length - Maximum number of suite results to return.
+ * @return {SuiteResult[]}
+ */
this.suiteResults = function(index, length) {
return suites.slice(index, index + length);
};
@@ -1065,6 +2271,13 @@ getJasmineRequireObj().JsApiReporter = function() {
suites_hash[result.id] = result;
}
+ /**
+ * Get all of the suites in a single object, with their `id` as the key.
+ * @name jsApiReporter#suites
+ * @since 2.0.0
+ * @function
+ * @return {Object} - Map of suite id to {@link SuiteResult}
+ */
this.suites = function() {
return suites_hash;
};
@@ -1075,1807 +2288,2416 @@ getJasmineRequireObj().JsApiReporter = function() {
specs.push(result);
};
+ /**
+ * Get the results for a set of specs.
+ *
+ * Retrievable in slices for easier serialization.
+ * @name jsApiReporter#specResults
+ * @since 2.0.0
+ * @function
+ * @param {Number} index - The position in the specs list to start from.
+ * @param {Number} length - Maximum number of specs results to return.
+ * @return {SpecResult[]}
+ */
this.specResults = function(index, length) {
return specs.slice(index, index + length);
};
+ /**
+ * Get all spec results.
+ * @name jsApiReporter#specs
+ * @since 2.0.0
+ * @function
+ * @return {SpecResult[]}
+ */
this.specs = function() {
return specs;
};
+ /**
+ * Get the number of milliseconds it took for the full Jasmine suite to run.
+ * @name jsApiReporter#executionTime
+ * @since 2.0.0
+ * @function
+ * @return {Number}
+ */
this.executionTime = function() {
return executionTime;
};
-
}
return JsApiReporter;
};
-getJasmineRequireObj().CallTracker = function(j$) {
+getJasmineRequireObj().Any = function(j$) {
- function CallTracker() {
- var calls = [];
- var opts = {};
+ 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;
+ }
- 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;
+ Any.prototype.asymmetricMatch = function(other) {
+ if (this.expectedObject == String) {
+ return typeof other == 'string' || other instanceof String;
}
- this.track = function(context) {
- if(opts.cloneArgs) {
- argCloner(context);
- }
- calls.push(context);
- };
+ if (this.expectedObject == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
- this.any = function() {
- return !!calls.length;
- };
+ if (this.expectedObject == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
- this.count = function() {
- return calls.length;
- };
+ if (this.expectedObject == Object) {
+ return other !== null && typeof other == 'object';
+ }
- this.argsFor = function(index) {
- var call = calls[index];
- return call ? call.args : [];
- };
+ if (this.expectedObject == Boolean) {
+ return typeof other == 'boolean';
+ }
- this.all = function() {
- return calls;
- };
+ /* jshint -W122 */
+ /* global Symbol */
+ if (typeof Symbol != 'undefined' && this.expectedObject == Symbol) {
+ return typeof other == 'symbol';
+ }
+ /* jshint +W122 */
- this.allArgs = function() {
- var callArgs = [];
- for(var i = 0; i < calls.length; i++){
- callArgs.push(calls[i].args);
- }
+ return other instanceof this.expectedObject;
+ };
- return callArgs;
- };
+ Any.prototype.jasmineToString = function() {
+ return '<jasmine.any(' + j$.fnNameFor(this.expectedObject) + ')>';
+ };
- this.first = function() {
- return calls[0];
- };
+ return Any;
+};
- this.mostRecent = function() {
- return calls[calls.length - 1];
- };
+getJasmineRequireObj().Anything = function(j$) {
- this.reset = function() {
- calls = [];
- };
+ function Anything() {}
- this.saveArgumentsByValue = function() {
- opts.cloneArgs = true;
- };
+ Anything.prototype.asymmetricMatch = function(other) {
+ return !j$.util.isUndefined(other) && other !== null;
+ };
- }
+ Anything.prototype.jasmineToString = function() {
+ return '<jasmine.anything>';
+ };
- return CallTracker;
+ return Anything;
};
-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;
+getJasmineRequireObj().ArrayContaining = function(j$) {
+ function ArrayContaining(sample) {
+ this.sample = sample;
+ }
+ ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (!j$.isArray_(this.sample)) {
+ throw new Error('You must provide an array to arrayContaining, not ' + j$.pp(this.sample) + '.');
+ }
- self.install = function() {
- if(!originalTimingFunctionsIntact()) {
- throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the
clock already installed?');
+ // If the actual parameter is not an array, we can fail immediately, since it couldn't
+ // possibly be an "array containing" anything. However, we also want an empty sample
+ // array to match anything, so we need to double-check we aren't in that case
+ if (!j$.isArray_(other) && this.sample.length > 0) {
+ return false;
+ }
+
+ for (var i = 0; i < this.sample.length; i++) {
+ var item = this.sample[i];
+ if (!matchersUtil.contains(other, item)) {
+ return false;
}
- replace(global, fakeTimingFunctions);
- timer = fakeTimingFunctions;
- delayedFunctionScheduler = delayedFunctionSchedulerFactory();
- installed = true;
+ }
- return self;
- };
+ return true;
+ };
- self.uninstall = function() {
- delayedFunctionScheduler = null;
- mockDate.uninstall();
- replace(global, realTimingFunctions);
+ ArrayContaining.prototype.jasmineToString = function (pp) {
+ return '<jasmine.arrayContaining(' + pp(this.sample) +')>';
+ };
- timer = realTimingFunctions;
- installed = false;
- };
-
- self.withMock = function(closure) {
- this.install();
- try {
- closure();
- } finally {
- this.uninstall();
- }
- };
-
- self.mockDate = function(initialDate) {
- mockDate.install(initialDate);
- };
+ return ArrayContaining;
+};
- 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]);
- };
+getJasmineRequireObj().ArrayWithExactContents = function(j$) {
- 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]);
- };
+ function ArrayWithExactContents(sample) {
+ this.sample = sample;
+ }
- self.clearTimeout = function(id) {
- return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
- };
+ ArrayWithExactContents.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (!j$.isArray_(this.sample)) {
+ throw new Error('You must provide an array to arrayWithExactContents, not ' + j$.pp(this.sample) +
'.');
+ }
- self.clearInterval = function(id) {
- return Function.prototype.call.apply(timer.clearInterval, [global, id]);
- };
+ if (this.sample.length !== other.length) {
+ return false;
+ }
- 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()');
+ for (var i = 0; i < this.sample.length; i++) {
+ var item = this.sample[i];
+ if (!matchersUtil.contains(other, item)) {
+ return false;
}
- };
+ }
- return self;
+ return true;
+ };
- function originalTimingFunctionsIntact() {
- return global.setTimeout === realTimingFunctions.setTimeout &&
- global.clearTimeout === realTimingFunctions.clearTimeout &&
- global.setInterval === realTimingFunctions.setInterval &&
- global.clearInterval === realTimingFunctions.clearInterval;
- }
+ ArrayWithExactContents.prototype.jasmineToString = function(pp) {
+ return '<jasmine.arrayWithExactContents(' + pp(this.sample) + ')>';
+ };
- function legacyIE() {
- //if these methods are polyfilled, apply will be present
- return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
- }
+ return ArrayWithExactContents;
+};
- function replace(dest, source) {
- for (var prop in source) {
- dest[prop] = source[prop];
- }
- }
+getJasmineRequireObj().Empty = function (j$) {
- function setTimeout(fn, delay) {
- return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
- }
+ function Empty() {}
- function clearTimeout(id) {
- return delayedFunctionScheduler.removeFunctionWithId(id);
+ Empty.prototype.asymmetricMatch = function (other) {
+ if (j$.isString_(other) || j$.isArray_(other) || j$.isTypedArray_(other)) {
+ return other.length === 0;
}
- function setInterval(fn, interval) {
- return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+ if (j$.isMap(other) || j$.isSet(other)) {
+ return other.size === 0;
}
- function clearInterval(id) {
- return delayedFunctionScheduler.removeFunctionWithId(id);
+ if (j$.isObject_(other)) {
+ return Object.keys(other).length === 0;
}
+ return false;
+ };
- function argSlice(argsObj, n) {
- return Array.prototype.slice.call(argsObj, n);
- }
- }
+ Empty.prototype.jasmineToString = function () {
+ return '<jasmine.empty>';
+ };
- return Clock;
+ return Empty;
};
-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;
- }
+getJasmineRequireObj().Falsy = function(j$) {
- millis = millis || 0;
- timeoutKey = timeoutKey || ++delayedFnCount;
- runAtMillis = runAtMillis || (currentTime + millis);
+ function Falsy() {}
- var funcToSchedule = {
- runAtMillis: runAtMillis,
- funcToCall: f,
- recurring: recurring,
- params: params,
- timeoutKey: timeoutKey,
- millis: millis
- };
+ Falsy.prototype.asymmetricMatch = function(other) {
+ return !other;
+ };
- if (runAtMillis in scheduledFunctions) {
- scheduledFunctions[runAtMillis].push(funcToSchedule);
- } else {
- scheduledFunctions[runAtMillis] = [funcToSchedule];
- scheduledLookup.push(runAtMillis);
- scheduledLookup.sort(function (a, b) {
- return a - b;
- });
- }
+ Falsy.prototype.jasmineToString = function() {
+ return '<jasmine.falsy>';
+ };
- return timeoutKey;
- };
+ return Falsy;
+};
- self.removeFunctionWithId = function(timeoutKey) {
- for (var runAtMillis in scheduledFunctions) {
- var funcs = scheduledFunctions[runAtMillis];
- var i = indexOfFirstToPass(funcs, function (func) {
- return func.timeoutKey === timeoutKey;
- });
+getJasmineRequireObj().MapContaining = function(j$) {
+ function MapContaining(sample) {
+ if (!j$.isMap(sample)) {
+ throw new Error('You must provide a map to `mapContaining`, not ' + j$.pp(sample));
+ }
- if (i > -1) {
- if (funcs.length === 1) {
- delete scheduledFunctions[runAtMillis];
- deleteFromLookup(runAtMillis);
- } else {
- funcs.splice(i, 1);
- }
+ this.sample = sample;
+ }
- // intervals get rescheduled when executed, so there's never more
- // than a single scheduled function with a given timeoutKey
- break;
+ MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (!j$.isMap(other)) return false;
+
+ var hasAllMatches = true;
+ j$.util.forEachBreakable(this.sample, function(breakLoop, value, key) {
+ // for each key/value pair in `sample`
+ // there should be at least one pair in `other` whose key and value both match
+ var hasMatch = false;
+ j$.util.forEachBreakable(other, function(oBreakLoop, oValue, oKey) {
+ if (
+ matchersUtil.equals(oKey, key)
+ && matchersUtil.equals(oValue, value)
+ ) {
+ hasMatch = true;
+ oBreakLoop();
}
+ });
+ if (!hasMatch) {
+ hasAllMatches = false;
+ breakLoop();
}
- };
+ });
- return self;
+ return hasAllMatches;
+ };
- function indexOfFirstToPass(array, testFn) {
- var index = -1;
+ MapContaining.prototype.jasmineToString = function(pp) {
+ return '<jasmine.mapContaining(' + pp(this.sample) + ')>';
+ };
- for (var i = 0; i < array.length; ++i) {
- if (testFn(array[i])) {
- index = i;
- break;
- }
- }
+ return MapContaining;
+};
- return index;
- }
+getJasmineRequireObj().NotEmpty = function (j$) {
- function deleteFromLookup(key) {
- var value = Number(key);
- var i = indexOfFirstToPass(scheduledLookup, function (millis) {
- return millis === value;
- });
+ function NotEmpty() {}
- if (i > -1) {
- scheduledLookup.splice(i, 1);
- }
+ NotEmpty.prototype.asymmetricMatch = function (other) {
+ if (j$.isString_(other) || j$.isArray_(other) || j$.isTypedArray_(other)) {
+ return other.length !== 0;
}
- function reschedule(scheduledFn) {
- self.scheduleFunction(scheduledFn.funcToCall,
- scheduledFn.millis,
- scheduledFn.params,
- true,
- scheduledFn.timeoutKey,
- scheduledFn.runAtMillis + scheduledFn.millis);
+ if (j$.isMap(other) || j$.isSet(other)) {
+ return other.size !== 0;
}
- function forEachFunction(funcsToRun, callback) {
- for (var i = 0; i < funcsToRun.length; ++i) {
- callback(funcsToRun[i]);
- }
+ if (j$.isObject_(other)) {
+ return Object.keys(other).length !== 0;
}
- function runScheduledFunctions(endTime, tickDate) {
- tickDate = tickDate || function() {};
- if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
- tickDate(endTime - currentTime);
- return;
- }
+ return false;
+ };
- do {
- var newCurrentTime = scheduledLookup.shift();
- tickDate(newCurrentTime - currentTime);
+ NotEmpty.prototype.jasmineToString = function () {
+ return '<jasmine.notEmpty>';
+ };
- currentTime = newCurrentTime;
+ return NotEmpty;
+};
- var funcsToRun = scheduledFunctions[currentTime];
- delete scheduledFunctions[currentTime];
+getJasmineRequireObj().ObjectContaining = function(j$) {
- forEachFunction(funcsToRun, function(funcToRun) {
- if (funcToRun.recurring) {
- reschedule(funcToRun);
- }
- });
+ function ObjectContaining(sample) {
+ this.sample = sample;
+ }
- 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);
+ function getPrototype(obj) {
+ if (Object.getPrototypeOf) {
+ return Object.getPrototypeOf(obj);
+ }
- // ran out of functions to call, but still time left on the clock
- if (currentTime !== endTime) {
- tickDate(endTime - currentTime);
- }
+ if (obj.constructor.prototype == obj) {
+ return null;
}
+
+ return obj.constructor.prototype;
}
- return DelayedFunctionScheduler;
-};
+ function hasProperty(obj, property) {
+ if (!obj || typeof(obj) !== 'object') {
+ return false;
+ }
-getJasmineRequireObj().ExceptionFormatter = function() {
- function ExceptionFormatter() {
- this.message = function(error) {
- var message = '';
+ if (Object.prototype.hasOwnProperty.call(obj, property)) {
+ return true;
+ }
- if (error.name && error.message) {
- message += error.name + ': ' + error.message;
- } else {
- message += error.toString() + ' thrown';
- }
+ return hasProperty(getPrototype(obj), property);
+ }
- if (error.fileName || error.sourceURL) {
- message += ' in ' + (error.fileName || error.sourceURL);
- }
+ ObjectContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining,
not \''+this.sample+'\'.'); }
+ if (typeof(other) !== 'object') { return false; }
- if (error.line || error.lineNumber) {
- message += ' (line ' + (error.line || error.lineNumber) + ')';
+ for (var property in this.sample) {
+ if (!hasProperty(other, property) ||
+ !matchersUtil.equals(this.sample[property], other[property])) {
+ return false;
}
+ }
- return message;
- };
+ return true;
+ };
- this.stack = function(error) {
- return error ? error.stack : null;
+ ObjectContaining.prototype.valuesForDiff_ = function(other, pp) {
+ if (!j$.isObject_(other)) {
+ return {
+ self: this.jasmineToString(pp),
+ other: other
+ };
+ }
+
+ var filteredOther = {};
+ Object.keys(this.sample).forEach(function (k) {
+ // eq short-circuits comparison of objects that have different key sets,
+ // so include all keys even if undefined.
+ filteredOther[k] = other[k];
+ });
+
+ return {
+ self: this.sample,
+ other: filteredOther
};
- }
+ };
- return ExceptionFormatter;
+ ObjectContaining.prototype.jasmineToString = function(pp) {
+ return '<jasmine.objectContaining(' + pp(this.sample) + ')>';
+ };
+
+ return ObjectContaining;
};
-getJasmineRequireObj().Expectation = function() {
+getJasmineRequireObj().SetContaining = function(j$) {
+ function SetContaining(sample) {
+ if (!j$.isSet(sample)) {
+ throw new Error('You must provide a set to `setContaining`, not ' + j$.pp(sample));
+ }
- 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]);
- }
+ this.sample = sample;
}
- 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);
+ SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
+ if (!j$.isSet(other)) return false;
+
+ var hasAllMatches = true;
+ j$.util.forEachBreakable(this.sample, function(breakLoop, item) {
+ // for each item in `sample` there should be at least one matching item in `other`
+ // (not using `matchersUtil.contains` because it compares set members by reference,
+ // not by deep value equality)
+ var hasMatch = false;
+ j$.util.forEachBreakable(other, function(oBreakLoop, oItem) {
+ if (matchersUtil.equals(oItem, item)) {
+ hasMatch = true;
+ oBreakLoop();
+ }
+ });
+ if (!hasMatch) {
+ hasAllMatches = false;
+ breakLoop();
+ }
+ });
- var matcher = matcherFactory(this.util, this.customEqualityTesters),
- matcherCompare = matcher.compare;
+ return hasAllMatches;
+ };
- function defaultNegativeCompare() {
- var result = matcher.compare.apply(null, args);
- result.pass = !result.pass;
- return result;
- }
+ SetContaining.prototype.jasmineToString = function(pp) {
+ return '<jasmine.setContaining(' + pp(this.sample) + ')>';
+ };
- if (this.isNot) {
- matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
- }
+ return SetContaining;
+};
- var result = matcherCompare.apply(null, args);
+getJasmineRequireObj().StringMatching = function(j$) {
- 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;
- }
- }
- }
+ function StringMatching(expected) {
+ if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+ throw new Error('Expected is not a String or a RegExp');
+ }
- if (expected.length == 1) {
- expected = expected[0];
- }
+ this.regexp = new RegExp(expected);
+ }
- // 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
- }
- );
- };
+ StringMatching.prototype.asymmetricMatch = function(other) {
+ return this.regexp.test(other);
};
- Expectation.addCoreMatchers = function(matchers) {
- var prototype = Expectation.prototype;
- for (var matcherName in matchers) {
- var matcher = matchers[matcherName];
- prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
- }
+ StringMatching.prototype.jasmineToString = function() {
+ return '<jasmine.stringMatching(' + this.regexp + ')>';
};
- Expectation.Factory = function(options) {
- options = options || {};
+ return StringMatching;
+};
- var expect = new Expectation(options);
+getJasmineRequireObj().Truthy = function(j$) {
- // TODO: this would be nice as its own Object - NegativeExpectation
- // TODO: copy instead of mutate options
- options.isNot = true;
- expect.not = new Expectation(options);
+ function Truthy() {}
- return expect;
+ Truthy.prototype.asymmetricMatch = function(other) {
+ return !!other;
};
- 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() {};
+ Truthy.prototype.jasmineToString = function() {
+ return '<jasmine.truthy>';
+ };
- var result = {
- matcherName: options.matcherName,
- message: message(),
- stack: stack(),
- passed: options.passed
- };
+ return Truthy;
+};
- if(!result.passed) {
- result.expected = options.expected;
- result.actual = options.actual;
+getJasmineRequireObj().asymmetricEqualityTesterArgCompatShim = function(j$) {
+ /*
+ Older versions of Jasmine passed an array of custom equality testers as the
+ second argument to each asymmetric equality tester's `asymmetricMatch`
+ method. Newer versions will pass a `MatchersUtil` instance. The
+ asymmetricEqualityTesterArgCompatShim allows for a graceful migration from
+ the old interface to the new by "being" both an array of custom equality
+ testers and a `MatchersUtil` at the same time.
+
+ This code should be removed in the next major release.
+ */
+
+ var likelyArrayProps = [
+ 'concat',
+ 'constructor',
+ 'copyWithin',
+ 'entries',
+ 'every',
+ 'fill',
+ 'filter',
+ 'find',
+ 'findIndex',
+ 'flat',
+ 'flatMap',
+ 'forEach',
+ 'includes',
+ 'indexOf',
+ 'join',
+ 'keys',
+ 'lastIndexOf',
+ 'length',
+ 'map',
+ 'pop',
+ 'push',
+ 'reduce',
+ 'reduceRight',
+ 'reverse',
+ 'shift',
+ 'slice',
+ 'some',
+ 'sort',
+ 'splice',
+ 'toLocaleString',
+ 'toSource',
+ 'toString',
+ 'unshift',
+ 'values'
+ ];
+
+ function asymmetricEqualityTesterArgCompatShim(
+ matchersUtil,
+ customEqualityTesters
+ ) {
+ var self = Object.create(matchersUtil),
+ props,
+ i,
+ k;
+
+ copy(self, customEqualityTesters, 'length');
+
+ for (i = 0; i < customEqualityTesters.length; i++) {
+ copy(self, customEqualityTesters, i);
}
- return result;
+ var props = arrayProps();
- function message() {
- if (options.passed) {
- return 'Passed.';
- } else if (options.message) {
- return options.message;
- } else if (options.error) {
- return messageFormatter(options.error);
+ for (i = 0; i < props.length; i++) {
+ k = props[i];
+ if (k !== 'length') {
+ copy(self, Array.prototype, k);
}
- return '';
}
- function stack() {
- if (options.passed) {
- return '';
- }
+ return self;
+ }
- var error = options.error;
- if (!error) {
- try {
- throw new Error(message());
- } catch (e) {
- error = e;
- }
+ function copy(dest, src, propName) {
+ Object.defineProperty(dest, propName, {
+ get: function() {
+ return src[propName];
}
- return stackFormatter(error);
- }
+ });
}
- return buildExpectationResult;
-};
+ function arrayProps() {
+ var props, a, k;
-getJasmineRequireObj().MockDate = function() {
- function MockDate(global) {
- var self = this;
- var currentTime = 0;
+ if (!Object.getOwnPropertyDescriptors) {
+ return likelyArrayProps.filter(function(k) {
+ return Array.prototype.hasOwnProperty(k);
+ });
+ }
- if (!global || !global.Date) {
- self.install = function() {};
- self.tick = function() {};
- self.uninstall = function() {};
- return self;
+ props = Object.getOwnPropertyDescriptors(Array.prototype); // eslint-disable-line compat/compat
+ a = [];
+
+ for (k in props) {
+ a.push(k);
}
- var GlobalDate = global.Date;
+ return a;
+ }
- self.install = function(mockDate) {
- if (mockDate instanceof GlobalDate) {
- currentTime = mockDate.getTime();
- } else {
- currentTime = new GlobalDate().getTime();
- }
+ return asymmetricEqualityTesterArgCompatShim;
+};
- global.Date = FakeDate;
+getJasmineRequireObj().CallTracker = function(j$) {
+ /**
+ * @namespace Spy#calls
+ * @since 2.0.0
+ */
+ function CallTracker() {
+ var calls = [];
+ var opts = {};
+
+ this.track = function(context) {
+ if (opts.cloneArgs) {
+ context.args = j$.util.cloneArgs(context.args);
+ }
+ calls.push(context);
};
- self.tick = function(millis) {
- millis = millis || 0;
- currentTime = currentTime + millis;
+ /**
+ * Check whether this spy has been invoked.
+ * @name Spy#calls#any
+ * @since 2.0.0
+ * @function
+ * @return {Boolean}
+ */
+ this.any = function() {
+ return !!calls.length;
};
- self.uninstall = function() {
- currentTime = 0;
- global.Date = GlobalDate;
+ /**
+ * Get the number of invocations of this spy.
+ * @name Spy#calls#count
+ * @since 2.0.0
+ * @function
+ * @return {Integer}
+ */
+ this.count = function() {
+ return calls.length;
};
- createDateProperties();
+ /**
+ * Get the arguments that were passed to a specific invocation of this spy.
+ * @name Spy#calls#argsFor
+ * @since 2.0.0
+ * @function
+ * @param {Integer} index The 0-based invocation index.
+ * @return {Array}
+ */
+ this.argsFor = function(index) {
+ var call = calls[index];
+ return call ? call.args : [];
+ };
- return self;
+ /**
+ * Get the raw calls array for this spy.
+ * @name Spy#calls#all
+ * @since 2.0.0
+ * @function
+ * @return {Spy.callData[]}
+ */
+ this.all = function() {
+ return calls;
+ };
- 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]);
+ /**
+ * Get all of the arguments for each invocation of this spy in the order they were received.
+ * @name Spy#calls#allArgs
+ * @since 2.0.0
+ * @function
+ * @return {Array}
+ */
+ this.allArgs = function() {
+ var callArgs = [];
+ for (var i = 0; i < calls.length; i++) {
+ callArgs.push(calls[i].args);
}
- }
- function createDateProperties() {
- FakeDate.prototype = GlobalDate.prototype;
+ return callArgs;
+ };
- FakeDate.now = function() {
- if (GlobalDate.now) {
- return currentTime;
- } else {
- throw new Error('Browser does not support Date.now()');
- }
- };
+ /**
+ * Get the first invocation of this spy.
+ * @name Spy#calls#first
+ * @since 2.0.0
+ * @function
+ * @return {ObjecSpy.callData}
+ */
+ this.first = function() {
+ return calls[0];
+ };
- FakeDate.toSource = GlobalDate.toSource;
- FakeDate.toString = GlobalDate.toString;
- FakeDate.parse = GlobalDate.parse;
- FakeDate.UTC = GlobalDate.UTC;
- }
- }
+ /**
+ * Get the most recent invocation of this spy.
+ * @name Spy#calls#mostRecent
+ * @since 2.0.0
+ * @function
+ * @return {ObjecSpy.callData}
+ */
+ this.mostRecent = function() {
+ return calls[calls.length - 1];
+ };
- return MockDate;
+ /**
+ * Reset this spy as if it has never been called.
+ * @name Spy#calls#reset
+ * @since 2.0.0
+ * @function
+ */
+ this.reset = function() {
+ calls = [];
+ };
+
+ /**
+ * Set this spy to do a shallow clone of arguments passed to each invocation.
+ * @name Spy#calls#saveArgumentsByValue
+ * @since 2.5.0
+ * @function
+ */
+ this.saveArgumentsByValue = function() {
+ opts.cloneArgs = true;
+ };
+ }
+
+ return CallTracker;
};
-getJasmineRequireObj().pp = function(j$) {
+getJasmineRequireObj().clearStack = function(j$) {
+ var maxInlineCallCount = 10;
- function PrettyPrinter() {
- this.ppNestLevel_ = 0;
- this.seen = [];
- }
+ function messageChannelImpl(global, setTimeout) {
+ var channel = new global.MessageChannel(),
+ head = {},
+ tail = head;
- 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();
+ var taskRunning = false;
+ channel.port1.onmessage = function() {
+ head = head.next;
+ var task = head.task;
+ delete head.task;
+
+ if (taskRunning) {
+ global.setTimeout(task, 0);
} else {
- this.emitScalar(value.toString());
+ try {
+ taskRunning = true;
+ task();
+ } finally {
+ taskRunning = false;
+ }
}
- } 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);
+ var currentCallCount = 0;
+ return function clearStack(fn) {
+ currentCallCount++;
- this.string = '';
+ if (currentCallCount < maxInlineCallCount) {
+ tail = tail.next = { task: fn };
+ channel.port2.postMessage(0);
+ } else {
+ currentCallCount = 0;
+ setTimeout(fn);
+ }
+ };
}
- j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+ function getClearStack(global) {
+ var currentCallCount = 0;
+ var realSetTimeout = global.setTimeout;
+ var setTimeoutImpl = function clearStack(fn) {
+ Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]);
+ };
- StringPrettyPrinter.prototype.emitScalar = function(value) {
- this.append(value);
- };
+ if (j$.isFunction_(global.setImmediate)) {
+ var realSetImmediate = global.setImmediate;
+ return function(fn) {
+ currentCallCount++;
- StringPrettyPrinter.prototype.emitString = function(value) {
- this.append('\'' + value + '\'');
- };
+ if (currentCallCount < maxInlineCallCount) {
+ realSetImmediate(fn);
+ } else {
+ currentCallCount = 0;
- 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(', ...');
+ setTimeoutImpl(fn);
+ }
+ };
+ } else if (!j$.util.isUndefined(global.MessageChannel)) {
+ return messageChannelImpl(global, setTimeoutImpl);
+ } else {
+ return setTimeoutImpl;
}
+ }
- 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(', ');
- }
+ return getClearStack;
+};
- self.formatProperty(obj, property, isGetter);
- });
+getJasmineRequireObj().Clock = function() {
+ /* global process */
+ var NODE_JS =
+ typeof process !== 'undefined' &&
+ process.versions &&
+ typeof process.versions.node === 'string';
+
+ /**
+ * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current
clock with {@link jasmine.clock}.
+ * @class Clock
+ * @classdesc Jasmine's mock clock is used when testing time dependent code.
+ */
+ 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;
- this.append(' })');
- };
+ self.FakeTimeout = FakeTimeout;
- StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) {
- this.append(property);
- this.append(': ');
- if (isGetter) {
- this.append('<getter>');
- } else {
- this.format(obj[property]);
+ /**
+ * Install the mock clock over the built-in methods.
+ * @name Clock#install
+ * @since 2.0.0
+ * @function
+ * @return {Clock}
+ */
+ 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;
- StringPrettyPrinter.prototype.append = function(value) {
- this.string += value;
- };
+ return self;
+ };
- return function(value) {
- var stringPrettyPrinter = new StringPrettyPrinter();
- stringPrettyPrinter.format(value);
- return stringPrettyPrinter.string;
- };
-};
+ /**
+ * Uninstall the mock clock, returning the built-in methods to their places.
+ * @name Clock#uninstall
+ * @since 2.0.0
+ * @function
+ */
+ self.uninstall = function() {
+ delayedFunctionScheduler = null;
+ mockDate.uninstall();
+ replace(global, realTimingFunctions);
-getJasmineRequireObj().QueueRunner = function(j$) {
+ timer = realTimingFunctions;
+ installed = false;
+ };
- function once(fn) {
- var called = false;
- return function() {
- if (!called) {
- called = true;
- fn();
+ /**
+ * Execute a function with a mocked Clock
+ *
+ * The clock will be {@link Clock#install|install}ed before the function is called and {@link
Clock#uninstall|uninstall}ed in a `finally` after the function completes.
+ * @name Clock#withMock
+ * @since 2.3.0
+ * @function
+ * @param {Function} closure The function to be called.
+ */
+ self.withMock = function(closure) {
+ this.install();
+ try {
+ closure();
+ } finally {
+ this.uninstall();
}
- 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() {};
- }
+ /**
+ * Instruct the installed Clock to also mock the date returned by `new Date()`
+ * @name Clock#mockDate
+ * @since 2.1.0
+ * @function
+ * @param {Date} [initialDate=now] The `Date` to provide.
+ */
+ self.mockDate = function(initialDate) {
+ mockDate.install(initialDate);
+ };
- QueueRunner.prototype.execute = function() {
- this.run(this.queueableFns, 0);
- };
+ self.setTimeout = function(fn, delay, params) {
+ return Function.prototype.apply.apply(timer.setTimeout, [
+ global,
+ arguments
+ ]);
+ };
- QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
- var length = queueableFns.length,
- self = this,
- iterativeIndex;
+ self.setInterval = function(fn, delay, params) {
+ 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]);
+ };
- for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
- var queueableFn = queueableFns[iterativeIndex];
- if (queueableFn.fn.length > 0) {
- attemptAsync(queueableFn);
- return;
+ /**
+ * Tick the Clock forward, running any enqueued timeouts along the way
+ * @name Clock#tick
+ * @since 1.3.0
+ * @function
+ * @param {int} millis The number of milliseconds to tick.
+ */
+ self.tick = function(millis) {
+ if (installed) {
+ delayedFunctionScheduler.tick(millis, function(millis) {
+ mockDate.tick(millis);
+ });
} else {
- attemptSync(queueableFn);
+ throw new Error(
+ 'Mock clock is not installed, use jasmine.clock().install()'
+ );
}
- }
+ };
- var runnerDone = iterativeIndex >= length;
+ return self;
- if (runnerDone) {
- this.clearStack(this.onComplete);
+ function originalTimingFunctionsIntact() {
+ return (
+ global.setTimeout === realTimingFunctions.setTimeout &&
+ global.clearTimeout === realTimingFunctions.clearTimeout &&
+ global.setInterval === realTimingFunctions.setInterval &&
+ global.clearInterval === realTimingFunctions.clearInterval
+ );
}
- function attemptSync(queueableFn) {
- try {
- queueableFn.fn.call(self.userContext);
- } catch (e) {
- handleException(e, queueableFn);
+ function replace(dest, source) {
+ for (var prop in source) {
+ dest[prop] = source[prop];
}
}
- 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()]]);
- }
+ function setTimeout(fn, delay) {
+ if (!NODE_JS) {
+ return delayedFunctionScheduler.scheduleFunction(
+ fn,
+ delay,
+ argSlice(arguments, 2)
+ );
+ }
+
+ var timeout = new FakeTimeout();
+
+ delayedFunctionScheduler.scheduleFunction(
+ fn,
+ delay,
+ argSlice(arguments, 2),
+ false,
+ timeout
+ );
- try {
- queueableFn.fn.call(self.userContext, next);
- } catch (e) {
- handleException(e, queueableFn);
- next();
- }
+ return timeout;
}
- function onException(e) {
- self.onException(e);
+ function clearTimeout(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
}
- 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;
+ function setInterval(fn, interval) {
+ if (!NODE_JS) {
+ return delayedFunctionScheduler.scheduleFunction(
+ fn,
+ interval,
+ argSlice(arguments, 2),
+ true
+ );
}
- }
- };
-
- return QueueRunner;
-};
-getJasmineRequireObj().ReportDispatcher = function() {
- function ReportDispatcher(methods) {
+ var timeout = new FakeTimeout();
- var dispatchedMethods = methods || [];
+ delayedFunctionScheduler.scheduleFunction(
+ fn,
+ interval,
+ argSlice(arguments, 2),
+ true,
+ timeout
+ );
- for (var i = 0; i < dispatchedMethods.length; i++) {
- var method = dispatchedMethods[i];
- this[method] = (function(m) {
- return function() {
- dispatch(m, arguments);
- };
- }(method));
+ return timeout;
}
- var reporters = [];
- var fallbackReporter = null;
-
- this.addReporter = function(reporter) {
- reporters.push(reporter);
- };
+ function clearInterval(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
- this.provideFallbackReporter = function(reporter) {
- fallbackReporter = reporter;
- };
+ function argSlice(argsObj, n) {
+ return Array.prototype.slice.call(argsObj, n);
+ }
+ }
- this.clearReporters = function() {
- reporters = [];
- };
+ /**
+ * Mocks Node.js Timeout class
+ */
+ function FakeTimeout() {}
+ FakeTimeout.prototype.ref = function() {
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);
- }
- }
- }
- }
+ FakeTimeout.prototype.unref = function() {
+ return this;
+ };
- return ReportDispatcher;
+ return Clock;
};
+getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
+ function DelayedFunctionScheduler() {
+ var self = this;
+ var scheduledLookup = [];
+ var scheduledFunctions = {};
+ var currentTime = 0;
+ var delayedFnCount = 0;
+ var deletedKeys = [];
-getJasmineRequireObj().SpyRegistry = function(j$) {
-
- var getErrorMsg = j$.formatErrorMsg('<spyOn>', 'spyOn(<object>, <methodName>)');
-
- function SpyRegistry(options) {
- options = options || {};
- var currentSpies = options.currentSpies || function() { return []; };
+ self.tick = function(millis, tickDate) {
+ millis = millis || 0;
+ var endTime = currentTime + millis;
- this.allowRespy = function(allow){
- this.respy = allow;
+ runScheduledFunctions(endTime, tickDate);
+ currentTime = endTime;
};
- this.spyOn = function(obj, methodName) {
-
- if (j$.util.isUndefined(obj)) {
- throw new Error(getErrorMsg('could not find an object to spy upon for ' + methodName + '()'));
+ 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;
}
- if (j$.util.isUndefined(methodName)) {
- throw new Error(getErrorMsg('No method name supplied'));
- }
+ millis = millis || 0;
+ timeoutKey = timeoutKey || ++delayedFnCount;
+ runAtMillis = runAtMillis || currentTime + millis;
- if (j$.util.isUndefined(obj[methodName])) {
- throw new Error(getErrorMsg(methodName + '() method does not exist'));
- }
+ var funcToSchedule = {
+ runAtMillis: runAtMillis,
+ funcToCall: f,
+ recurring: recurring,
+ params: params,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
- if (obj[methodName] && j$.isSpy(obj[methodName]) ) {
- if ( !!this.respy ){
- return obj[methodName];
- }else {
- throw new Error(getErrorMsg(methodName + ' has already been spied upon'));
- }
+ if (runAtMillis in scheduledFunctions) {
+ scheduledFunctions[runAtMillis].push(funcToSchedule);
+ } else {
+ scheduledFunctions[runAtMillis] = [funcToSchedule];
+ scheduledLookup.push(runAtMillis);
+ scheduledLookup.sort(function(a, b) {
+ return a - b;
+ });
}
- var descriptor;
- try {
- descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
- } catch(e) {
- // IE 8 doesn't support `definePropery` on non-DOM nodes
- }
+ return timeoutKey;
+ };
- if (descriptor && !(descriptor.writable || descriptor.set)) {
- throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter'));
- }
+ self.removeFunctionWithId = function(timeoutKey) {
+ deletedKeys.push(timeoutKey);
- var originalMethod = obj[methodName],
- spiedMethod = j$.createSpy(methodName, originalMethod),
- restoreStrategy;
+ for (var runAtMillis in scheduledFunctions) {
+ var funcs = scheduledFunctions[runAtMillis];
+ var i = indexOfFirstToPass(funcs, function(func) {
+ return func.timeoutKey === timeoutKey;
+ });
- if (Object.prototype.hasOwnProperty.call(obj, methodName)) {
- restoreStrategy = function() {
- obj[methodName] = originalMethod;
- };
- } else {
- restoreStrategy = function() {
- if (!delete obj[methodName]) {
- obj[methodName] = originalMethod;
+ if (i > -1) {
+ if (funcs.length === 1) {
+ delete scheduledFunctions[runAtMillis];
+ deleteFromLookup(runAtMillis);
+ } else {
+ funcs.splice(i, 1);
}
- };
- }
- currentSpies().push({
- restoreObjectToOriginalState: restoreStrategy
- });
+ // intervals get rescheduled when executed, so there's never more
+ // than a single scheduled function with a given timeoutKey
+ break;
+ }
+ }
+ };
- obj[methodName] = spiedMethod;
+ return self;
- return spiedMethod;
- };
+ function indexOfFirstToPass(array, testFn) {
+ var index = -1;
- this.clearSpies = function() {
- var spies = currentSpies();
- for (var i = spies.length - 1; i >= 0; i--) {
- var spyEntry = spies[i];
- spyEntry.restoreObjectToOriginalState();
+ for (var i = 0; i < array.length; ++i) {
+ if (testFn(array[i])) {
+ index = i;
+ break;
+ }
}
- };
- }
- return SpyRegistry;
-};
+ return index;
+ }
-getJasmineRequireObj().SpyStrategy = function(j$) {
+ function deleteFromLookup(key) {
+ var value = Number(key);
+ var i = indexOfFirstToPass(scheduledLookup, function(millis) {
+ return millis === value;
+ });
- function SpyStrategy(options) {
- options = options || {};
+ if (i > -1) {
+ scheduledLookup.splice(i, 1);
+ }
+ }
- var identity = options.name || 'unknown',
- originalFn = options.fn || function() {},
- getSpy = options.getSpy || function() {},
- plan = function() {};
+ function reschedule(scheduledFn) {
+ self.scheduleFunction(
+ scheduledFn.funcToCall,
+ scheduledFn.millis,
+ scheduledFn.params,
+ true,
+ scheduledFn.timeoutKey,
+ scheduledFn.runAtMillis + scheduledFn.millis
+ );
+ }
- this.identity = function() {
- return identity;
- };
+ function forEachFunction(funcsToRun, callback) {
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ callback(funcsToRun[i]);
+ }
+ }
- this.exec = function() {
- return plan.apply(this, arguments);
- };
+ function runScheduledFunctions(endTime, tickDate) {
+ tickDate = tickDate || function() {};
+ if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+ tickDate(endTime - currentTime);
+ return;
+ }
- this.callThrough = function() {
- plan = originalFn;
- return getSpy();
- };
+ do {
+ deletedKeys = [];
+ var newCurrentTime = scheduledLookup.shift();
+ tickDate(newCurrentTime - currentTime);
- this.returnValue = function(value) {
- plan = function() {
- return value;
- };
- return getSpy();
- };
+ currentTime = newCurrentTime;
- this.returnValues = function() {
- var values = Array.prototype.slice.call(arguments);
- plan = function () {
- return values.shift();
- };
- return getSpy();
- };
+ var funcsToRun = scheduledFunctions[currentTime];
- this.throwError = function(something) {
- var error = (something instanceof Error) ? something : new Error(something);
- plan = function() {
- throw error;
- };
- return getSpy();
- };
+ delete scheduledFunctions[currentTime];
- 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();
- };
+ forEachFunction(funcsToRun, function(funcToRun) {
+ if (funcToRun.recurring) {
+ reschedule(funcToRun);
+ }
+ });
- this.stub = function(fn) {
- plan = function() {};
- return getSpy();
- };
+ forEachFunction(funcsToRun, function(funcToRun) {
+ if (j$.util.arrayContains(deletedKeys, funcToRun.timeoutKey)) {
+ // skip a timeoutKey deleted whilst we were running
+ return;
+ }
+ funcToRun.funcToCall.apply(null, funcToRun.params || []);
+ });
+ deletedKeys = [];
+ } 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 SpyStrategy;
+ return DelayedFunctionScheduler;
};
-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;
+getJasmineRequireObj().errors = function() {
+ function ExpectationFailed() {}
- this.beforeFns = [];
- this.afterFns = [];
- this.beforeAllFns = [];
- this.afterAllFns = [];
- this.disabled = false;
+ ExpectationFailed.prototype = new Error();
+ ExpectationFailed.prototype.constructor = ExpectationFailed;
- this.children = [];
+ return {
+ ExpectationFailed: ExpectationFailed
+ };
+};
- this.result = {
- id: this.id,
- description: this.description,
- fullName: this.getFullName(),
- failedExpectations: []
- };
- }
+getJasmineRequireObj().ExceptionFormatter = function(j$) {
+ var ignoredProperties = [
+ 'name',
+ 'message',
+ 'stack',
+ 'fileName',
+ 'sourceURL',
+ 'line',
+ 'lineNumber',
+ 'column',
+ 'description',
+ 'jasmineMessage'
+ ];
+
+ function ExceptionFormatter(options) {
+ var jasmineFile = (options && options.jasmineFile) || j$.util.jasmineFile();
+ this.message = function(error) {
+ var message = '';
- Suite.prototype.expect = function(actual) {
- return this.expectationFactory(actual, this);
- };
+ if (error.jasmineMessage) {
+ message += error.jasmineMessage;
+ } else if (error.name && error.message) {
+ message += error.name + ': ' + error.message;
+ } else if (error.message) {
+ message += error.message;
+ } else {
+ message += error.toString() + ' thrown';
+ }
- Suite.prototype.getFullName = function() {
- var fullName = [];
- for (var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite) {
- if (parentSuite.parentSuite) {
- fullName.unshift(parentSuite.description);
+ if (error.fileName || error.sourceURL) {
+ message += ' in ' + (error.fileName || error.sourceURL);
}
- }
- return fullName.join(' ');
- };
- Suite.prototype.disable = function() {
- this.disabled = true;
- };
+ if (error.line || error.lineNumber) {
+ message += ' (line ' + (error.line || error.lineNumber) + ')';
+ }
- Suite.prototype.pend = function(message) {
- this.markedPending = true;
- };
+ return message;
+ };
- Suite.prototype.beforeEach = function(fn) {
- this.beforeFns.unshift(fn);
- };
+ this.stack = function(error) {
+ if (!error || !error.stack) {
+ return null;
+ }
- Suite.prototype.beforeAll = function(fn) {
- this.beforeAllFns.push(fn);
- };
+ var stackTrace = new j$.StackTrace(error);
+ var lines = filterJasmine(stackTrace);
+ var result = '';
- Suite.prototype.afterEach = function(fn) {
- this.afterFns.unshift(fn);
- };
+ if (stackTrace.message) {
+ lines.unshift(stackTrace.message);
+ }
- Suite.prototype.afterAll = function(fn) {
- this.afterAllFns.push(fn);
- };
+ result += formatProperties(error);
+ result += lines.join('\n');
- Suite.prototype.addChild = function(child) {
- this.children.push(child);
- };
+ return result;
+ };
- Suite.prototype.status = function() {
- if (this.disabled) {
- return 'disabled';
- }
+ function filterJasmine(stackTrace) {
+ var result = [],
+ jasmineMarker =
+ stackTrace.style === 'webkit' ? '<Jasmine>' : ' at <Jasmine>';
+
+ stackTrace.frames.forEach(function(frame) {
+ if (frame.file && frame.file !== jasmineFile) {
+ result.push(frame.raw);
+ } else if (result[result.length - 1] !== jasmineMarker) {
+ result.push(jasmineMarker);
+ }
+ });
- if (this.markedPending) {
- return 'pending';
+ return result;
}
- if (this.result.failedExpectations.length > 0) {
- return 'failed';
- } else {
- return 'finished';
- }
- };
+ function formatProperties(error) {
+ if (!(error instanceof Object)) {
+ return;
+ }
- Suite.prototype.isExecutable = function() {
- return !this.disabled;
- };
+ var result = {};
+ var empty = true;
- Suite.prototype.canBeReentered = function() {
- return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
- };
+ for (var prop in error) {
+ if (j$.util.arrayContains(ignoredProperties, prop)) {
+ continue;
+ }
+ result[prop] = error[prop];
+ empty = false;
+ }
- Suite.prototype.getResult = function() {
- this.result.status = this.status();
- return this.result;
- };
+ if (!empty) {
+ return 'error properties: ' + j$.pp(result) + '\n';
+ }
- Suite.prototype.sharedUserContext = function() {
- if (!this.sharedContext) {
- this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
+ return '';
}
+ }
- return this.sharedContext;
- };
+ return ExceptionFormatter;
+};
- Suite.prototype.clonedSharedUserContext = function() {
- return clone(this.sharedUserContext());
- };
+getJasmineRequireObj().Expectation = function(j$) {
+ /**
+ * Matchers that come with Jasmine out of the box.
+ * @namespace matchers
+ */
+ function Expectation(options) {
+ this.expector = new j$.Expector(options);
- Suite.prototype.onException = function() {
- if (arguments[0] instanceof j$.errors.ExpectationFailed) {
- return;
+ var customMatchers = options.customMatchers || {};
+ for (var matcherName in customMatchers) {
+ this[matcherName] = wrapSyncCompare(
+ matcherName,
+ customMatchers[matcherName]
+ );
}
+ }
- 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);
- }
- }
+ /**
+ * Add some context for an {@link expect}
+ * @function
+ * @name matchers#withContext
+ * @since 3.3.0
+ * @param {String} message - Additional context to show when the matcher fails
+ * @return {matchers}
+ */
+ Expectation.prototype.withContext = function withContext(message) {
+ return addFilter(this, new ContextAddingFilter(message));
};
- 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
- }
- }
+ /**
+ * Invert the matcher following this {@link expect}
+ * @member
+ * @name matchers#not
+ * @since 1.3.0
+ * @type {matchers}
+ * @example
+ * expect(something).not.toBe(true);
+ */
+ Object.defineProperty(Expectation.prototype, 'not', {
+ get: function() {
+ return addFilter(this, syncNegatingFilter);
}
- };
+ });
- function isAfterAll(children) {
- return children && children[0].result.status;
- }
-
- function isFailure(args) {
- return !args[0];
- }
+ /**
+ * Asynchronous matchers.
+ * @namespace async-matchers
+ */
+ function AsyncExpectation(options) {
+ var global = options.global || j$.getGlobal();
+ this.expector = new j$.Expector(options);
- function clone(obj) {
- var clonedObj = {};
- for (var prop in obj) {
- if (obj.hasOwnProperty(prop)) {
- clonedObj[prop] = obj[prop];
- }
+ if (!global.Promise) {
+ throw new Error(
+ 'expectAsync is unavailable because the environment does not support promises.'
+ );
}
- return clonedObj;
+ var customAsyncMatchers = options.customAsyncMatchers || {};
+ for (var matcherName in customAsyncMatchers) {
+ this[matcherName] = wrapAsyncCompare(
+ matcherName,
+ customAsyncMatchers[matcherName]
+ );
+ }
}
- 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;
+ /**
+ * Add some context for an {@link expectAsync}
+ * @function
+ * @name async-matchers#withContext
+ * @since 3.3.0
+ * @param {String} message - Additional context to show when the async matcher fails
+ * @return {async-matchers}
+ */
+ AsyncExpectation.prototype.withContext = function withContext(message) {
+ return addFilter(this, new ContextAddingFilter(message));
+ };
- this.start = function() {
- startTime = now();
- };
+ /**
+ * Invert the matcher following this {@link expectAsync}
+ * @member
+ * @name async-matchers#not
+ * @type {async-matchers}
+ * @example
+ * await expectAsync(myPromise).not.toBeResolved();
+ * @example
+ * return expectAsync(myPromise).not.toBeResolved();
+ */
+ Object.defineProperty(AsyncExpectation.prototype, 'not', {
+ get: function() {
+ return addFilter(this, asyncNegatingFilter);
+ }
+ });
- this.elapsed = function() {
- return now() - startTime;
+ function wrapSyncCompare(name, matcherFactory) {
+ return function() {
+ var result = this.expector.compare(name, matcherFactory, arguments);
+ this.expector.processResult(result);
};
}
- return Timer;
-};
+ function wrapAsyncCompare(name, matcherFactory) {
+ return function() {
+ var self = this;
-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;
+ // Capture the call stack here, before we go async, so that it will contain
+ // frames that are relevant to the user instead of just parts of Jasmine.
+ var errorForStack = j$.util.errorWithStack();
- this.processTree = function() {
- processNode(tree, false);
- processed = true;
- return stats;
+ return this.expector
+ .compare(name, matcherFactory, arguments)
+ .then(function(result) {
+ self.expector.processResult(result, errorForStack);
+ });
};
+ }
- this.execute = function(done) {
- if (!processed) {
- this.processTree();
- }
+ function addCoreMatchers(prototype, matchers, wrapper) {
+ for (var matcherName in matchers) {
+ var matcher = matchers[matcherName];
+ prototype[matcherName] = wrapper(matcherName, matcher);
+ }
+ }
- if (!stats.valid) {
- throw 'invalid order';
+ function addFilter(source, filter) {
+ var result = Object.create(source);
+ result.expector = source.expector.addFilter(filter);
+ return result;
+ }
+
+ function negatedFailureMessage(result, matcherName, args, matchersUtil) {
+ if (result.message) {
+ if (j$.isFunction_(result.message)) {
+ return result.message();
+ } else {
+ return result.message;
}
+ }
- var childFns = wrapChildren(tree, 0);
+ args = args.slice();
+ args.unshift(true);
+ args.unshift(matcherName);
+ return matchersUtil.buildFailureMessage.apply(matchersUtil, args);
+ }
- queueRunnerFactory({
- queueableFns: childFns,
- userContext: tree.sharedUserContext(),
- onException: function() {
- tree.onException.apply(tree, arguments);
- },
- onComplete: done
- });
- };
+ function negate(result) {
+ result.pass = !result.pass;
+ return result;
+ }
- function runnableIndex(id) {
- for (var i = 0; i < runnableIds.length; i++) {
- if (runnableIds[i] === id) {
- return i;
- }
+ var syncNegatingFilter = {
+ selectComparisonFunc: function(matcher) {
+ function defaultNegativeCompare() {
+ return negate(matcher.compare.apply(null, arguments));
}
- }
- function processNode(node, parentEnabled) {
- var executableIndex = runnableIndex(node.id);
+ return matcher.negativeCompare || defaultNegativeCompare;
+ },
+ buildFailureMessage: negatedFailureMessage
+ };
- if (executableIndex !== undefined) {
- parentEnabled = true;
+ var asyncNegatingFilter = {
+ selectComparisonFunc: function(matcher) {
+ function defaultNegativeCompare() {
+ return matcher.compare.apply(this, arguments).then(negate);
}
- 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;
+ return matcher.negativeCompare || defaultNegativeCompare;
+ },
+ buildFailureMessage: negatedFailureMessage
+ };
- var orderedChildren = orderChildren(node);
+ function ContextAddingFilter(message) {
+ this.message = message;
+ }
- for (var i = 0; i < orderedChildren.length; i++) {
- var child = orderedChildren[i];
+ ContextAddingFilter.prototype.modifyFailureMessage = function(msg) {
+ var nl = msg.indexOf('\n');
- processNode(child, parentEnabled);
+ if (nl === -1) {
+ return this.message + ': ' + msg;
+ } else {
+ return this.message + ':\n' + indent(msg);
+ }
+ };
- if (!stats.valid) {
- return;
- }
+ function indent(s) {
+ return s.replace(/^/gm, ' ');
+ }
- var childStats = stats[child.id];
+ return {
+ factory: function(options) {
+ return new Expectation(options || {});
+ },
+ addCoreMatchers: function(matchers) {
+ addCoreMatchers(Expectation.prototype, matchers, wrapSyncCompare);
+ },
+ asyncFactory: function(options) {
+ return new AsyncExpectation(options || {});
+ },
+ addAsyncCoreMatchers: function(matchers) {
+ addCoreMatchers(AsyncExpectation.prototype, matchers, wrapAsyncCompare);
+ }
+ };
+};
- hasExecutableChild = hasExecutableChild || childStats.executable;
- }
+getJasmineRequireObj().ExpectationFilterChain = function() {
+ function ExpectationFilterChain(maybeFilter, prev) {
+ this.filter_ = maybeFilter;
+ this.prev_ = prev;
+ }
- stats[node.id] = {
- executable: hasExecutableChild
- };
+ ExpectationFilterChain.prototype.addFilter = function(filter) {
+ return new ExpectationFilterChain(filter, this);
+ };
- segmentChildren(node, orderedChildren, stats[node.id], executableIndex);
+ ExpectationFilterChain.prototype.selectComparisonFunc = function(matcher) {
+ return this.callFirst_('selectComparisonFunc', arguments).result;
+ };
- if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
- stats = { valid: false };
- }
- }
- }
+ ExpectationFilterChain.prototype.buildFailureMessage = function(
+ result,
+ matcherName,
+ args,
+ matchersUtil
+ ) {
+ return this.callFirst_('buildFailureMessage', arguments).result;
+ };
- function startingMin(executableIndex) {
- return executableIndex === undefined ? defaultMin : executableIndex;
- }
+ ExpectationFilterChain.prototype.modifyFailureMessage = function(msg) {
+ var result = this.callFirst_('modifyFailureMessage', arguments).result;
+ return result || msg;
+ };
- function startingMax(executableIndex) {
- return executableIndex === undefined ? defaultMax : executableIndex;
- }
+ ExpectationFilterChain.prototype.callFirst_ = function(fname, args) {
+ var prevResult;
- 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);
+ if (this.prev_) {
+ prevResult = this.prev_.callFirst_(fname, args);
- function isSegmentBoundary(minIndex) {
- return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
+ if (prevResult.found) {
+ return prevResult;
}
+ }
- for (var i = 0; i < orderedChildSegments.length; i++) {
- var childSegment = orderedChildSegments[i],
- maxIndex = childSegment.max,
- minIndex = childSegment.min;
+ if (this.filter_ && this.filter_[fname]) {
+ return {
+ found: true,
+ result: this.filter_[fname].apply(this.filter_, args)
+ };
+ }
- if (isSegmentBoundary(minIndex)) {
- currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax};
- result.push(currentSegment);
- }
+ return { found: false };
+ };
- currentSegment.nodes.push(childSegment);
- currentSegment.min = Math.min(currentSegment.min, minIndex);
- currentSegment.max = Math.max(currentSegment.max, maxIndex);
- lastMax = maxIndex;
- }
+ return ExpectationFilterChain;
+};
- nodeStats.segments = result;
- }
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function(j$) {
+ function buildExpectationResult(options) {
+ var messageFormatter = options.messageFormatter || function() {},
+ stackFormatter = options.stackFormatter || function() {};
- function orderChildSegments(children) {
- var specifiedOrder = [],
- unspecifiedOrder = [];
+ /**
+ * @typedef Expectation
+ * @property {String} matcherName - The name of the matcher that was executed for this expectation.
+ * @property {String} message - The failure message for the expectation.
+ * @property {String} stack - The stack trace for the failure if available.
+ * @property {Boolean} passed - Whether the expectation passed or failed.
+ * @property {Object} expected - If the expectation failed, what was the expected value.
+ * @property {Object} actual - If the expectation failed, what actual value was produced.
+ */
+ var result = {
+ matcherName: options.matcherName,
+ message: message(),
+ stack: stack(),
+ passed: options.passed
+ };
- for (var i = 0; i < children.length; i++) {
- var child = children[i],
- segments = stats[child.id].segments;
+ if (!result.passed) {
+ result.expected = options.expected;
+ result.actual = options.actual;
- for (var j = 0; j < segments.length; j++) {
- var seg = segments[j];
+ if (options.error && !j$.isString_(options.error)) {
+ if ('code' in options.error) {
+ result.code = options.error.code;
+ }
- if (seg.min === defaultMin) {
- unspecifiedOrder.push(seg);
- } else {
- specifiedOrder.push(seg);
- }
+ if (
+ options.error.code === 'ERR_ASSERTION' &&
+ options.expected === '' &&
+ options.actual === ''
+ ) {
+ result.expected = options.error.expected;
+ result.actual = options.error.actual;
+ result.matcherName = 'assert ' + options.error.operator;
}
}
-
- 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);
+ return result;
- 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 message() {
+ if (options.passed) {
+ return 'Passed.';
+ } else if (options.message) {
+ return options.message;
+ } else if (options.error) {
+ return messageFormatter(options.error);
}
+ return '';
}
- 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));
+ function stack() {
+ if (options.passed) {
+ return '';
}
- if (!stats[node.id].executable) {
- return result;
+ var error = options.error;
+ if (!error) {
+ if (options.errorForStack) {
+ error = options.errorForStack;
+ } else if (options.stack) {
+ error = options;
+ } else {
+ try {
+ throw new Error(message());
+ } catch (e) {
+ error = e;
+ }
+ }
}
-
- return node.beforeAllFns.concat(result).concat(node.afterAllFns);
+ return stackFormatter(error);
}
}
- return TreeProcessor;
+ return buildExpectationResult;
};
-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;
+getJasmineRequireObj().Expector = function(j$) {
+ function Expector(options) {
+ this.matchersUtil = options.matchersUtil || {
+ buildFailureMessage: function() {}
+ };
+ this.customEqualityTesters = options.customEqualityTesters || [];
+ this.actual = options.actual;
+ this.addExpectationResult = options.addExpectationResult || function() {};
+ this.filters = new j$.ExpectationFilterChain();
}
- Any.prototype.asymmetricMatch = function(other) {
- if (this.expectedObject == String) {
- return typeof other == 'string' || other instanceof String;
- }
+ Expector.prototype.instantiateMatcher = function(
+ matcherName,
+ matcherFactory,
+ args
+ ) {
+ this.matcherName = matcherName;
+ this.args = Array.prototype.slice.call(args, 0);
+ this.expected = this.args.slice(0);
- if (this.expectedObject == Number) {
- return typeof other == 'number' || other instanceof Number;
- }
+ this.args.unshift(this.actual);
- if (this.expectedObject == Function) {
- return typeof other == 'function' || other instanceof Function;
- }
+ var matcher = matcherFactory(this.matchersUtil, this.customEqualityTesters);
+ var comparisonFunc = this.filters.selectComparisonFunc(matcher);
+ return comparisonFunc || matcher.compare;
+ };
- if (this.expectedObject == Object) {
- return typeof other == 'object';
+ Expector.prototype.buildMessage = function(result) {
+ var self = this;
+
+ if (result.pass) {
+ return '';
}
- if (this.expectedObject == Boolean) {
- return typeof other == 'boolean';
+ var msg = this.filters.buildFailureMessage(
+ result,
+ this.matcherName,
+ this.args,
+ this.matchersUtil,
+ defaultMessage
+ );
+ return this.filters.modifyFailureMessage(msg || defaultMessage());
+
+ function defaultMessage() {
+ if (!result.message) {
+ var args = self.args.slice();
+ args.unshift(false);
+ args.unshift(self.matcherName);
+ return self.matchersUtil.buildFailureMessage.apply(
+ self.matchersUtil,
+ args
+ );
+ } else if (j$.isFunction_(result.message)) {
+ return result.message();
+ } else {
+ return result.message;
+ }
}
+ };
- return other instanceof this.expectedObject;
+ Expector.prototype.compare = function(matcherName, matcherFactory, args) {
+ var matcherCompare = this.instantiateMatcher(
+ matcherName,
+ matcherFactory,
+ args
+ );
+ return matcherCompare.apply(null, this.args);
};
- Any.prototype.jasmineToString = function() {
- return '<jasmine.any(' + j$.fnNameFor(this.expectedObject) + ')>';
+ Expector.prototype.addFilter = function(filter) {
+ var result = Object.create(this);
+ result.filters = this.filters.addFilter(filter);
+ return result;
};
- return Any;
-};
-
-getJasmineRequireObj().Anything = function(j$) {
-
- function Anything() {}
+ Expector.prototype.processResult = function(result, errorForStack) {
+ var message = this.buildMessage(result);
- Anything.prototype.asymmetricMatch = function(other) {
- return !j$.util.isUndefined(other) && other !== null;
- };
+ if (this.expected.length === 1) {
+ this.expected = this.expected[0];
+ }
- Anything.prototype.jasmineToString = function() {
- return '<jasmine.anything>';
+ this.addExpectationResult(result.pass, {
+ matcherName: this.matcherName,
+ passed: result.pass,
+ message: message,
+ error: errorForStack ? undefined : result.error,
+ errorForStack: errorForStack || undefined,
+ actual: this.actual,
+ expected: this.expected // TODO: this may need to be arrayified/sliced
+ });
};
- return Anything;
+ return Expector;
};
-getJasmineRequireObj().ArrayContaining = function(j$) {
- function ArrayContaining(sample) {
- this.sample = sample;
+getJasmineRequireObj().formatErrorMsg = function() {
+ function generateErrorMsg(domain, usage) {
+ var usageDefinition = usage ? '\nUsage: ' + usage : '';
+
+ return function errorMsg(msg) {
+ return domain + ' : ' + msg + usageDefinition;
+ };
}
- 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 + '\'.'); }
+ return generateErrorMsg;
+};
+
+getJasmineRequireObj().GlobalErrors = function(j$) {
+ function GlobalErrors(global) {
+ var handlers = [];
+ global = global || j$.getGlobal();
- for (var i = 0; i < this.sample.length; i++) {
- var item = this.sample[i];
- if (!j$.matchersUtil.contains(other, item)) {
- return false;
+ var onerror = function onerror() {
+ var handler = handlers[handlers.length - 1];
+
+ if (handler) {
+ handler.apply(null, Array.prototype.slice.call(arguments, 0));
+ } else {
+ throw arguments[0];
}
- }
+ };
- return true;
+ this.originalHandlers = {};
+ this.jasmineHandlers = {};
+ this.installOne_ = function installOne_(errorType, jasmineMessage) {
+ function taggedOnError(error) {
+ error.jasmineMessage = jasmineMessage + ': ' + error;
+
+ var handler = handlers[handlers.length - 1];
+
+ if (handler) {
+ handler(error);
+ } else {
+ throw error;
+ }
+ }
+
+ this.originalHandlers[errorType] = global.process.listeners(errorType);
+ this.jasmineHandlers[errorType] = taggedOnError;
+
+ global.process.removeAllListeners(errorType);
+ global.process.on(errorType, taggedOnError);
+
+ this.uninstall = function uninstall() {
+ var errorTypes = Object.keys(this.originalHandlers);
+ for (var iType = 0; iType < errorTypes.length; iType++) {
+ var errorType = errorTypes[iType];
+ global.process.removeListener(
+ errorType,
+ this.jasmineHandlers[errorType]
+ );
+ for (var i = 0; i < this.originalHandlers[errorType].length; i++) {
+ global.process.on(errorType, this.originalHandlers[errorType][i]);
+ }
+ delete this.originalHandlers[errorType];
+ delete this.jasmineHandlers[errorType];
+ }
+ };
+ };
+
+ this.install = function install() {
+ if (
+ global.process &&
+ global.process.listeners &&
+ j$.isFunction_(global.process.on)
+ ) {
+ this.installOne_('uncaughtException', 'Uncaught exception');
+ this.installOne_('unhandledRejection', 'Unhandled promise rejection');
+ } else {
+ var originalHandler = global.onerror;
+ global.onerror = onerror;
+
+ var browserRejectionHandler = function browserRejectionHandler(event) {
+ if (j$.isError_(event.reason)) {
+ event.reason.jasmineMessage =
+ 'Unhandled promise rejection: ' + event.reason;
+ onerror(event.reason);
+ } else {
+ onerror('Unhandled promise rejection: ' + event.reason);
+ }
+ };
+
+ if (global.addEventListener) {
+ global.addEventListener(
+ 'unhandledrejection',
+ browserRejectionHandler
+ );
+ }
+
+ this.uninstall = function uninstall() {
+ global.onerror = originalHandler;
+ if (global.removeEventListener) {
+ global.removeEventListener(
+ 'unhandledrejection',
+ browserRejectionHandler
+ );
+ }
+ };
+ }
+ };
+
+ this.pushListener = function pushListener(listener) {
+ handlers.push(listener);
+ };
+
+ this.popListener = function popListener() {
+ handlers.pop();
+ };
+ }
+
+ return GlobalErrors;
+};
+
+/* eslint-disable compat/compat */
+getJasmineRequireObj().toBePending = function(j$) {
+ /**
+ * Expect a promise to be pending, ie. the promise is neither resolved nor rejected.
+ * @function
+ * @async
+ * @name async-matchers#toBePending
+ * @since 3.6
+ * @example
+ * await expectAsync(aPromise).toBePending();
+ */
+ return function toBePending() {
+ return {
+ compare: function(actual) {
+ if (!j$.isPromiseLike(actual)) {
+ throw new Error('Expected toBePending to be called on a promise.');
+ }
+ var want = {};
+ return Promise.race([actual, Promise.resolve(want)]).then(
+ function(got) { return {pass: want === got}; },
+ function() { return {pass: false}; }
+ );
+ }
+ };
};
+};
- ArrayContaining.prototype.jasmineToString = function () {
- return '<jasmine.arrayContaining(' + jasmine.pp(this.sample) +')>';
+getJasmineRequireObj().toBeRejected = function(j$) {
+ /**
+ * Expect a promise to be rejected.
+ * @function
+ * @async
+ * @name async-matchers#toBeRejected
+ * @since 3.1.0
+ * @example
+ * await expectAsync(aPromise).toBeRejected();
+ * @example
+ * return expectAsync(aPromise).toBeRejected();
+ */
+ return function toBeRejected() {
+ return {
+ compare: function(actual) {
+ if (!j$.isPromiseLike(actual)) {
+ throw new Error('Expected toBeRejected to be called on a promise.');
+ }
+ return actual.then(
+ function() { return {pass: false}; },
+ function() { return {pass: true}; }
+ );
+ }
+ };
};
+};
- return ArrayContaining;
+getJasmineRequireObj().toBeRejectedWith = function(j$) {
+ /**
+ * Expect a promise to be rejected with a value equal to the expected, using deep equality comparison.
+ * @function
+ * @async
+ * @name async-matchers#toBeRejectedWith
+ * @since 3.3.0
+ * @param {Object} expected - Value that the promise is expected to be rejected with
+ * @example
+ * await expectAsync(aPromise).toBeRejectedWith({prop: 'value'});
+ * @example
+ * return expectAsync(aPromise).toBeRejectedWith({prop: 'value'});
+ */
+ return function toBeRejectedWith(matchersUtil) {
+ return {
+ compare: function(actualPromise, expectedValue) {
+ if (!j$.isPromiseLike(actualPromise)) {
+ throw new Error('Expected toBeRejectedWith to be called on a promise.');
+ }
+
+ function prefix(passed) {
+ return 'Expected a promise ' +
+ (passed ? 'not ' : '') +
+ 'to be rejected with ' + matchersUtil.pp(expectedValue);
+ }
+
+ return actualPromise.then(
+ function() {
+ return {
+ pass: false,
+ message: prefix(false) + ' but it was resolved.'
+ };
+ },
+ function(actualValue) {
+ if (matchersUtil.equals(actualValue, expectedValue)) {
+ return {
+ pass: true,
+ message: prefix(true) + '.'
+ };
+ } else {
+ return {
+ pass: false,
+ message: prefix(false) + ' but it was rejected with ' + matchersUtil.pp(actualValue) + '.'
+ };
+ }
+ }
+ );
+ }
+ };
+ };
};
-getJasmineRequireObj().ObjectContaining = function(j$) {
+getJasmineRequireObj().toBeRejectedWithError = function(j$) {
+ /**
+ * Expect a promise to be rejected with a value matched to the expected
+ * @function
+ * @async
+ * @name async-matchers#toBeRejectedWithError
+ * @since 3.5.0
+ * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of.
If not provided, `Error` will be used.
+ * @param {RegExp|String} [message] - The message that should be set on the thrown `Error`
+ * @example
+ * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, 'Error message');
+ * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError, /Error message/);
+ * await expectAsync(aPromise).toBeRejectedWithError(MyCustomError);
+ * await expectAsync(aPromise).toBeRejectedWithError('Error message');
+ * return expectAsync(aPromise).toBeRejectedWithError(/Error message/);
+ */
+ return function toBeRejectedWithError(matchersUtil) {
+ return {
+ compare: function(actualPromise, arg1, arg2) {
+ if (!j$.isPromiseLike(actualPromise)) {
+ throw new Error('Expected toBeRejectedWithError to be called on a promise.');
+ }
- function ObjectContaining(sample) {
- this.sample = sample;
- }
+ var expected = getExpectedFromArgs(arg1, arg2, matchersUtil);
- function getPrototype(obj) {
- if (Object.getPrototypeOf) {
- return Object.getPrototypeOf(obj);
+ return actualPromise.then(
+ function() {
+ return {
+ pass: false,
+ message: 'Expected a promise to be rejected but it was resolved.'
+ };
+ },
+ function(actualValue) { return matchError(actualValue, expected, matchersUtil); }
+ );
+ }
+ };
+ };
+
+ function matchError(actual, expected, matchersUtil) {
+ if (!j$.isError_(actual)) {
+ return fail(expected, 'rejected with ' + matchersUtil.pp(actual));
}
- if (obj.constructor.prototype == obj) {
- return null;
+ if (!(actual instanceof expected.error)) {
+ return fail(expected, 'rejected with type ' + j$.fnNameFor(actual.constructor));
}
- return obj.constructor.prototype;
- }
+ var actualMessage = actual.message;
- function hasProperty(obj, property) {
- if (!obj) {
- return false;
+ if (actualMessage === expected.message || typeof expected.message === 'undefined') {
+ return pass(expected);
}
- if (Object.prototype.hasOwnProperty.call(obj, property)) {
- return true;
+ if (expected.message instanceof RegExp && expected.message.test(actualMessage)) {
+ return pass(expected);
}
- return hasProperty(getPrototype(obj), property);
+ return fail(expected, 'rejected with ' + matchersUtil.pp(actual));
}
- 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;
- };
+ function pass(expected) {
+ return {
+ pass: true,
+ message: 'Expected a promise not to be rejected with ' + expected.printValue + ', but it was.'
+ };
+ }
- ObjectContaining.prototype.jasmineToString = function() {
- return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
- };
+ function fail(expected, message) {
+ return {
+ pass: false,
+ message: 'Expected a promise to be rejected with ' + expected.printValue + ' but it was ' + message +
'.'
+ };
+ }
- return ObjectContaining;
-};
-getJasmineRequireObj().StringMatching = function(j$) {
+ function getExpectedFromArgs(arg1, arg2, matchersUtil) {
+ var error, message;
- function StringMatching(expected) {
- if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
- throw new Error('Expected is not a String or a RegExp');
+ if (isErrorConstructor(arg1)) {
+ error = arg1;
+ message = arg2;
+ } else {
+ error = Error;
+ message = arg1;
}
- this.regexp = new RegExp(expected);
+ return {
+ error: error,
+ message: message,
+ printValue: j$.fnNameFor(error) + (typeof message === 'undefined' ? '' : ': ' +
matchersUtil.pp(message))
+ };
}
- StringMatching.prototype.asymmetricMatch = function(other) {
- return this.regexp.test(other);
- };
+ function isErrorConstructor(value) {
+ return typeof value === 'function' && (value === Error || j$.isError_(value.prototype));
+ }
+};
- StringMatching.prototype.jasmineToString = function() {
- return '<jasmine.stringMatching(' + this.regexp + ')>';
- };
+getJasmineRequireObj().toBeResolved = function(j$) {
+ /**
+ * Expect a promise to be resolved.
+ * @function
+ * @async
+ * @name async-matchers#toBeResolved
+ * @since 3.1.0
+ * @example
+ * await expectAsync(aPromise).toBeResolved();
+ * @example
+ * return expectAsync(aPromise).toBeResolved();
+ */
+ return function toBeResolved() {
+ return {
+ compare: function(actual) {
+ if (!j$.isPromiseLike(actual)) {
+ throw new Error('Expected toBeResolved to be called on a promise.');
+ }
- return StringMatching;
+ return actual.then(
+ function() { return {pass: true}; },
+ function() { return {pass: false}; }
+ );
+ }
+ };
+ };
};
-getJasmineRequireObj().errors = function() {
- function ExpectationFailed() {}
+getJasmineRequireObj().toBeResolvedTo = function(j$) {
+ /**
+ * Expect a promise to be resolved to a value equal to the expected, using deep equality comparison.
+ * @function
+ * @async
+ * @name async-matchers#toBeResolvedTo
+ * @since 3.1.0
+ * @param {Object} expected - Value that the promise is expected to resolve to
+ * @example
+ * await expectAsync(aPromise).toBeResolvedTo({prop: 'value'});
+ * @example
+ * return expectAsync(aPromise).toBeResolvedTo({prop: 'value'});
+ */
+ return function toBeResolvedTo(matchersUtil) {
+ return {
+ compare: function(actualPromise, expectedValue) {
+ if (!j$.isPromiseLike(actualPromise)) {
+ throw new Error('Expected toBeResolvedTo to be called on a promise.');
+ }
- ExpectationFailed.prototype = new Error();
- ExpectationFailed.prototype.constructor = ExpectationFailed;
+ function prefix(passed) {
+ return 'Expected a promise ' +
+ (passed ? 'not ' : '') +
+ 'to be resolved to ' + matchersUtil.pp(expectedValue);
+ }
- return {
- ExpectationFailed: ExpectationFailed
+ return actualPromise.then(
+ function(actualValue) {
+ if (matchersUtil.equals(actualValue, expectedValue)) {
+ return {
+ pass: true,
+ message: prefix(true) + '.'
+ };
+ } else {
+ return {
+ pass: false,
+ message: prefix(false) + ' but it was resolved to ' + matchersUtil.pp(actualValue) + '.'
+ };
+ }
+ },
+ function() {
+ return {
+ pass: false,
+ message: prefix(false) + ' but it was rejected.'
+ };
+ }
+ );
+ }
+ };
};
};
-getJasmineRequireObj().formatErrorMsg = function() {
- function generateErrorMsg(domain, usage) {
- var usageDefinition = usage ? '\nUsage: ' + usage : '';
-
- return function errorMsg(msg) {
- return domain + ' : ' + msg + usageDefinition;
- };
- }
- return generateErrorMsg;
-};
+getJasmineRequireObj().DiffBuilder = function (j$) {
+ return function DiffBuilder(config) {
+ var prettyPrinter = (config || {}).prettyPrinter || j$.makePrettyPrinter(),
+ mismatches = new j$.MismatchTree(),
+ path = new j$.ObjectPath(),
+ actualRoot = undefined,
+ expectedRoot = undefined;
-getJasmineRequireObj().matchersUtil = function(j$) {
- // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+ return {
+ setRoots: function (actual, expected) {
+ actualRoot = actual;
+ expectedRoot = expected;
+ },
- return {
- equals: function(a, b, customTesters) {
- customTesters = customTesters || [];
+ recordMismatch: function (formatter) {
+ mismatches.add(path, formatter);
+ },
- return eq(a, b, [], [], customTesters);
- },
+ getMessage: function () {
+ var messages = [];
- contains: function(haystack, needle, customTesters) {
- customTesters = customTesters || [];
+ mismatches.traverse(function (path, isLeaf, formatter) {
+ var actualCustom, expectedCustom, useCustom,
+ derefResult = dereferencePath(path, actualRoot, expectedRoot, prettyPrinter),
+ actual = derefResult.actual,
+ expected = derefResult.expected;
- 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)) {
+ if (formatter) {
+ messages.push(formatter(actual, expected, path, prettyPrinter));
return true;
}
- }
- return false;
- }
- return !!haystack && haystack.indexOf(needle) >= 0;
- },
+ actualCustom = prettyPrinter.customFormat_(actual);
+ expectedCustom = prettyPrinter.customFormat_(expected);
+ useCustom = !(j$.util.isUndefined(actualCustom) && j$.util.isUndefined(expectedCustom));
- 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 += ',';
+ if (useCustom) {
+ messages.push(wrapPrettyPrinted(actualCustom, expectedCustom, path));
+ return false; // don't recurse further
}
- message += ' ' + j$.pp(expected[i]);
- }
+
+ if (isLeaf) {
+ messages.push(defaultFormatter(actual, expected, path, prettyPrinter));
+ }
+
+ return true;
+ });
+
+ return messages.join('\n');
+ },
+
+ withPath: function (pathComponent, block) {
+ var oldPath = path;
+ path = path.add(pathComponent);
+ block();
+ path = oldPath;
}
+ };
+
+ function defaultFormatter(actual, expected, path, prettyPrinter) {
+ return wrapPrettyPrinted(prettyPrinter(actual), prettyPrinter(expected), path);
+ }
- return message + '.';
+ function wrapPrettyPrinted(actual, expected, path) {
+ return 'Expected ' +
+ path + (path.depth() ? ' = ' : '') +
+ actual +
+ ' to equal ' +
+ expected +
+ '.';
}
};
- function isAsymmetric(obj) {
- return obj && j$.isA_('Function', obj.asymmetricMatch);
+ function dereferencePath(objectPath, actual, expected, pp) {
+ function handleAsymmetricExpected() {
+ if (j$.isAsymmetricEqualityTester_(expected) && j$.isFunction_(expected.valuesForDiff_)) {
+ var asymmetricResult = expected.valuesForDiff_(actual, pp);
+ expected = asymmetricResult.self;
+ actual = asymmetricResult.other;
+ }
+ }
+
+ var i;
+ handleAsymmetricExpected();
+
+ for (i = 0; i < objectPath.components.length; i++) {
+ actual = actual[objectPath.components[i]];
+ expected = expected[objectPath.components[i]];
+ handleAsymmetricExpected();
+ }
+
+ return {actual: actual, expected: expected};
}
- function asymmetricMatch(a, b) {
- var asymmetricA = isAsymmetric(a),
- asymmetricB = isAsymmetric(b);
+};
+
+getJasmineRequireObj().MatchersUtil = function(j$) {
+ // TODO: convert all uses of j$.pp to use the injected pp
+
+ /**
+ * _Note:_ Do not construct this directly. Jasmine will construct one and
+ * pass it to matchers and asymmetric equality testers.
+ * @name MatchersUtil
+ * @classdesc Utilities for use in implementing matchers
+ * @constructor
+ */
+ function MatchersUtil(options) {
+ options = options || {};
+ this.customTesters_ = options.customTesters || [];
+ /**
+ * Formats a value for use in matcher failure messages and similar contexts,
+ * taking into account the current set of custom value formatters.
+ * @function
+ * @name MatchersUtil#pp
+ * @since 3.6.0
+ * @param {*} value The value to pretty-print
+ * @return {string} The pretty-printed value
+ */
+ this.pp = options.pp || function() {};
+ };
+
+ /**
+ * Determines whether `haystack` contains `needle`, using the same comparison
+ * logic as {@link MatchersUtil#equals}.
+ * @function
+ * @name MatchersUtil#contains
+ * @since 2.0.0
+ * @param {*} haystack The collection to search
+ * @param {*} needle The value to search for
+ * @param [customTesters] An array of custom equality testers
+ * @returns {boolean} True if `needle` was found in `haystack`
+ */
+ MatchersUtil.prototype.contains = function(haystack, needle, customTesters) {
+ if (j$.isSet(haystack)) {
+ return haystack.has(needle);
+ }
+
+ if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
+ (!!haystack && !haystack.indexOf))
+ {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals(haystack[i], needle, customTesters)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return !!haystack && haystack.indexOf(needle) >= 0;
+ };
+
+ MatchersUtil.prototype.buildFailureMessage = function() {
+ var self = this;
+ 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 ' +
+ self.pp(actual) +
+ (isNot ? ' not ' : ' ') +
+ englishyPredicate;
+
+ if (expected.length > 0) {
+ for (var i = 0; i < expected.length; i++) {
+ if (i > 0) {
+ message += ',';
+ }
+ message += ' ' + self.pp(expected[i]);
+ }
+ }
+
+ return message + '.';
+ };
+
+ MatchersUtil.prototype.asymmetricDiff_ = function(a, b, aStack, bStack, customTesters, diffBuilder) {
+ if (j$.isFunction_(b.valuesForDiff_)) {
+ var values = b.valuesForDiff_(a, this.pp);
+ this.eq_(values.other, values.self, aStack, bStack, customTesters, diffBuilder);
+ } else {
+ diffBuilder.recordMismatch();
+ }
+ };
+
+ MatchersUtil.prototype.asymmetricMatch_ = function(a, b, aStack, bStack, customTesters, diffBuilder) {
+ var asymmetricA = j$.isAsymmetricEqualityTester_(a),
+ asymmetricB = j$.isAsymmetricEqualityTester_(b),
+ shim,
+ result;
- if (asymmetricA && asymmetricB) {
+ if (asymmetricA === asymmetricB) {
return undefined;
}
+ shim = j$.asymmetricEqualityTesterArgCompatShim(this, customTesters);
+
if (asymmetricA) {
- return a.asymmetricMatch(b);
+ result = a.asymmetricMatch(b, shim);
+ if (!result) {
+ diffBuilder.recordMismatch();
+ }
+ return result;
}
if (asymmetricB) {
- return b.asymmetricMatch(a);
+ result = b.asymmetricMatch(a, shim);
+ if (!result) {
+ this.asymmetricDiff_(a, b, aStack, bStack, customTesters, diffBuilder);
+ }
+ return result;
}
- }
+ };
+
+ /**
+ * Determines whether two values are deeply equal to each other.
+ * @function
+ * @name MatchersUtil#equals
+ * @since 2.0.0
+ * @param {*} a The first value to compare
+ * @param {*} b The second value to compare
+ * @param [customTesters] An array of custom equality testers
+ * @returns {boolean} True if the values are equal
+ */
+ MatchersUtil.prototype.equals = function(a, b, customTestersOrDiffBuilder, diffBuilderOrNothing) {
+ var customTesters, diffBuilder;
+
+ if (isDiffBuilder(customTestersOrDiffBuilder)) {
+ diffBuilder = customTestersOrDiffBuilder;
+ } else {
+ customTesters = customTestersOrDiffBuilder;
+ diffBuilder = diffBuilderOrNothing;
+ }
+
+ customTesters = customTesters || this.customTesters_;
+ diffBuilder = diffBuilder || j$.NullDiffBuilder();
+ diffBuilder.setRoots(a, b);
+
+ return this.eq_(a, b, [], [], customTesters, diffBuilder);
+ };
// Equality function lovingly adapted from isEqual in
// [Underscore](http://underscorejs.org)
- function eq(a, b, aStack, bStack, customTesters) {
- var result = true;
+ MatchersUtil.prototype.eq_ = function(a, b, aStack, bStack, customTesters, diffBuilder) {
+ var result = true, self = this, i;
- var asymmetricResult = asymmetricMatch(a, b);
+ var asymmetricResult = this.asymmetricMatch_(a, b, aStack, bStack, customTesters, diffBuilder);
if (!j$.util.isUndefined(asymmetricResult)) {
return asymmetricResult;
}
- for (var i = 0; i < customTesters.length; i++) {
+ for (i = 0; i < customTesters.length; i++) {
var customTesterResult = customTesters[i](a, b);
if (!j$.util.isUndefined(customTesterResult)) {
+ if (!customTesterResult) {
+ diffBuilder.recordMismatch();
+ }
return customTesterResult;
}
}
if (a instanceof Error && b instanceof Error) {
- return a.message == b.message;
+ result = a.message == b.message;
+ if (!result) {
+ diffBuilder.recordMismatch();
+ }
+ return result;
}
// 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; }
+ if (a === b) {
+ result = a !== 0 || 1 / a == 1 / b;
+ if (!result) {
+ diffBuilder.recordMismatch();
+ }
+ return result;
+ }
// A strict comparison is necessary because `null == undefined`.
- if (a === null || b === null) { return a === b; }
+ if (a === null || b === null) {
+ result = a === b;
+ if (!result) {
+ diffBuilder.recordMismatch();
+ }
+ return result;
+ }
var className = Object.prototype.toString.call(a);
- if (className != Object.prototype.toString.call(b)) { return false; }
+ if (className != Object.prototype.toString.call(b)) {
+ diffBuilder.recordMismatch();
+ 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]':
+ result = a == String(b);
+ if (!result) {
+ diffBuilder.recordMismatch();
+ }
+ return result;
+ 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);
+ result = a != +a ? b != +b : (a === 0 && b === 0 ? 1 / a == 1 / b : a == +b);
+ if (!result) {
+ diffBuilder.recordMismatch();
+ }
+ return result;
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;
+ result = +a == +b;
+ if (!result) {
+ diffBuilder.recordMismatch();
+ }
+ return result;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
@@ -2883,30 +4705,32 @@ getJasmineRequireObj().matchersUtil = function(j$) {
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
- if (typeof a != 'object' || typeof b != 'object') { return false; }
+ if (typeof a != 'object' || typeof b != 'object') {
+ diffBuilder.recordMismatch();
+ 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;
+ result = a.isEqualNode(b);
+ if (!result) {
+ diffBuilder.recordMismatch();
}
- return a.innerText == b.innerText && a.textContent == b.textContent;
+ return result;
}
if (aIsDomNode || bIsDomNode) {
+ diffBuilder.recordMismatch();
return false;
}
+ var aIsPromise = j$.isPromise(a);
+ var bIsPromise = j$.isPromise(b);
+ if (aIsPromise && bIsPromise) {
+ return a === b;
+ }
+
// 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;
@@ -2922,24 +4746,137 @@ getJasmineRequireObj().matchersUtil = function(j$) {
// 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) {
+ var aLength = a.length;
+ var bLength = b.length;
+
+ diffBuilder.withPath('length', function() {
+ if (aLength !== bLength) {
+ diffBuilder.recordMismatch();
+ result = false;
+ }
+ });
+
+ for (i = 0; i < aLength || i < bLength; i++) {
+ diffBuilder.withPath(i, function() {
+ if (i >= bLength) {
+ diffBuilder.recordMismatch(actualArrayIsLongerFormatter.bind(null, self.pp));
+ result = false;
+ } else {
+ result = self.eq_(i < aLength ? a[i] : void 0, i < bLength ? b[i] : void 0, aStack, bStack,
customTesters, diffBuilder) && result;
+ }
+ });
+ }
+ if (!result) {
+ return false;
+ }
+ } else if (j$.isMap(a) && j$.isMap(b)) {
+ if (a.size != b.size) {
+ diffBuilder.recordMismatch();
return false;
}
- while (size--) {
- result = eq(a[size], b[size], aStack, bStack, customTesters);
- if (!result) {
- return false;
+ var keysA = [];
+ var keysB = [];
+ a.forEach( function( valueA, keyA ) {
+ keysA.push( keyA );
+ });
+ b.forEach( function( valueB, keyB ) {
+ keysB.push( keyB );
+ });
+
+ // For both sets of keys, check they map to equal values in both maps.
+ // Keep track of corresponding keys (in insertion order) in order to handle asymmetric obj keys.
+ var mapKeys = [keysA, keysB];
+ var cmpKeys = [keysB, keysA];
+ var mapIter, mapKey, mapValueA, mapValueB;
+ var cmpIter, cmpKey;
+ for (i = 0; result && i < mapKeys.length; i++) {
+ mapIter = mapKeys[i];
+ cmpIter = cmpKeys[i];
+
+ for (var j = 0; result && j < mapIter.length; j++) {
+ mapKey = mapIter[j];
+ cmpKey = cmpIter[j];
+ mapValueA = a.get(mapKey);
+
+ // Only use the cmpKey when one of the keys is asymmetric and the corresponding key matches,
+ // otherwise explicitly look up the mapKey in the other Map since we want keys with unique
+ // obj identity (that are otherwise equal) to not match.
+ if (j$.isAsymmetricEqualityTester_(mapKey) || j$.isAsymmetricEqualityTester_(cmpKey) &&
+ this.eq_(mapKey, cmpKey, aStack, bStack, customTesters, j$.NullDiffBuilder())) {
+ mapValueB = b.get(cmpKey);
+ } else {
+ mapValueB = b.get(mapKey);
+ }
+ result = this.eq_(mapValueA, mapValueB, aStack, bStack, customTesters, j$.NullDiffBuilder());
+ }
+ }
+
+ if (!result) {
+ diffBuilder.recordMismatch();
+ return false;
+ }
+ } else if (j$.isSet(a) && j$.isSet(b)) {
+ if (a.size != b.size) {
+ diffBuilder.recordMismatch();
+ return false;
+ }
+
+ var valuesA = [];
+ a.forEach( function( valueA ) {
+ valuesA.push( valueA );
+ });
+ var valuesB = [];
+ b.forEach( function( valueB ) {
+ valuesB.push( valueB );
+ });
+
+ // For both sets, check they are all contained in the other set
+ var setPairs = [[valuesA, valuesB], [valuesB, valuesA]];
+ var stackPairs = [[aStack, bStack], [bStack, aStack]];
+ var baseValues, baseValue, baseStack;
+ var otherValues, otherValue, otherStack;
+ var found;
+ var prevStackSize;
+ for (i = 0; result && i < setPairs.length; i++) {
+ baseValues = setPairs[i][0];
+ otherValues = setPairs[i][1];
+ baseStack = stackPairs[i][0];
+ otherStack = stackPairs[i][1];
+ // For each value in the base set...
+ for (var k = 0; result && k < baseValues.length; k++) {
+ baseValue = baseValues[k];
+ found = false;
+ // ... test that it is present in the other set
+ for (var l = 0; !found && l < otherValues.length; l++) {
+ otherValue = otherValues[l];
+ prevStackSize = baseStack.length;
+ // compare by value equality
+ found = this.eq_(baseValue, otherValue, baseStack, otherStack, customTesters,
j$.NullDiffBuilder());
+ if (!found && prevStackSize !== baseStack.length) {
+ baseStack.splice(prevStackSize);
+ otherStack.splice(prevStackSize);
+ }
+ }
+ result = result && found;
}
}
+
+ if (!result) {
+ diffBuilder.recordMismatch();
+ 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))) {
+ if (aCtor !== bCtor &&
+ isFunction(aCtor) && isFunction(bCtor) &&
+ a instanceof aCtor && b instanceof bCtor &&
+ !(aCtor instanceof aCtor && bCtor instanceof bCtor)) {
+
+ diffBuilder.recordMismatch(constructorsAreDifferentFormatter.bind(null, this.pp));
return false;
}
}
@@ -2949,78 +4886,319 @@ getJasmineRequireObj().matchersUtil = function(j$) {
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; }
+ if (keys(b, className == '[object Array]').length !== size) {
+ diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp));
+ return false;
+ }
- while (size--) {
- key = aKeys[size];
+ for (i = 0; i < size; i++) {
+ key = aKeys[i];
// Deep compare each member
- result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters);
-
- if (!result) {
- return false;
+ if (!j$.util.has(b, key)) {
+ diffBuilder.recordMismatch(objectKeysAreDifferentFormatter.bind(null, this.pp));
+ result = false;
+ continue;
}
+
+ diffBuilder.withPath(key, function() {
+ if(!self.eq_(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) {
+ result = false;
+ }
+ });
+ }
+
+ 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);
+ function keys(obj, isArray) {
+ var allKeys = Object.keys ? Object.keys(obj) :
+ (function(o) {
+ var keys = [];
+ for (var key in o) {
+ if (j$.util.has(o, key)) {
+ keys.push(key);
+ }
+ }
+ return keys;
+ })(obj);
- if (!isArray) {
+ if (!isArray) {
+ return allKeys;
+ }
+
+ if (allKeys.length === 0) {
return allKeys;
+ }
+
+ var extraKeys = [];
+ for (var i = 0; i < allKeys.length; i++) {
+ if (!/^[0-9]+$/.test(allKeys[i])) {
+ extraKeys.push(allKeys[i]);
+ }
+ }
+
+ return extraKeys;
+ }
+
+ function isFunction(obj) {
+ return typeof obj === 'function';
+ }
+
+ function objectKeysAreDifferentFormatter(pp, actual, expected, path) {
+ var missingProperties = j$.util.objectDifference(expected, actual),
+ extraProperties = j$.util.objectDifference(actual, expected),
+ missingPropertiesMessage = formatKeyValuePairs(pp, missingProperties),
+ extraPropertiesMessage = formatKeyValuePairs(pp, extraProperties),
+ messages = [];
+
+ if (!path.depth()) {
+ path = 'object';
+ }
+
+ if (missingPropertiesMessage.length) {
+ messages.push('Expected ' + path + ' to have properties' + missingPropertiesMessage);
+ }
+
+ if (extraPropertiesMessage.length) {
+ messages.push('Expected ' + path + ' not to have properties' + extraPropertiesMessage);
+ }
+
+ return messages.join('\n');
+ }
+
+ function constructorsAreDifferentFormatter(pp, actual, expected, path) {
+ if (!path.depth()) {
+ path = 'object';
+ }
+
+ return 'Expected ' +
+ path + ' to be a kind of ' +
+ j$.fnNameFor(expected.constructor) +
+ ', but was ' + pp(actual) + '.';
+ }
+
+ function actualArrayIsLongerFormatter(pp, actual, expected, path) {
+ return 'Unexpected ' +
+ path + (path.depth() ? ' = ' : '') +
+ pp(actual) +
+ ' in array.';
+ }
+
+ function formatKeyValuePairs(pp, obj) {
+ var formatted = '';
+ for (var key in obj) {
+ formatted += '\n ' + key + ': ' + pp(obj[key]);
+ }
+ return formatted;
+ }
+
+ function isDiffBuilder(obj) {
+ return obj && typeof obj.recordMismatch === 'function';
+ }
+
+ return MatchersUtil;
+};
+
+getJasmineRequireObj().MismatchTree = function (j$) {
+
+ /*
+ To be able to apply custom object formatters at all possible levels of an
+ object graph, DiffBuilder needs to be able to know not just where the
+ mismatch occurred but also all ancestors of the mismatched value in both
+ the expected and actual object graphs. MismatchTree maintains that context
+ and provides it via the traverse method.
+ */
+ function MismatchTree(path) {
+ this.path = path || new j$.ObjectPath([]);
+ this.formatter = undefined;
+ this.children = [];
+ this.isMismatch = false;
+ }
+
+ MismatchTree.prototype.add = function (path, formatter) {
+ var key, child;
+
+ if (path.depth() === 0) {
+ this.formatter = formatter;
+ this.isMismatch = true;
+ } else {
+ key = path.components[0];
+ path = path.shift();
+ child = this.child(key);
+
+ if (!child) {
+ child = new MismatchTree(this.path.add(key));
+ this.children.push(child);
+ }
+
+ child.add(path, formatter);
+ }
+ };
+
+ MismatchTree.prototype.traverse = function (visit) {
+ var i, hasChildren = this.children.length > 0;
+
+ if (this.isMismatch || hasChildren) {
+ if (visit(this.path, !hasChildren, this.formatter)) {
+ for (i = 0; i < this.children.length; i++) {
+ this.children[i].traverse(visit);
+ }
}
+ }
+ };
- var extraKeys = [];
- if (allKeys.length === 0) {
- return allKeys;
+ MismatchTree.prototype.child = function(key) {
+ var i, pathEls;
+
+ for (i = 0; i < this.children.length; i++) {
+ pathEls = this.children[i].path.components;
+ if (pathEls[pathEls.length - 1] === key) {
+ return this.children[i];
}
+ }
+ };
- for (var x = 0; x < allKeys.length; x++) {
- if (!allKeys[x].match(/^[0-9]+$/)) {
- extraKeys.push(allKeys[x]);
- }
+ return MismatchTree;
+};
+
+
+getJasmineRequireObj().nothing = function() {
+ /**
+ * {@link expect} nothing explicitly.
+ * @function
+ * @name matchers#nothing
+ * @since 2.8.0
+ * @example
+ * expect().nothing();
+ */
+ function nothing() {
+ return {
+ compare: function() {
+ return {
+ pass: true
+ };
}
+ };
+ }
+
+ return nothing;
+};
+
+getJasmineRequireObj().NullDiffBuilder = function(j$) {
+ return function() {
+ return {
+ withPath: function(_, block) {
+ block();
+ },
+ setRoots: function() {},
+ recordMismatch: function() {}
+ };
+ };
+};
+
+getJasmineRequireObj().ObjectPath = function(j$) {
+ function ObjectPath(components) {
+ this.components = components || [];
+ }
+
+ ObjectPath.prototype.toString = function() {
+ if (this.components.length) {
+ return '$' + map(this.components, formatPropertyAccess).join('');
+ } else {
+ return '';
+ }
+ };
+
+ ObjectPath.prototype.add = function(component) {
+ return new ObjectPath(this.components.concat([component]));
+ };
- return extraKeys;
+ ObjectPath.prototype.shift = function() {
+ return new ObjectPath(this.components.slice(1));
+ };
+
+ ObjectPath.prototype.depth = function() {
+ return this.components.length;
+ };
+
+ function formatPropertyAccess(prop) {
+ if (typeof prop === 'number') {
+ return '[' + prop + ']';
}
+
+ if (isValidIdentifier(prop)) {
+ return '.' + prop;
+ }
+
+ return '[\'' + prop + '\']';
}
- function has(obj, key) {
- return Object.prototype.hasOwnProperty.call(obj, key);
+ function map(array, fn) {
+ var results = [];
+ for (var i = 0; i < array.length; i++) {
+ results.push(fn(array[i]));
+ }
+ return results;
}
- function isFunction(obj) {
- return typeof obj === 'function';
+ function isValidIdentifier(string) {
+ return /^[A-Za-z\$_][A-Za-z0-9\$_]*$/.test(string);
}
- 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;
+ return ObjectPath;
+};
+
+getJasmineRequireObj().requireAsyncMatchers = function(jRequire, j$) {
+ var availableMatchers = [
+ 'toBePending',
+ 'toBeResolved',
+ 'toBeRejected',
+ 'toBeResolvedTo',
+ 'toBeRejectedWith',
+ 'toBeRejectedWithError'
+ ],
+ matchers = {};
+
+ for (var i = 0; i < availableMatchers.length; i++) {
+ var name = availableMatchers[i];
+ matchers[name] = jRequire[name](j$);
}
+
+ return matchers;
};
-getJasmineRequireObj().toBe = function() {
- function toBe() {
+getJasmineRequireObj().toBe = function(j$) {
+ /**
+ * {@link expect} the actual value to be `===` to the expected value.
+ * @function
+ * @name matchers#toBe
+ * @since 1.3.0
+ * @param {Object} expected - The expected value to compare against.
+ * @example
+ * expect(thing).toBe(realThing);
+ */
+ function toBe(matchersUtil) {
+ var tip = ' Tip: To check for deep equality, use .toEqual() instead of .toBe().';
+
return {
compare: function(actual, expected) {
- return {
+ var result = {
pass: actual === expected
};
+
+ if (typeof expected === 'object') {
+ result.message = matchersUtil.buildFailureMessage('toBe', result.pass, actual, expected) + tip;
+ }
+
+ return result;
}
};
}
@@ -3029,7 +5207,16 @@ getJasmineRequireObj().toBe = function() {
};
getJasmineRequireObj().toBeCloseTo = function() {
-
+ /**
+ * {@link expect} the actual value to be within a specified precision of the expected value.
+ * @function
+ * @name matchers#toBeCloseTo
+ * @since 1.3.0
+ * @param {Object} expected - The expected value to compare against.
+ * @param {Number} [precision=2] - The number of decimal points to check.
+ * @example
+ * expect(number).toBeCloseTo(42.2, 3);
+ */
function toBeCloseTo() {
return {
compare: function(actual, expected, precision) {
@@ -3037,8 +5224,18 @@ getJasmineRequireObj().toBeCloseTo = function() {
precision = precision || 2;
}
+ if (expected === null || actual === null) {
+ throw new Error('Cannot use toBeCloseTo with null. Arguments evaluated to: ' +
+ 'expect(' + actual + ').toBeCloseTo(' + expected + ').'
+ );
+ }
+
+ var pow = Math.pow(10, precision + 1);
+ var delta = Math.abs(expected - actual);
+ var maxDelta = Math.pow(10, -precision) / 2;
+
return {
- pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+ pass: Math.round(delta * pow) <= maxDelta * pow
};
}
};
@@ -3048,6 +5245,14 @@ getJasmineRequireObj().toBeCloseTo = function() {
};
getJasmineRequireObj().toBeDefined = function() {
+ /**
+ * {@link expect} the actual value to be defined. (Not `undefined`)
+ * @function
+ * @name matchers#toBeDefined
+ * @since 1.3.0
+ * @example
+ * expect(result).toBeDefined();
+ */
function toBeDefined() {
return {
compare: function(actual) {
@@ -3061,12 +5266,42 @@ getJasmineRequireObj().toBeDefined = function() {
return toBeDefined;
};
+getJasmineRequireObj().toBeFalse = function() {
+ /**
+ * {@link expect} the actual value to be `false`.
+ * @function
+ * @name matchers#toBeFalse
+ * @since 3.5.0
+ * @example
+ * expect(result).toBeFalse();
+ */
+ function toBeFalse() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: actual === false
+ };
+ }
+ };
+ }
+
+ return toBeFalse;
+};
+
getJasmineRequireObj().toBeFalsy = function() {
+ /**
+ * {@link expect} the actual value to be falsy
+ * @function
+ * @name matchers#toBeFalsy
+ * @since 2.0.0
+ * @example
+ * expect(result).toBeFalsy();
+ */
function toBeFalsy() {
return {
compare: function(actual) {
return {
- pass: !!!actual
+ pass: !actual
};
}
};
@@ -3076,7 +5311,15 @@ getJasmineRequireObj().toBeFalsy = function() {
};
getJasmineRequireObj().toBeGreaterThan = function() {
-
+ /**
+ * {@link expect} the actual value to be greater than the expected value.
+ * @function
+ * @name matchers#toBeGreaterThan
+ * @since 2.0.0
+ * @param {Number} expected - The value to compare against.
+ * @example
+ * expect(result).toBeGreaterThan(3);
+ */
function toBeGreaterThan() {
return {
compare: function(actual, expected) {
@@ -3092,7 +5335,15 @@ getJasmineRequireObj().toBeGreaterThan = function() {
getJasmineRequireObj().toBeGreaterThanOrEqual = function() {
-
+ /**
+ * {@link expect} the actual value to be greater than or equal to the expected value.
+ * @function
+ * @name matchers#toBeGreaterThanOrEqual
+ * @since 2.0.0
+ * @param {Number} expected - The expected value to compare against.
+ * @example
+ * expect(result).toBeGreaterThanOrEqual(25);
+ */
function toBeGreaterThanOrEqual() {
return {
compare: function(actual, expected) {
@@ -3106,7 +5357,63 @@ getJasmineRequireObj().toBeGreaterThanOrEqual = function() {
return toBeGreaterThanOrEqual;
};
+getJasmineRequireObj().toBeInstanceOf = function(j$) {
+ var usageError = j$.formatErrorMsg('<toBeInstanceOf>',
'expect(value).toBeInstanceOf(<ConstructorFunction>)');
+
+ /**
+ * {@link expect} the actual to be an instance of the expected class
+ * @function
+ * @name matchers#toBeInstanceOf
+ * @since 3.5.0
+ * @param {Object} expected - The class or constructor function to check for
+ * @example
+ * expect('foo').toBeInstanceOf(String);
+ * expect(3).toBeInstanceOf(Number);
+ * expect(new Error()).toBeInstanceOf(Error);
+ */
+ function toBeInstanceOf(matchersUtil) {
+ return {
+ compare: function(actual, expected) {
+ var actualType = actual && actual.constructor ? j$.fnNameFor(actual.constructor) :
matchersUtil.pp(actual),
+ expectedType = expected ? j$.fnNameFor(expected) : matchersUtil.pp(expected),
+ expectedMatcher,
+ pass;
+
+ try {
+ expectedMatcher = new j$.Any(expected);
+ pass = expectedMatcher.asymmetricMatch(actual);
+ } catch (error) {
+ throw new Error(usageError('Expected value is not a constructor function'));
+ }
+
+ if (pass) {
+ return {
+ pass: true,
+ message: 'Expected instance of ' + actualType + ' not to be an instance of ' + expectedType
+ };
+ } else {
+ return {
+ pass: false,
+ message: 'Expected instance of ' + actualType + ' to be an instance of ' + expectedType
+ };
+ }
+ }
+ };
+ }
+
+ return toBeInstanceOf;
+};
+
getJasmineRequireObj().toBeLessThan = function() {
+ /**
+ * {@link expect} the actual value to be less than the expected value.
+ * @function
+ * @name matchers#toBeLessThan
+ * @since 2.0.0
+ * @param {Number} expected - The expected value to compare against.
+ * @example
+ * expect(result).toBeLessThan(0);
+ */
function toBeLessThan() {
return {
@@ -3120,7 +5427,17 @@ getJasmineRequireObj().toBeLessThan = function() {
return toBeLessThan;
};
+
getJasmineRequireObj().toBeLessThanOrEqual = function() {
+ /**
+ * {@link expect} the actual value to be less than or equal to the expected value.
+ * @function
+ * @name matchers#toBeLessThanOrEqual
+ * @since 2.0.0
+ * @param {Number} expected - The expected value to compare against.
+ * @example
+ * expect(result).toBeLessThanOrEqual(123);
+ */
function toBeLessThanOrEqual() {
return {
@@ -3136,8 +5453,15 @@ getJasmineRequireObj().toBeLessThanOrEqual = function() {
};
getJasmineRequireObj().toBeNaN = function(j$) {
-
- function toBeNaN() {
+ /**
+ * {@link expect} the actual value to be `NaN` (Not a Number).
+ * @function
+ * @name matchers#toBeNaN
+ * @since 1.3.0
+ * @example
+ * expect(thing).toBeNaN();
+ */
+ function toBeNaN(matchersUtil) {
return {
compare: function(actual) {
var result = {
@@ -3147,7 +5471,7 @@ getJasmineRequireObj().toBeNaN = function(j$) {
if (result.pass) {
result.message = 'Expected actual not to be NaN.';
} else {
- result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; };
+ result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be NaN.'; };
}
return result;
@@ -3158,80 +5482,201 @@ getJasmineRequireObj().toBeNaN = function(j$) {
return toBeNaN;
};
-getJasmineRequireObj().toBeNull = function() {
-
- function toBeNull() {
+getJasmineRequireObj().toBeNegativeInfinity = function(j$) {
+ /**
+ * {@link expect} the actual value to be `-Infinity` (-infinity).
+ * @function
+ * @name matchers#toBeNegativeInfinity
+ * @since 2.6.0
+ * @example
+ * expect(thing).toBeNegativeInfinity();
+ */
+ function toBeNegativeInfinity(matchersUtil) {
return {
compare: function(actual) {
- return {
- pass: actual === null
+ var result = {
+ pass: (actual === Number.NEGATIVE_INFINITY)
};
+
+ if (result.pass) {
+ result.message = 'Expected actual not to be -Infinity.';
+ } else {
+ result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be -Infinity.';
};
+ }
+
+ return result;
}
};
}
- return toBeNull;
+ return toBeNegativeInfinity;
};
-getJasmineRequireObj().toBeTruthy = function() {
-
- function toBeTruthy() {
+getJasmineRequireObj().toBeNull = function() {
+ /**
+ * {@link expect} the actual value to be `null`.
+ * @function
+ * @name matchers#toBeNull
+ * @since 1.3.0
+ * @example
+ * expect(result).toBeNull();
+ */
+ function toBeNull() {
return {
compare: function(actual) {
return {
- pass: !!actual
+ pass: actual === null
};
}
};
}
- return toBeTruthy;
+ return toBeNull;
};
-getJasmineRequireObj().toBeUndefined = function() {
-
- function toBeUndefined() {
+getJasmineRequireObj().toBePositiveInfinity = function(j$) {
+ /**
+ * {@link expect} the actual value to be `Infinity` (infinity).
+ * @function
+ * @name matchers#toBePositiveInfinity
+ * @since 2.6.0
+ * @example
+ * expect(thing).toBePositiveInfinity();
+ */
+ function toBePositiveInfinity(matchersUtil) {
return {
compare: function(actual) {
- return {
- pass: void 0 === actual
+ var result = {
+ pass: (actual === Number.POSITIVE_INFINITY)
};
+
+ if (result.pass) {
+ result.message = 'Expected actual not to be Infinity.';
+ } else {
+ result.message = function() { return 'Expected ' + matchersUtil.pp(actual) + ' to be Infinity.'; };
+ }
+
+ return result;
}
};
}
- return toBeUndefined;
+ return toBePositiveInfinity;
};
-getJasmineRequireObj().toContain = function() {
- function toContain(util, customEqualityTesters) {
- customEqualityTesters = customEqualityTesters || [];
-
+getJasmineRequireObj().toBeTrue = function() {
+ /**
+ * {@link expect} the actual value to be `true`.
+ * @function
+ * @name matchers#toBeTrue
+ * @since 3.5.0
+ * @example
+ * expect(result).toBeTrue();
+ */
+ function toBeTrue() {
return {
- compare: function(actual, expected) {
-
+ compare: function(actual) {
return {
- pass: util.contains(actual, expected, customEqualityTesters)
+ pass: actual === true
};
}
};
}
- return toContain;
+ return toBeTrue;
};
-getJasmineRequireObj().toEqual = function() {
+getJasmineRequireObj().toBeTruthy = function() {
+ /**
+ * {@link expect} the actual value to be truthy.
+ * @function
+ * @name matchers#toBeTruthy
+ * @since 2.0.0
+ * @example
+ * expect(thing).toBeTruthy();
+ */
+ function toBeTruthy() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: !!actual
+ };
+ }
+ };
+ }
+
+ return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+ /**
+ * {@link expect} the actual value to be `undefined`.
+ * @function
+ * @name matchers#toBeUndefined
+ * @since 1.3.0
+ * @example
+ * expect(result).toBeUndefined():
+ */
+ function toBeUndefined() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: void 0 === actual
+ };
+ }
+ };
+ }
- function toEqual(util, customEqualityTesters) {
- customEqualityTesters = customEqualityTesters || [];
+ return toBeUndefined;
+};
+getJasmineRequireObj().toContain = function() {
+ /**
+ * {@link expect} the actual value to contain a specific value.
+ * @function
+ * @name matchers#toContain
+ * @since 2.0.0
+ * @param {Object} expected - The value to look for.
+ * @example
+ * expect(array).toContain(anElement);
+ * expect(string).toContain(substring);
+ */
+ function toContain(matchersUtil) {
return {
compare: function(actual, expected) {
- var result = {
- pass: false
+
+ return {
+ pass: matchersUtil.contains(actual, expected)
};
+ }
+ };
+ }
+
+ return toContain;
+};
+
+getJasmineRequireObj().toEqual = function(j$) {
+ /**
+ * {@link expect} the actual value to be equal to the expected, using deep equality comparison.
+ * @function
+ * @name matchers#toEqual
+ * @since 1.3.0
+ * @param {Object} expected - Expected value
+ * @example
+ * expect(bigObject).toEqual({"foo": ['bar', 'baz']});
+ */
+ function toEqual(matchersUtil) {
+ return {
+ compare: function(actual, expected) {
+ var result = {
+ pass: false
+ },
+ diffBuilder = j$.DiffBuilder({prettyPrinter: matchersUtil.pp});
- result.pass = util.equals(actual, expected, customEqualityTesters);
+ result.pass = matchersUtil.equals(actual, expected, diffBuilder);
+
+ // TODO: only set error message if test fails
+ result.message = diffBuilder.getMessage();
return result;
}
@@ -3245,13 +5690,22 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalled>', 'expect(<spyObj>).toHaveBeenCalled()');
- function toHaveBeenCalled() {
+ /**
+ * {@link expect} the actual (a {@link Spy}) to have been called.
+ * @function
+ * @name matchers#toHaveBeenCalled
+ * @since 1.3.0
+ * @example
+ * expect(mySpy).toHaveBeenCalled();
+ * expect(mySpy).not.toHaveBeenCalled();
+ */
+ function toHaveBeenCalled(matchersUtil) {
return {
compare: function(actual) {
var result = {};
if (!j$.isSpy(actual)) {
- throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+ throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.'));
}
if (arguments.length > 1) {
@@ -3261,8 +5715,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
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.';
+ 'Expected spy ' + actual.and.identity + ' not to have been called.' :
+ 'Expected spy ' + actual.and.identity + ' to have been called.';
return result;
}
@@ -3272,21 +5726,162 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
return toHaveBeenCalled;
};
+getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) {
+
+ var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledBefore>',
'expect(<spyObj>).toHaveBeenCalledBefore(<spyObj>)');
+
+ /**
+ * {@link expect} the actual value (a {@link Spy}) to have been called before another {@link Spy}.
+ * @function
+ * @name matchers#toHaveBeenCalledBefore
+ * @since 2.6.0
+ * @param {Spy} expected - {@link Spy} that should have been called after the `actual` {@link Spy}.
+ * @example
+ * expect(mySpy).toHaveBeenCalledBefore(otherSpy);
+ */
+ function toHaveBeenCalledBefore(matchersUtil) {
+ return {
+ compare: function(firstSpy, latterSpy) {
+ if (!j$.isSpy(firstSpy)) {
+ throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(firstSpy) + '.'));
+ }
+ if (!j$.isSpy(latterSpy)) {
+ throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(latterSpy) + '.'));
+ }
+
+ var result = { pass: false };
+
+ if (!firstSpy.calls.count()) {
+ result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called.';
+ return result;
+ }
+ if (!latterSpy.calls.count()) {
+ result.message = 'Expected spy ' + latterSpy.and.identity + ' to have been called.';
+ return result;
+ }
+
+ var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder;
+ var first2ndSpyCall = latterSpy.calls.first().invocationOrder;
+
+ result.pass = latest1stSpyCall < first2ndSpyCall;
+
+ if (result.pass) {
+ result.message = 'Expected spy ' + firstSpy.and.identity + ' to not have been called before spy '
+ latterSpy.and.identity + ', but it was';
+ } else {
+ var first1stSpyCall = firstSpy.calls.first().invocationOrder;
+ var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder;
+
+ if(first1stSpyCall < first2ndSpyCall) {
+ result.message = 'Expected latest call to spy ' + firstSpy.and.identity + ' to have been called
before first call to spy ' + latterSpy.and.identity + ' (no interleaved calls)';
+ } else if (latest2ndSpyCall > latest1stSpyCall) {
+ result.message = 'Expected first call to spy ' + latterSpy.and.identity + ' to have been called
after latest call to spy ' + firstSpy.and.identity + ' (no interleaved calls)';
+ } else {
+ result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called before spy ' +
latterSpy.and.identity;
+ }
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalledBefore;
+};
+
+getJasmineRequireObj().toHaveBeenCalledOnceWith = function (j$) {
+
+ var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledOnceWith>',
'expect(<spyObj>).toHaveBeenCalledOnceWith(...arguments)');
+
+ /**
+ * {@link expect} the actual (a {@link Spy}) to have been called exactly once, and exactly with the
particular arguments.
+ * @function
+ * @name matchers#toHaveBeenCalledOnceWith
+ * @since 3.6.0
+ * @param {...Object} - The arguments to look for
+ * @example
+ * expect(mySpy).toHaveBeenCalledOnceWith('foo', 'bar', 2);
+ */
+ function toHaveBeenCalledOnceWith(util) {
+ return {
+ compare: function () {
+ var args = Array.prototype.slice.call(arguments, 0),
+ actual = args[0],
+ expectedArgs = args.slice(1);
+
+ if (!j$.isSpy(actual)) {
+ throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+ }
+
+ var prettyPrintedCalls = actual.calls.allArgs().map(function (argsForCall) {
+ return ' ' + j$.pp(argsForCall);
+ });
+
+ if (actual.calls.count() === 1 && util.contains(actual.calls.allArgs(), expectedArgs)) {
+ return {
+ pass: true,
+ message: 'Expected spy ' + actual.and.identity + ' to have been called 0 times, multiple times,
or once, but with arguments different from:\n'
+ + ' ' + j$.pp(expectedArgs) + '\n'
+ + 'But the actual call was:\n'
+ + prettyPrintedCalls.join(',\n') + '.\n\n'
+ };
+ }
+
+ function getDiffs() {
+ return actual.calls.allArgs().map(function (argsForCall, callIx) {
+ var diffBuilder = new j$.DiffBuilder();
+ util.equals(argsForCall, expectedArgs, diffBuilder);
+ return diffBuilder.getMessage();
+ });
+ }
+
+ function butString() {
+ switch (actual.calls.count()) {
+ case 0:
+ return 'But it was never called.\n\n';
+ case 1:
+ return 'But the actual call was:\n' + prettyPrintedCalls.join(',\n') + '.\n' +
getDiffs().join('\n') + '\n\n';
+ default:
+ return 'But the actual calls were:\n' + prettyPrintedCalls.join(',\n') + '.\n\n';
+ }
+ }
+
+ return {
+ pass: false,
+ message: 'Expected spy ' + actual.and.identity + ' to have been called only once, and with given
args:\n'
+ + ' ' + j$.pp(expectedArgs) + '\n'
+ + butString()
+ };
+ }
+ };
+ }
+
+ return toHaveBeenCalledOnceWith;
+};
+
getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledTimes>',
'expect(<spyObj>).toHaveBeenCalledTimes(<Number>)');
- function toHaveBeenCalledTimes() {
+ /**
+ * {@link expect} the actual (a {@link Spy}) to have been called the specified number of times.
+ * @function
+ * @name matchers#toHaveBeenCalledTimes
+ * @since 2.4.0
+ * @param {Number} expected - The number of invocations to look for.
+ * @example
+ * expect(mySpy).toHaveBeenCalledTimes(3);
+ */
+ function toHaveBeenCalledTimes(matchersUtil) {
return {
compare: function(actual, expected) {
if (!j$.isSpy(actual)) {
- throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+ throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.pp(actual) + '.'));
}
var args = Array.prototype.slice.call(arguments, 0),
result = { pass: false };
- if (!j$.isNumber_(expected)){
+ if (!j$.isNumber_(expected)) {
throw new Error(getErrorMsg('The expected times failed is a required argument and must be a
number.'));
}
@@ -3295,8 +5890,8 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
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.';
+ '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;
}
};
@@ -3309,7 +5904,16 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
var getErrorMsg = j$.formatErrorMsg('<toHaveBeenCalledWith>',
'expect(<spyObj>).toHaveBeenCalledWith(...arguments)');
- function toHaveBeenCalledWith(util, customEqualityTesters) {
+ /**
+ * {@link expect} the actual (a {@link Spy}) to have been called with particular arguments at least once.
+ * @function
+ * @name matchers#toHaveBeenCalledWith
+ * @since 1.3.0
+ * @param {...Object} - The arguments to look for
+ * @example
+ * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2);
+ */
+ function toHaveBeenCalledWith(matchersUtil) {
return {
compare: function() {
var args = Array.prototype.slice.call(arguments, 0),
@@ -3318,19 +5922,44 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
result = { pass: false };
if (!j$.isSpy(actual)) {
- throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.'));
+ throw new Error(getErrorMsg('Expected a spy, but got ' + matchersUtil.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.'; };
+ result.message = function() {
+ return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' +
+ ' ' + matchersUtil.pp(expectedArgs) +
+ '\nbut it was never called.';
+ };
return result;
}
- if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
+ if (matchersUtil.contains(actual.calls.allArgs(), expectedArgs)) {
result.pass = true;
- result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been
called with ' + j$.pp(expectedArgs) + ' but it was.'; };
+ result.message = function() {
+ return 'Expected spy ' + actual.and.identity + ' not to have been called with:\n' +
+ ' ' + matchersUtil.pp(expectedArgs) +
+ '\nbut 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, '') + '.'; };
+ result.message = function() {
+ var prettyPrintedCalls = actual.calls.allArgs().map(function(argsForCall) {
+ return ' ' + matchersUtil.pp(argsForCall);
+ });
+
+ var diffs = actual.calls.allArgs().map(function(argsForCall, callIx) {
+ var diffBuilder = new j$.DiffBuilder();
+ matchersUtil.equals(argsForCall, expectedArgs, diffBuilder);
+ return 'Call ' + callIx + ':\n' +
+ diffBuilder.getMessage().replace(/^/mg, ' ');
+ });
+
+ return 'Expected spy ' + actual.and.identity + ' to have been called with:\n' +
+ ' ' + matchersUtil.pp(expectedArgs) + '\n' + '' +
+ 'but actual calls were:\n' +
+ prettyPrintedCalls.join(',\n') + '.\n\n' +
+ diffs.join('\n');
+ };
}
return result;
@@ -3341,10 +5970,98 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
return toHaveBeenCalledWith;
};
+getJasmineRequireObj().toHaveClass = function(j$) {
+ /**
+ * {@link expect} the actual value to be a DOM element that has the expected class
+ * @function
+ * @name matchers#toHaveClass
+ * @since 3.0.0
+ * @param {Object} expected - The class name to test for
+ * @example
+ * var el = document.createElement('div');
+ * el.className = 'foo bar baz';
+ * expect(el).toHaveClass('bar');
+ */
+ function toHaveClass(matchersUtil) {
+ return {
+ compare: function(actual, expected) {
+ if (!isElement(actual)) {
+ throw new Error(matchersUtil.pp(actual) + ' is not a DOM element');
+ }
+
+ return {
+ pass: actual.classList.contains(expected)
+ };
+ }
+ };
+ }
+
+ function isElement(maybeEl) {
+ return maybeEl &&
+ maybeEl.classList &&
+ j$.isFunction_(maybeEl.classList.contains);
+ }
+
+ return toHaveClass;
+};
+
+getJasmineRequireObj().toHaveSize = function(j$) {
+ /**
+ * {@link expect} the actual size to be equal to the expected, using array-like length or object keys size.
+ * @function
+ * @name matchers#toHaveSize
+ * @since 3.6.0
+ * @param {Object} expected - Expected size
+ * @example
+ * array = [1,2];
+ * expect(array).toHaveSize(2);
+ */
+ function toHaveSize() {
+ return {
+ compare: function(actual, expected) {
+ var result = {
+ pass: false
+ };
+
+ if (j$.isA_('WeakSet', actual) || j$.isWeakMap(actual) || j$.isDataView(actual)) {
+ throw new Error('Cannot get size of ' + actual + '.');
+ }
+
+ if (j$.isSet(actual) || j$.isMap(actual)) {
+ result.pass = actual.size === expected;
+ } else if (isLength(actual.length)) {
+ result.pass = actual.length === expected;
+ } else {
+ result.pass = Object.keys(actual).length === expected;
+ }
+
+ return result;
+ }
+ };
+ }
+
+ var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; // eslint-disable-line compat/compat
+ function isLength(value) {
+ return (typeof value == 'number') && value > -1 && value % 1 === 0 && value <= MAX_SAFE_INTEGER;
+ }
+
+ return toHaveSize;
+};
+
getJasmineRequireObj().toMatch = function(j$) {
var getErrorMsg = j$.formatErrorMsg('<toMatch>', 'expect(<expectation>).toMatch(<string> || <regexp>)');
+ /**
+ * {@link expect} the actual value to match a regular expression
+ * @function
+ * @name matchers#toMatch
+ * @since 1.3.0
+ * @param {RegExp|String} expected - Value to look for in the string.
+ * @example
+ * expect("my string").toMatch(/string$/);
+ * expect("other string").toMatch("her");
+ */
function toMatch() {
return {
compare: function(actual, expected) {
@@ -3368,7 +6085,17 @@ getJasmineRequireObj().toThrow = function(j$) {
var getErrorMsg = j$.formatErrorMsg('<toThrow>', 'expect(function() {<expectation>}).toThrow()');
- function toThrow(util) {
+ /**
+ * {@link expect} a function to `throw` something.
+ * @function
+ * @name matchers#toThrow
+ * @since 2.0.0
+ * @param {Object} [expected] - Value that should be thrown. If not provided, simply the fact that
something was thrown will be checked.
+ * @example
+ * expect(function() { return 'things'; }).toThrow('foo');
+ * expect(function() { return 'stuff'; }).toThrow();
+ */
+ function toThrow(matchersUtil) {
return {
compare: function(actual, expected) {
var result = { pass: false },
@@ -3393,16 +6120,16 @@ getJasmineRequireObj().toThrow = function(j$) {
if (arguments.length == 1) {
result.pass = true;
- result.message = function() { return 'Expected function not to throw, but it threw ' +
j$.pp(thrown) + '.'; };
+ result.message = function() { return 'Expected function not to throw, but it threw ' +
matchersUtil.pp(thrown) + '.'; };
return result;
}
- if (util.equals(thrown, expected)) {
+ if (matchersUtil.equals(thrown, expected)) {
result.pass = true;
- result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; };
+ result.message = function() { return 'Expected function not to throw ' + matchersUtil.pp(expected)
+ '.'; };
} else {
- result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it
threw ' + j$.pp(thrown) + '.'; };
+ result.message = function() { return 'Expected function to throw ' + matchersUtil.pp(expected) +
', but it threw ' + matchersUtil.pp(thrown) + '.'; };
}
return result;
@@ -3417,75 +6144,78 @@ getJasmineRequireObj().toThrowError = function(j$) {
var getErrorMsg = j$.formatErrorMsg('<toThrowError>', 'expect(function()
{<expectation>}).toThrowError(<ErrorConstructor>, <message>)');
- function toThrowError () {
+ /**
+ * {@link expect} a function to `throw` an `Error`.
+ * @function
+ * @name matchers#toThrowError
+ * @since 2.0.0
+ * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of.
If not provided, `Error` will be used.
+ * @param {RegExp|String} [message] - The message that should be set on the thrown `Error`
+ * @example
+ * expect(function() { return 'things'; }).toThrowError(MyCustomError, 'message');
+ * expect(function() { return 'things'; }).toThrowError(MyCustomError, /bar/);
+ * expect(function() { return 'stuff'; }).toThrowError(MyCustomError);
+ * expect(function() { return 'other'; }).toThrowError(/foo/);
+ * expect(function() { return 'other'; }).toThrowError();
+ */
+ function toThrowError(matchersUtil) {
return {
compare: function(actual) {
- var threw = false,
- pass = {pass: true},
- fail = {pass: false},
+ var errorMatcher = getMatcher.apply(null, arguments),
thrown;
if (typeof actual != 'function') {
throw new Error(getErrorMsg('Actual is not a Function'));
}
- var errorMatcher = getMatcher.apply(null, arguments);
-
try {
actual();
+ return fail('Expected function to throw an Error.');
} 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 (!j$.isError_(thrown)) {
+ return fail(function() { return 'Expected function to throw an Error, but it threw ' +
matchersUtil.pp(thrown) + '.'; });
}
- 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;
- }
+ return errorMatcher.match(thrown);
}
};
function getMatcher() {
- var expected = null,
- errorType = null;
+ var expected, errorType;
- if (arguments.length == 2) {
- expected = arguments[1];
- if (isAnErrorType(expected)) {
- errorType = expected;
- expected = null;
- }
- } else if (arguments.length > 2) {
+ if (arguments[2]) {
errorType = arguments[1];
expected = arguments[2];
if (!isAnErrorType(errorType)) {
throw new Error(getErrorMsg('Expected error type is not an Error.'));
}
+
+ return exactMatcher(expected, errorType);
+ } else if (arguments[1]) {
+ expected = arguments[1];
+
+ if (isAnErrorType(arguments[1])) {
+ return exactMatcher(null, arguments[1]);
+ } else {
+ return exactMatcher(arguments[1], null);
+ }
+ } else {
+ return anyMatcher();
}
+ }
+ function anyMatcher() {
+ return {
+ match: function(error) {
+ return pass('Expected function not to throw an Error, but it threw ' + j$.fnNameFor(error) + '.');
+ }
+ };
+ }
+
+ function exactMatcher(expected, errorType) {
if (expected && !isStringOrRegExp(expected)) {
if (errorType) {
throw new Error(getErrorMsg('Expected error message is not a string or RegExp.'));
@@ -3502,33 +6232,46 @@ getJasmineRequireObj().toThrowError = function(j$) {
}
}
- return {
- errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception',
- thrownDescription: function(thrown) {
- var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
- thrownMessage = '';
+ var errorTypeDescription = errorType ? j$.fnNameFor(errorType) : 'an exception';
- if (expected) {
- thrownMessage = ' with message ' + j$.pp(thrown.message);
- }
+ function thrownDescription(thrown) {
+ var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
+ thrownMessage = '';
- return thrownName + thrownMessage;
- },
- messageDescription: function() {
- if (expected === null) {
- return '';
- } else if (expected instanceof RegExp) {
- return ' with a message matching ' + j$.pp(expected);
+ if (expected) {
+ thrownMessage = ' with message ' + matchersUtil.pp(thrown.message);
+ }
+
+ return thrownName + thrownMessage;
+ }
+
+ function messageDescription() {
+ if (expected === null) {
+ return '';
+ } else if (expected instanceof RegExp) {
+ return ' with a message matching ' + matchersUtil.pp(expected);
+ } else {
+ return ' with message ' + matchersUtil.pp(expected);
+ }
+ }
+
+ function matches(error) {
+ return (errorType === null || error instanceof errorType) &&
+ (expected === null || messageMatch(error.message));
+ }
+
+ return {
+ match: function(thrown) {
+ if (matches(thrown)) {
+ return pass(function() {
+ return 'Expected function not to throw ' + errorTypeDescription + messageDescription() + '.';
+ });
} else {
- return ' with message ' + j$.pp(expected);
+ return fail(function() {
+ return 'Expected function to throw ' + errorTypeDescription + messageDescription() +
+ ', but it threw ' + thrownDescription(thrown) + '.';
+ });
}
- },
- hasNoSpecifics: function() {
- return expected === null && errorType === null;
- },
- matches: function(error) {
- return (errorType === null || error instanceof errorType) &&
- (expected === null || messageMatch(error.message));
}
};
}
@@ -3544,93 +6287,2659 @@ getJasmineRequireObj().toThrowError = function(j$) {
var Surrogate = function() {};
Surrogate.prototype = type.prototype;
- return (new Surrogate()) instanceof Error;
+ return j$.isError_(new Surrogate());
}
}
+ function pass(message) {
+ return {
+ pass: true,
+ message: message
+ };
+ }
+
+ function fail(message) {
+ return {
+ pass: false,
+ message: message
+ };
+ }
+
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);
- },
+getJasmineRequireObj().toThrowMatching = function(j$) {
+ var usageError = j$.formatErrorMsg('<toThrowMatching>', 'expect(function()
{<expectation>}).toThrowMatching(<Predicate>)');
+
+ /**
+ * {@link expect} a function to `throw` something matching a predicate.
+ * @function
+ * @name matchers#toThrowMatching
+ * @since 3.0.0
+ * @param {Function} predicate - A function that takes the thrown exception as its parameter and returns
true if it matches.
+ * @example
+ * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return
thrown.message === 'nope'; });
+ */
+ function toThrowMatching(matchersUtil) {
+ return {
+ compare: function(actual, predicate) {
+ var thrown;
- fdescribe: function(description, specDefinitions) {
- return env.fdescribe(description, specDefinitions);
- },
+ if (typeof actual !== 'function') {
+ throw new Error(usageError('Actual is not a Function'));
+ }
- it: function() {
- return env.it.apply(env, arguments);
- },
+ if (typeof predicate !== 'function') {
+ throw new Error(usageError('Predicate is not a Function'));
+ }
- xit: function() {
- return env.xit.apply(env, arguments);
- },
+ try {
+ actual();
+ return fail('Expected function to throw an exception.');
+ } catch (e) {
+ thrown = e;
+ }
- fit: function() {
- return env.fit.apply(env, arguments);
- },
+ if (predicate(thrown)) {
+ return pass('Expected function not to throw an exception matching a predicate.');
+ } else {
+ return fail(function() {
+ return 'Expected function to throw an exception matching a predicate, ' +
+ 'but it threw ' + thrownDescription(thrown) + '.';
+ });
+ }
+ }
+ };
- beforeEach: function() {
- return env.beforeEach.apply(env, arguments);
- },
+ function thrownDescription(thrown) {
+ if (thrown && thrown.constructor) {
+ return j$.fnNameFor(thrown.constructor) + ' with message ' +
+ matchersUtil.pp(thrown.message);
+ } else {
+ return matchersUtil.pp(thrown);
+ }
+ }
+ }
- afterEach: function() {
- return env.afterEach.apply(env, arguments);
- },
+ function pass(message) {
+ return {
+ pass: true,
+ message: message
+ };
+ }
- beforeAll: function() {
- return env.beforeAll.apply(env, arguments);
- },
+ function fail(message) {
+ return {
+ pass: false,
+ message: message
+ };
+ }
- afterAll: function() {
- return env.afterAll.apply(env, arguments);
- },
+ return toThrowMatching;
+};
- expect: function(actual) {
- return env.expect(actual);
- },
+getJasmineRequireObj().MockDate = function() {
+ function MockDate(global) {
+ var self = this;
+ var currentTime = 0;
- pending: function() {
+ 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().makePrettyPrinter = function(j$) {
+ function SinglePrettyPrintRun(customObjectFormatters, pp) {
+ this.customObjectFormatters_ = customObjectFormatters;
+ this.ppNestLevel_ = 0;
+ this.seen = [];
+ this.length = 0;
+ this.stringParts = [];
+ this.pp_ = pp;
+ }
+
+ function hasCustomToString(value) {
+ // value.toString !== Object.prototype.toString if value has no custom toString but is from another
context (e.g.
+ // iframe, web worker)
+ try {
+ return (
+ j$.isFunction_(value.toString) &&
+ value.toString !== Object.prototype.toString &&
+ value.toString() !== Object.prototype.toString.call(value)
+ );
+ } catch (e) {
+ // The custom toString() threw.
+ return true;
+ }
+ }
+
+ SinglePrettyPrintRun.prototype.format = function(value) {
+ this.ppNestLevel_++;
+ try {
+ var customFormatResult = this.applyCustomFormatters_(value);
+
+ if (customFormatResult) {
+ this.emitScalar(customFormatResult);
+ } else 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(this.pp_));
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (j$.isSpy(value)) {
+ this.emitScalar('spy on ' + value.and.identity);
+ } else if (j$.isSpy(value.toString)) {
+ this.emitScalar('spy on ' + value.toString.and.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (j$.isDomNode(value)) {
+ if (value.tagName) {
+ this.emitDomElement(value);
+ } else {
+ this.emitScalar('HTMLNode');
+ }
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (j$.isSet(value)) {
+ this.emitSet(value);
+ } else if (j$.isMap(value)) {
+ this.emitMap(value);
+ } else if (j$.isTypedArray_(value)) {
+ this.emitTypedArray(value);
+ } else if (
+ value.toString &&
+ typeof value === 'object' &&
+ !j$.isArray_(value) &&
+ hasCustomToString(value)
+ ) {
+ try {
+ this.emitScalar(value.toString());
+ } catch (e) {
+ this.emitScalar('has-invalid-toString-method');
+ }
+ } 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());
+ }
+ } catch (e) {
+ if (this.ppNestLevel_ > 1 || !(e instanceof MaxCharsReachedError)) {
+ throw e;
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+ };
+
+ SinglePrettyPrintRun.prototype.applyCustomFormatters_ = function(value) {
+ return customFormat(value, this.customObjectFormatters_);
+ };
+
+ SinglePrettyPrintRun.prototype.iterateObject = function(obj, fn) {
+ var objKeys = keys(obj, j$.isArray_(obj));
+ var isGetter = function isGetter(prop) {};
+
+ if (obj.__lookupGetter__) {
+ isGetter = function isGetter(prop) {
+ var getter = obj.__lookupGetter__(prop);
+ return !j$.util.isUndefined(getter) && getter !== null;
+ };
+ }
+ var length = Math.min(objKeys.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+ for (var i = 0; i < length; i++) {
+ var property = objKeys[i];
+ fn(property, isGetter(property));
+ }
+
+ return objKeys.length > length;
+ };
+
+ SinglePrettyPrintRun.prototype.emitScalar = function(value) {
+ this.append(value);
+ };
+
+ SinglePrettyPrintRun.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+ };
+
+ SinglePrettyPrintRun.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;
+ var truncated = this.iterateObject(array, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.formatProperty(array, property, isGetter);
+ });
+
+ if (truncated) {
+ this.append(', ...');
+ }
+
+ this.append(' ]');
+ };
+
+ SinglePrettyPrintRun.prototype.emitSet = function(set) {
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ this.append('Set');
+ return;
+ }
+ this.append('Set( ');
+ var size = Math.min(set.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+ var i = 0;
+ set.forEach(function(value, key) {
+ if (i >= size) {
+ return;
+ }
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(value);
+
+ i++;
+ }, this);
+ if (set.size > size) {
+ this.append(', ...');
+ }
+ this.append(' )');
+ };
+
+ SinglePrettyPrintRun.prototype.emitMap = function(map) {
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ this.append('Map');
+ return;
+ }
+ this.append('Map( ');
+ var size = Math.min(map.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+ var i = 0;
+ map.forEach(function(value, key) {
+ if (i >= size) {
+ return;
+ }
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format([key, value]);
+
+ i++;
+ }, this);
+ if (map.size > size) {
+ this.append(', ...');
+ }
+ this.append(' )');
+ };
+
+ SinglePrettyPrintRun.prototype.emitObject = function(obj) {
+ var ctor = obj.constructor,
+ constructorName;
+
+ constructorName =
+ typeof ctor === 'function' && obj instanceof ctor
+ ? 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;
+
+ var truncated = this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.formatProperty(obj, property, isGetter);
+ });
+
+ if (truncated) {
+ this.append(', ...');
+ }
+
+ this.append(' })');
+ };
+
+ SinglePrettyPrintRun.prototype.emitTypedArray = function(arr) {
+ var constructorName = j$.fnNameFor(arr.constructor),
+ limitedArray = Array.prototype.slice.call(
+ arr,
+ 0,
+ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH
+ ),
+ itemsString = Array.prototype.join.call(limitedArray, ', ');
+
+ if (limitedArray.length !== arr.length) {
+ itemsString += ', ...';
+ }
+
+ this.append(constructorName + ' [ ' + itemsString + ' ]');
+ };
+
+ SinglePrettyPrintRun.prototype.emitDomElement = function(el) {
+ var tagName = el.tagName.toLowerCase(),
+ attrs = el.attributes,
+ i,
+ len = attrs.length,
+ out = '<' + tagName,
+ attr;
+
+ for (i = 0; i < len; i++) {
+ attr = attrs[i];
+ out += ' ' + attr.name;
+
+ if (attr.value !== '') {
+ out += '="' + attr.value + '"';
+ }
+ }
+
+ out += '>';
+
+ if (el.childElementCount !== 0 || el.textContent !== '') {
+ out += '...</' + tagName + '>';
+ }
+
+ this.append(out);
+ };
+
+ SinglePrettyPrintRun.prototype.formatProperty = function(
+ obj,
+ property,
+ isGetter
+ ) {
+ this.append(property);
+ this.append(': ');
+ if (isGetter) {
+ this.append('<getter>');
+ } else {
+ this.format(obj[property]);
+ }
+ };
+
+ SinglePrettyPrintRun.prototype.append = function(value) {
+ // This check protects us from the rare case where an object has overriden
+ // `toString()` with an invalid implementation (returning a non-string).
+ if (typeof value !== 'string') {
+ value = Object.prototype.toString.call(value);
+ }
+
+ var result = truncate(value, j$.MAX_PRETTY_PRINT_CHARS - this.length);
+ this.length += result.value.length;
+ this.stringParts.push(result.value);
+
+ if (result.truncated) {
+ throw new MaxCharsReachedError();
+ }
+ };
+
+ function truncate(s, maxlen) {
+ if (s.length <= maxlen) {
+ return { value: s, truncated: false };
+ }
+
+ s = s.substring(0, maxlen - 4) + ' ...';
+ return { value: s, truncated: true };
+ }
+
+ function MaxCharsReachedError() {
+ this.message =
+ 'Exceeded ' +
+ j$.MAX_PRETTY_PRINT_CHARS +
+ ' characters while pretty-printing a value';
+ }
+
+ MaxCharsReachedError.prototype = new Error();
+
+ function keys(obj, isArray) {
+ var allKeys = Object.keys
+ ? Object.keys(obj)
+ : (function(o) {
+ var keys = [];
+ for (var key in o) {
+ if (j$.util.has(o, key)) {
+ keys.push(key);
+ }
+ }
+ return keys;
+ })(obj);
+
+ if (!isArray) {
+ return allKeys;
+ }
+
+ if (allKeys.length === 0) {
+ return allKeys;
+ }
+
+ var extraKeys = [];
+ for (var i = 0; i < allKeys.length; i++) {
+ if (!/^[0-9]+$/.test(allKeys[i])) {
+ extraKeys.push(allKeys[i]);
+ }
+ }
+
+ return extraKeys;
+ }
+
+ function customFormat(value, customObjectFormatters) {
+ var i, result;
+
+ for (i = 0; i < customObjectFormatters.length; i++) {
+ result = customObjectFormatters[i](value);
+
+ if (result !== undefined) {
+ return result;
+ }
+ }
+ }
+
+ return function(customObjectFormatters) {
+ customObjectFormatters = customObjectFormatters || [];
+
+ var pp = function(value) {
+ var prettyPrinter = new SinglePrettyPrintRun(customObjectFormatters, pp);
+ prettyPrinter.format(value);
+ return prettyPrinter.stringParts.join('');
+ };
+
+ pp.customFormat_ = function(value) {
+ return customFormat(value, customObjectFormatters);
+ };
+
+ return pp;
+ };
+};
+
+getJasmineRequireObj().QueueRunner = function(j$) {
+ function StopExecutionError() {}
+ StopExecutionError.prototype = new Error();
+ j$.StopExecutionError = StopExecutionError;
+
+ function once(fn) {
+ var called = false;
+ return function(arg) {
+ if (!called) {
+ called = true;
+ // Direct call using single parameter, because cleanup/next does not need more
+ fn(arg);
+ }
+ return null;
+ };
+ }
+
+ function emptyFn() {}
+
+ function QueueRunner(attrs) {
+ var queueableFns = attrs.queueableFns || [];
+ this.queueableFns = queueableFns.concat(attrs.cleanupFns || []);
+ this.firstCleanupIx = queueableFns.length;
+ this.onComplete = attrs.onComplete || emptyFn;
+ this.clearStack =
+ attrs.clearStack ||
+ function(fn) {
+ fn();
+ };
+ this.onException = attrs.onException || emptyFn;
+ this.userContext = attrs.userContext || new j$.UserContext();
+ this.timeout = attrs.timeout || {
+ setTimeout: setTimeout,
+ clearTimeout: clearTimeout
+ };
+ this.fail = attrs.fail || emptyFn;
+ this.globalErrors = attrs.globalErrors || {
+ pushListener: emptyFn,
+ popListener: emptyFn
+ };
+ this.completeOnFirstError = !!attrs.completeOnFirstError;
+ this.errored = false;
+
+ if (typeof this.onComplete !== 'function') {
+ throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete));
+ }
+ this.deprecated = attrs.deprecated;
+ }
+
+ QueueRunner.prototype.execute = function() {
+ var self = this;
+ this.handleFinalError = function(message, source, lineno, colno, error) {
+ // Older browsers would send the error as the first parameter. HTML5
+ // specifies the the five parameters above. The error instance should
+ // be preffered, otherwise the call stack would get lost.
+ self.onException(error || message);
+ };
+ this.globalErrors.pushListener(this.handleFinalError);
+ this.run(0);
+ };
+
+ QueueRunner.prototype.skipToCleanup = function(lastRanIndex) {
+ if (lastRanIndex < this.firstCleanupIx) {
+ this.run(this.firstCleanupIx);
+ } else {
+ this.run(lastRanIndex + 1);
+ }
+ };
+
+ QueueRunner.prototype.clearTimeout = function(timeoutId) {
+ Function.prototype.apply.apply(this.timeout.clearTimeout, [
+ j$.getGlobal(),
+ [timeoutId]
+ ]);
+ };
+
+ QueueRunner.prototype.setTimeout = function(fn, timeout) {
+ return Function.prototype.apply.apply(this.timeout.setTimeout, [
+ j$.getGlobal(),
+ [fn, timeout]
+ ]);
+ };
+
+ QueueRunner.prototype.attempt = function attempt(iterativeIndex) {
+ var self = this,
+ completedSynchronously = true,
+ handleError = function handleError(error) {
+ onException(error);
+ next(error);
+ },
+ cleanup = once(function cleanup() {
+ if (timeoutId !== void 0) {
+ self.clearTimeout(timeoutId);
+ }
+ self.globalErrors.popListener(handleError);
+ }),
+ next = once(function next(err) {
+ cleanup();
+
+ if (j$.isError_(err)) {
+ if (!(err instanceof StopExecutionError) && !err.jasmineMessage) {
+ self.fail(err);
+ }
+ self.errored = errored = true;
+ }
+
+ function runNext() {
+ if (self.completeOnFirstError && errored) {
+ self.skipToCleanup(iterativeIndex);
+ } else {
+ self.run(iterativeIndex + 1);
+ }
+ }
+
+ if (completedSynchronously) {
+ self.setTimeout(runNext);
+ } else {
+ runNext();
+ }
+ }),
+ errored = false,
+ queueableFn = self.queueableFns[iterativeIndex],
+ timeoutId;
+
+ next.fail = function nextFail() {
+ self.fail.apply(null, arguments);
+ self.errored = errored = true;
+ next();
+ };
+
+ self.globalErrors.pushListener(handleError);
+
+ if (queueableFn.timeout !== undefined) {
+ var timeoutInterval = queueableFn.timeout || j$.DEFAULT_TIMEOUT_INTERVAL;
+ timeoutId = self.setTimeout(function() {
+ var error = new Error(
+ 'Timeout - Async function did not complete within ' +
+ timeoutInterval +
+ 'ms ' +
+ (queueableFn.timeout
+ ? '(custom timeout)'
+ : '(set by jasmine.DEFAULT_TIMEOUT_INTERVAL)')
+ );
+ onException(error);
+ next();
+ }, timeoutInterval);
+ }
+
+ try {
+ if (queueableFn.fn.length === 0) {
+ var maybeThenable = queueableFn.fn.call(self.userContext);
+
+ if (maybeThenable && j$.isFunction_(maybeThenable.then)) {
+ maybeThenable.then(next, onPromiseRejection);
+ completedSynchronously = false;
+ return { completedSynchronously: false };
+ }
+ } else {
+ queueableFn.fn.call(self.userContext, next);
+ completedSynchronously = false;
+ return { completedSynchronously: false };
+ }
+ } catch (e) {
+ onException(e);
+ self.errored = errored = true;
+ }
+
+ cleanup();
+ return { completedSynchronously: true, errored: errored };
+
+ function onException(e) {
+ self.onException(e);
+ self.errored = errored = true;
+ }
+
+ function onPromiseRejection(e) {
+ onException(e);
+ next();
+ }
+ };
+
+ QueueRunner.prototype.run = function(recursiveIndex) {
+ var length = this.queueableFns.length,
+ self = this,
+ iterativeIndex;
+
+ for (
+ iterativeIndex = recursiveIndex;
+ iterativeIndex < length;
+ iterativeIndex++
+ ) {
+ var result = this.attempt(iterativeIndex);
+
+ if (!result.completedSynchronously) {
+ return;
+ }
+
+ self.errored = self.errored || result.errored;
+
+ if (this.completeOnFirstError && result.errored) {
+ this.skipToCleanup(iterativeIndex);
+ return;
+ }
+ }
+
+ this.clearStack(function() {
+ self.globalErrors.popListener(self.handleFinalError);
+ self.onComplete(self.errored && new StopExecutionError());
+ });
+ };
+
+ return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function(j$) {
+ function ReportDispatcher(methods, queueRunnerFactory) {
+ 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);
+ }
+ var onComplete = args[args.length - 1];
+ args = j$.util.argsToArray(args).splice(0, args.length - 1);
+ var fns = [];
+ for (var i = 0; i < reporters.length; i++) {
+ var reporter = reporters[i];
+ addFn(fns, reporter, method, args);
+ }
+
+ queueRunnerFactory({
+ queueableFns: fns,
+ onComplete: onComplete,
+ isReporter: true
+ });
+ }
+
+ function addFn(fns, reporter, method, args) {
+ var fn = reporter[method];
+ if (!fn) {
+ return;
+ }
+
+ var thisArgs = j$.util.cloneArgs(args);
+ if (fn.length <= 1) {
+ fns.push({
+ fn: function() {
+ return fn.apply(reporter, thisArgs);
+ }
+ });
+ } else {
+ fns.push({
+ fn: function(done) {
+ return fn.apply(reporter, thisArgs.concat([done]));
+ }
+ });
+ }
+ }
+ }
+
+ return ReportDispatcher;
+};
+
+getJasmineRequireObj().interface = function(jasmine, env) {
+ var jasmineInterface = {
+ /**
+ * Callback passed to parts of the Jasmine base interface.
+ *
+ * By default Jasmine assumes this function completes synchronously.
+ * If you have code that you need to test asynchronously, you can declare that you receive a `done`
callback, return a Promise, or use the `async` keyword if it is supported in your environment.
+ * @callback implementationCallback
+ * @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine
should wait until it has been called before moving on.
+ * @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for
completion.
+ */
+
+ /**
+ * Create a group of specs (often called a suite).
+ *
+ * Calls to `describe` can be nested within other calls to compose your suite as a tree.
+ * @name describe
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {String} description Textual description of the group
+ * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and
specs
+ */
+ describe: function(description, specDefinitions) {
+ return env.describe(description, specDefinitions);
+ },
+
+ /**
+ * A temporarily disabled [`describe`]{@link describe}
+ *
+ * Specs within an `xdescribe` will be marked pending and not executed
+ * @name xdescribe
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {String} description Textual description of the group
+ * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and
specs
+ */
+ xdescribe: function(description, specDefinitions) {
+ return env.xdescribe(description, specDefinitions);
+ },
+
+ /**
+ * A focused [`describe`]{@link describe}
+ *
+ * If suites or specs are focused, only those that are focused will be executed
+ * @see fit
+ * @name fdescribe
+ * @since 2.1.0
+ * @function
+ * @global
+ * @param {String} description Textual description of the group
+ * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites and
specs
+ */
+ fdescribe: function(description, specDefinitions) {
+ return env.fdescribe(description, specDefinitions);
+ },
+
+ /**
+ * Define a single spec. A spec should contain one or more {@link expect|expectations} that test the
state of the code.
+ *
+ * A spec whose expectations all succeed will be passing and a spec with any failures will fail.
+ * The name `it` is a pronoun for the test target, not an abbreviation of anything. It makes the
+ * spec more readable by connecting the function name `it` and the argument `description` as a
+ * complete sentence.
+ * @name it
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {String} description Textual description of what this spec is checking
+ * @param {implementationCallback} [testFunction] Function that contains the code of your test. If not
provided the test will be `pending`.
+ * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec.
+ * @see async
+ */
+ it: function() {
+ return env.it.apply(env, arguments);
+ },
+
+ /**
+ * A temporarily disabled [`it`]{@link it}
+ *
+ * The spec will report as `pending` and will not be executed.
+ * @name xit
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {String} description Textual description of what this spec is checking.
+ * @param {implementationCallback} [testFunction] Function that contains the code of your test. Will not
be executed.
+ */
+ xit: function() {
+ return env.xit.apply(env, arguments);
+ },
+
+ /**
+ * A focused [`it`]{@link it}
+ *
+ * If suites or specs are focused, only those that are focused will be executed.
+ * @name fit
+ * @since 2.1.0
+ * @function
+ * @global
+ * @param {String} description Textual description of what this spec is checking.
+ * @param {implementationCallback} testFunction Function that contains the code of your test.
+ * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec.
+ * @see async
+ */
+ fit: function() {
+ return env.fit.apply(env, arguments);
+ },
+
+ /**
+ * Run some shared setup before each of the specs in the {@link describe} in which it is called.
+ * @name beforeEach
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {implementationCallback} [function] Function that contains the code to setup your specs.
+ * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async
beforeEach.
+ * @see async
+ */
+ beforeEach: function() {
+ return env.beforeEach.apply(env, arguments);
+ },
+
+ /**
+ * Run some shared teardown after each of the specs in the {@link describe} in which it is called.
+ * @name afterEach
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {implementationCallback} [function] Function that contains the code to teardown your specs.
+ * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterEach.
+ * @see async
+ */
+ afterEach: function() {
+ return env.afterEach.apply(env, arguments);
+ },
+
+ /**
+ * Run some shared setup once before all of the specs in the {@link describe} are run.
+ *
+ * _Note:_ Be careful, sharing the setup from a beforeAll makes it easy to accidentally leak state
between your specs so that they erroneously pass or fail.
+ * @name beforeAll
+ * @since 2.1.0
+ * @function
+ * @global
+ * @param {implementationCallback} [function] Function that contains the code to setup your specs.
+ * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeAll.
+ * @see async
+ */
+ beforeAll: function() {
+ return env.beforeAll.apply(env, arguments);
+ },
+
+ /**
+ * Run some shared teardown once after all of the specs in the {@link describe} are run.
+ *
+ * _Note:_ Be careful, sharing the teardown from a afterAll makes it easy to accidentally leak state
between your specs so that they erroneously pass or fail.
+ * @name afterAll
+ * @since 2.1.0
+ * @function
+ * @global
+ * @param {implementationCallback} [function] Function that contains the code to teardown your specs.
+ * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterAll.
+ * @see async
+ */
+ afterAll: function() {
+ return env.afterAll.apply(env, arguments);
+ },
+
+ /**
+ * Sets a user-defined property that will be provided to reporters as part of the properties field of
{@link SpecResult}
+ * @name setSpecProperty
+ * @since 3.6.0
+ * @function
+ * @param {String} key The name of the property
+ * @param {*} value The value of the property
+ */
+ setSpecProperty: function(key, value) {
+ return env.setSpecProperty(key, value);
+ },
+
+ /**
+ * Sets a user-defined property that will be provided to reporters as part of the properties field of
{@link SuiteResult}
+ * @name setSuiteProperty
+ * @since 3.6.0
+ * @function
+ * @param {String} key The name of the property
+ * @param {*} value The value of the property
+ */
+ setSuiteProperty: function(key, value) {
+ return env.setSuiteProperty(key, value);
+ },
+
+ /**
+ * Create an expectation for a spec.
+ * @name expect
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {Object} actual - Actual computed value to test expectations against.
+ * @return {matchers}
+ */
+ expect: function(actual) {
+ return env.expect(actual);
+ },
+
+ /**
+ * Create an asynchronous expectation for a spec. Note that the matchers
+ * that are provided by an asynchronous expectation all return promises
+ * which must be either returned from the spec or waited for using `await`
+ * in order for Jasmine to associate them with the correct spec.
+ * @name expectAsync
+ * @since 3.3.0
+ * @function
+ * @global
+ * @param {Object} actual - Actual computed value to test expectations against.
+ * @return {async-matchers}
+ * @example
+ * await expectAsync(somePromise).toBeResolved();
+ * @example
+ * return expectAsync(somePromise).toBeResolved();
+ */
+ expectAsync: function(actual) {
+ return env.expectAsync(actual);
+ },
+
+ /**
+ * Mark a spec as pending, expectation results will be ignored.
+ * @name pending
+ * @since 2.0.0
+ * @function
+ * @global
+ * @param {String} [message] - Reason the spec is pending.
+ */
+ pending: function() {
return env.pending.apply(env, arguments);
},
- fail: function() {
- return env.fail.apply(env, arguments);
- },
+ /**
+ * Explicitly mark a spec as failed.
+ * @name fail
+ * @since 2.1.0
+ * @function
+ * @global
+ * @param {String|Error} [error] - Reason for the failure.
+ */
+ fail: function() {
+ return env.fail.apply(env, arguments);
+ },
+
+ /**
+ * Install a spy onto an existing object.
+ * @name spyOn
+ * @since 1.3.0
+ * @function
+ * @global
+ * @param {Object} obj - The object upon which to install the {@link Spy}.
+ * @param {String} methodName - The name of the method to replace with a {@link Spy}.
+ * @returns {Spy}
+ */
+ spyOn: function(obj, methodName) {
+ return env.spyOn(obj, methodName);
+ },
+
+ /**
+ * Install a spy on a property installed with `Object.defineProperty` onto an existing object.
+ * @name spyOnProperty
+ * @since 2.6.0
+ * @function
+ * @global
+ * @param {Object} obj - The object upon which to install the {@link Spy}
+ * @param {String} propertyName - The name of the property to replace with a {@link Spy}.
+ * @param {String} [accessType=get] - The access type (get|set) of the property to {@link Spy} on.
+ * @returns {Spy}
+ */
+ spyOnProperty: function(obj, methodName, accessType) {
+ return env.spyOnProperty(obj, methodName, accessType);
+ },
+
+ /**
+ * Installs spies on all writable and configurable properties of an object.
+ * @name spyOnAllFunctions
+ * @since 3.2.1
+ * @function
+ * @global
+ * @param {Object} obj - The object upon which to install the {@link Spy}s
+ * @returns {Object} the spied object
+ */
+ spyOnAllFunctions: function(obj) {
+ return env.spyOnAllFunctions(obj);
+ },
+
+ jsApiReporter: new jasmine.JsApiReporter({
+ timer: new jasmine.Timer()
+ }),
+
+ /**
+ * @namespace jasmine
+ */
+ jasmine: jasmine
+ };
+
+ /**
+ * Add a custom equality tester for the current scope of specs.
+ *
+ * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+ * @name jasmine.addCustomEqualityTester
+ * @since 2.0.0
+ * @function
+ * @param {Function} tester - A function which takes two arguments to compare and returns a `true` or
`false` comparison result if it knows how to compare them, and `undefined` otherwise.
+ * @see custom_equality
+ */
+ jasmine.addCustomEqualityTester = function(tester) {
+ env.addCustomEqualityTester(tester);
+ };
+
+ /**
+ * Add custom matchers for the current scope of specs.
+ *
+ * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+ * @name jasmine.addMatchers
+ * @since 2.0.0
+ * @function
+ * @param {Object} matchers - Keys from this object will be the new matcher names.
+ * @see custom_matcher
+ */
+ jasmine.addMatchers = function(matchers) {
+ return env.addMatchers(matchers);
+ };
+
+ /**
+ * Add custom async matchers for the current scope of specs.
+ *
+ * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+ * @name jasmine.addAsyncMatchers
+ * @since 3.5.0
+ * @function
+ * @param {Object} matchers - Keys from this object will be the new async matcher names.
+ * @see custom_matcher
+ */
+ jasmine.addAsyncMatchers = function(matchers) {
+ return env.addAsyncMatchers(matchers);
+ };
+
+ /**
+ * Add a custom object formatter for the current scope of specs.
+ *
+ * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+ * @name jasmine.addCustomObjectFormatter
+ * @since 3.6.0
+ * @function
+ * @param {Function} formatter - A function which takes a value to format and returns a string if it knows
how to format it, and `undefined` otherwise.
+ * @see custom_object_formatters
+ */
+ jasmine.addCustomObjectFormatter = function(formatter) {
+ return env.addCustomObjectFormatter(formatter);
+ };
+
+ /**
+ * Get the currently booted mock {Clock} for this Jasmine environment.
+ * @name jasmine.clock
+ * @since 2.0.0
+ * @function
+ * @returns {Clock}
+ */
+ jasmine.clock = function() {
+ return env.clock;
+ };
+
+ /**
+ * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation
behind it.
+ * @name jasmine.createSpy
+ * @since 1.3.0
+ * @function
+ * @param {String} [name] - Name to give the spy. This will be displayed in failure messages.
+ * @param {Function} [originalFn] - Function to act as the real implementation.
+ * @return {Spy}
+ */
+ jasmine.createSpy = function(name, originalFn) {
+ return env.createSpy(name, originalFn);
+ };
+
+ /**
+ * Create an object with multiple {@link Spy}s as its members.
+ * @name jasmine.createSpyObj
+ * @since 1.3.0
+ * @function
+ * @param {String} [baseName] - Base name for the spies in the object.
+ * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys
will be method names and values the {@link Spy#and#returnValue|returnValue}.
+ * @param {String[]|Object} [propertyNames] - Array of property names to create spies for, or Object whose
keys will be propertynames and values the {@link Spy#and#returnValue|returnValue}.
+ * @return {Object}
+ */
+ jasmine.createSpyObj = function(baseName, methodNames, propertyNames) {
+ return env.createSpyObj(baseName, methodNames, propertyNames);
+ };
+
+ /**
+ * Add a custom spy strategy for the current scope of specs.
+ *
+ * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+ * @name jasmine.addSpyStrategy
+ * @since 3.5.0
+ * @function
+ * @param {String} name - The name of the strategy (i.e. what you call from `and`)
+ * @param {Function} factory - Factory function that returns the plan to be executed.
+ */
+ jasmine.addSpyStrategy = function(name, factory) {
+ return env.addSpyStrategy(name, factory);
+ };
+
+ /**
+ * Set the default spy strategy for the current scope of specs.
+ *
+ * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
+ * @name jasmine.setDefaultSpyStrategy
+ * @function
+ * @param {Function} defaultStrategyFn - a function that assigns a strategy
+ * @example
+ * beforeEach(function() {
+ * jasmine.setDefaultSpyStrategy(and => and.returnValue(true));
+ * });
+ */
+ jasmine.setDefaultSpyStrategy = function(defaultStrategyFn) {
+ return env.setDefaultSpyStrategy(defaultStrategyFn);
+ };
+
+ return jasmineInterface;
+};
+
+getJasmineRequireObj().Spy = function(j$) {
+ var nextOrder = (function() {
+ var order = 0;
+
+ return function() {
+ return order++;
+ };
+ })();
+
+ var matchersUtil = new j$.MatchersUtil({
+ customTesters: [],
+ pp: j$.makePrettyPrinter()
+ });
+
+ /**
+ * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link
jasmine.createSpy}, or {@link jasmine.createSpyObj}
+ * @constructor
+ * @name Spy
+ */
+ function Spy(
+ name,
+ originalFn,
+ customStrategies,
+ defaultStrategyFn,
+ getPromise
+ ) {
+ var numArgs = typeof originalFn === 'function' ? originalFn.length : 0,
+ wrapper = makeFunc(numArgs, function(context, args, invokeNew) {
+ return spy(context, args, invokeNew);
+ }),
+ strategyDispatcher = new SpyStrategyDispatcher({
+ name: name,
+ fn: originalFn,
+ getSpy: function() {
+ return wrapper;
+ },
+ customStrategies: customStrategies,
+ getPromise: getPromise
+ }),
+ callTracker = new j$.CallTracker(),
+ spy = function(context, args, invokeNew) {
+ /**
+ * @name Spy.callData
+ * @property {object} object - `this` context for the invocation.
+ * @property {number} invocationOrder - Order of the invocation.
+ * @property {Array} args - The arguments passed for this invocation.
+ */
+ var callData = {
+ object: context,
+ invocationOrder: nextOrder(),
+ args: Array.prototype.slice.apply(args)
+ };
+
+ callTracker.track(callData);
+ var returnValue = strategyDispatcher.exec(context, args, invokeNew);
+ callData.returnValue = returnValue;
+
+ return returnValue;
+ };
+
+ function makeFunc(length, fn) {
+ switch (length) {
+ case 1:
+ return function wrap1(a) {
+ return fn(this, arguments, this instanceof wrap1);
+ };
+ case 2:
+ return function wrap2(a, b) {
+ return fn(this, arguments, this instanceof wrap2);
+ };
+ case 3:
+ return function wrap3(a, b, c) {
+ return fn(this, arguments, this instanceof wrap3);
+ };
+ case 4:
+ return function wrap4(a, b, c, d) {
+ return fn(this, arguments, this instanceof wrap4);
+ };
+ case 5:
+ return function wrap5(a, b, c, d, e) {
+ return fn(this, arguments, this instanceof wrap5);
+ };
+ case 6:
+ return function wrap6(a, b, c, d, e, f) {
+ return fn(this, arguments, this instanceof wrap6);
+ };
+ case 7:
+ return function wrap7(a, b, c, d, e, f, g) {
+ return fn(this, arguments, this instanceof wrap7);
+ };
+ case 8:
+ return function wrap8(a, b, c, d, e, f, g, h) {
+ return fn(this, arguments, this instanceof wrap8);
+ };
+ case 9:
+ return function wrap9(a, b, c, d, e, f, g, h, i) {
+ return fn(this, arguments, this instanceof wrap9);
+ };
+ default:
+ return function wrap() {
+ return fn(this, arguments, this instanceof wrap);
+ };
+ }
+ }
+
+ 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"
+ );
+ }
+
+ wrapper[prop] = originalFn[prop];
+ }
+
+ /**
+ * @member {SpyStrategy} - Accesses the default strategy for the spy. This strategy will be used
+ * whenever the spy is called with arguments that don't match any strategy
+ * created with {@link Spy#withArgs}.
+ * @name Spy#and
+ * @since 2.0.0
+ * @example
+ * spyOn(someObj, 'func').and.returnValue(42);
+ */
+ wrapper.and = strategyDispatcher.and;
+ /**
+ * Specifies a strategy to be used for calls to the spy that have the
+ * specified arguments.
+ * @name Spy#withArgs
+ * @since 3.0.0
+ * @function
+ * @param {...*} args - The arguments to match
+ * @type {SpyStrategy}
+ * @example
+ * spyOn(someObj, 'func').withArgs(1, 2, 3).and.returnValue(42);
+ * someObj.func(1, 2, 3); // returns 42
+ */
+ wrapper.withArgs = function() {
+ return strategyDispatcher.withArgs.apply(strategyDispatcher, arguments);
+ };
+ wrapper.calls = callTracker;
+
+ if (defaultStrategyFn) {
+ defaultStrategyFn(wrapper.and);
+ }
+
+ return wrapper;
+ }
+
+ function SpyStrategyDispatcher(strategyArgs) {
+ var baseStrategy = new j$.SpyStrategy(strategyArgs);
+ var argsStrategies = new StrategyDict(function() {
+ return new j$.SpyStrategy(strategyArgs);
+ });
+
+ this.and = baseStrategy;
+
+ this.exec = function(spy, args, invokeNew) {
+ var strategy = argsStrategies.get(args);
+
+ if (!strategy) {
+ if (argsStrategies.any() && !baseStrategy.isConfigured()) {
+ throw new Error(
+ "Spy '" +
+ strategyArgs.name +
+ "' received a call with arguments " +
+ j$.pp(Array.prototype.slice.call(args)) +
+ ' but all configured strategies specify other arguments.'
+ );
+ } else {
+ strategy = baseStrategy;
+ }
+ }
+
+ return strategy.exec(spy, args, invokeNew);
+ };
+
+ this.withArgs = function() {
+ return { and: argsStrategies.getOrCreate(arguments) };
+ };
+ }
+
+ function StrategyDict(strategyFactory) {
+ this.strategies = [];
+ this.strategyFactory = strategyFactory;
+ }
+
+ StrategyDict.prototype.any = function() {
+ return this.strategies.length > 0;
+ };
+
+ StrategyDict.prototype.getOrCreate = function(args) {
+ var strategy = this.get(args);
+
+ if (!strategy) {
+ strategy = this.strategyFactory();
+ this.strategies.push({
+ args: args,
+ strategy: strategy
+ });
+ }
+
+ return strategy;
+ };
+
+ StrategyDict.prototype.get = function(args) {
+ var i;
+
+ for (i = 0; i < this.strategies.length; i++) {
+ if (matchersUtil.equals(args, this.strategies[i].args)) {
+ return this.strategies[i].strategy;
+ }
+ }
+ };
+
+ return Spy;
+};
+
+getJasmineRequireObj().SpyFactory = function(j$) {
+ function SpyFactory(getCustomStrategies, getDefaultStrategyFn, getPromise) {
+ var self = this;
+
+ this.createSpy = function(name, originalFn) {
+ return j$.Spy(
+ name,
+ originalFn,
+ getCustomStrategies(),
+ getDefaultStrategyFn(),
+ getPromise
+ );
+ };
+
+ this.createSpyObj = function(baseName, methodNames, propertyNames) {
+ var baseNameIsCollection =
+ j$.isObject_(baseName) || j$.isArray_(baseName);
+
+ if (baseNameIsCollection) {
+ propertyNames = methodNames;
+ methodNames = baseName;
+ baseName = 'unknown';
+ }
+
+ var obj = {};
+ var spy, descriptor;
+
+ var methods = normalizeKeyValues(methodNames);
+ for (var i = 0; i < methods.length; i++) {
+ spy = obj[methods[i][0]] = self.createSpy(
+ baseName + '.' + methods[i][0]
+ );
+ if (methods[i].length > 1) {
+ spy.and.returnValue(methods[i][1]);
+ }
+ }
+
+ var properties = normalizeKeyValues(propertyNames);
+ for (var i = 0; i < properties.length; i++) {
+ descriptor = {
+ get: self.createSpy(baseName + '.' + properties[i][0] + '.get'),
+ set: self.createSpy(baseName + '.' + properties[i][0] + '.set')
+ };
+ if (properties[i].length > 1) {
+ descriptor.get.and.returnValue(properties[i][1]);
+ descriptor.set.and.returnValue(properties[i][1]);
+ }
+ Object.defineProperty(obj, properties[i][0], descriptor);
+ }
+
+ if (methods.length === 0 && properties.length === 0) {
+ throw 'createSpyObj requires a non-empty array or object of method names to create spies for';
+ }
+
+ return obj;
+ };
+ }
+
+ function normalizeKeyValues(object) {
+ var result = [];
+ if (j$.isArray_(object)) {
+ for (var i = 0; i < object.length; i++) {
+ result.push([object[i]]);
+ }
+ } else if (j$.isObject_(object)) {
+ for (var key in object) {
+ if (object.hasOwnProperty(key)) {
+ result.push([key, object[key]]);
+ }
+ }
+ }
+ return result;
+ }
+
+ return SpyFactory;
+};
+
+getJasmineRequireObj().SpyRegistry = function(j$) {
+ var spyOnMsg = j$.formatErrorMsg('<spyOn>', 'spyOn(<object>, <methodName>)');
+ var spyOnPropertyMsg = j$.formatErrorMsg(
+ '<spyOnProperty>',
+ 'spyOnProperty(<object>, <propName>, [accessType])'
+ );
+
+ function SpyRegistry(options) {
+ options = options || {};
+ var global = options.global || j$.getGlobal();
+ var createSpy = options.createSpy;
+ var currentSpies =
+ options.currentSpies ||
+ function() {
+ return [];
+ };
+
+ this.allowRespy = function(allow) {
+ this.respy = allow;
+ };
+
+ this.spyOn = function(obj, methodName) {
+ var getErrorMsg = spyOnMsg;
+
+ if (j$.util.isUndefined(obj) || obj === null) {
+ throw new Error(
+ getErrorMsg(
+ 'could not find an object to spy upon for ' + methodName + '()'
+ )
+ );
+ }
+
+ if (j$.util.isUndefined(methodName) || methodName === null) {
+ 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 = Object.getOwnPropertyDescriptor(obj, methodName);
+
+ if (descriptor && !(descriptor.writable || descriptor.set)) {
+ throw new Error(
+ getErrorMsg(methodName + ' is not declared writable or has no setter')
+ );
+ }
+
+ var originalMethod = obj[methodName],
+ spiedMethod = createSpy(methodName, originalMethod),
+ restoreStrategy;
+
+ if (
+ Object.prototype.hasOwnProperty.call(obj, methodName) ||
+ (obj === global && methodName === 'onerror')
+ ) {
+ 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.spyOnProperty = function(obj, propertyName, accessType) {
+ var getErrorMsg = spyOnPropertyMsg;
+
+ accessType = accessType || 'get';
+
+ if (j$.util.isUndefined(obj)) {
+ throw new Error(
+ getErrorMsg(
+ 'spyOn could not find an object to spy upon for ' +
+ propertyName +
+ ''
+ )
+ );
+ }
+
+ if (j$.util.isUndefined(propertyName)) {
+ throw new Error(getErrorMsg('No property name supplied'));
+ }
+
+ var descriptor = j$.util.getPropertyDescriptor(obj, propertyName);
+
+ if (!descriptor) {
+ throw new Error(getErrorMsg(propertyName + ' property does not exist'));
+ }
+
+ if (!descriptor.configurable) {
+ throw new Error(
+ getErrorMsg(propertyName + ' is not declared configurable')
+ );
+ }
+
+ if (!descriptor[accessType]) {
+ throw new Error(
+ getErrorMsg(
+ 'Property ' +
+ propertyName +
+ ' does not have access type ' +
+ accessType
+ )
+ );
+ }
+
+ if (j$.isSpy(descriptor[accessType])) {
+ if (this.respy) {
+ return descriptor[accessType];
+ } else {
+ throw new Error(
+ getErrorMsg(
+ propertyName + '#' + accessType + ' has already been spied upon'
+ )
+ );
+ }
+ }
+
+ var originalDescriptor = j$.util.clone(descriptor),
+ spy = createSpy(propertyName, descriptor[accessType]),
+ restoreStrategy;
+
+ if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
+ restoreStrategy = function() {
+ Object.defineProperty(obj, propertyName, originalDescriptor);
+ };
+ } else {
+ restoreStrategy = function() {
+ delete obj[propertyName];
+ };
+ }
+
+ currentSpies().push({
+ restoreObjectToOriginalState: restoreStrategy
+ });
+
+ descriptor[accessType] = spy;
+
+ Object.defineProperty(obj, propertyName, descriptor);
+
+ return spy;
+ };
+
+ this.spyOnAllFunctions = function(obj) {
+ if (j$.util.isUndefined(obj)) {
+ throw new Error(
+ 'spyOnAllFunctions could not find an object to spy upon'
+ );
+ }
+
+ var pointer = obj,
+ props = [],
+ prop,
+ descriptor;
+
+ while (pointer) {
+ for (prop in pointer) {
+ if (
+ Object.prototype.hasOwnProperty.call(pointer, prop) &&
+ pointer[prop] instanceof Function
+ ) {
+ descriptor = Object.getOwnPropertyDescriptor(pointer, prop);
+ if (
+ (descriptor.writable || descriptor.set) &&
+ descriptor.configurable
+ ) {
+ props.push(prop);
+ }
+ }
+ }
+ pointer = Object.getPrototypeOf(pointer);
+ }
+
+ for (var i = 0; i < props.length; i++) {
+ this.spyOn(obj, props[i]);
+ }
+
+ return obj;
+ };
+
+ 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$) {
+ /**
+ * @interface SpyStrategy
+ */
+ function SpyStrategy(options) {
+ options = options || {};
+
+ var self = this;
+
+ /**
+ * Get the identifying information for the spy.
+ * @name SpyStrategy#identity
+ * @since 3.0.0
+ * @member
+ * @type {String}
+ */
+ this.identity = options.name || 'unknown';
+ this.originalFn = options.fn || function() {};
+ this.getSpy = options.getSpy || function() {};
+ this.plan = this._defaultPlan = function() {};
+
+ var k,
+ cs = options.customStrategies || {};
+ for (k in cs) {
+ if (j$.util.has(cs, k) && !this[k]) {
+ this[k] = createCustomPlan(cs[k]);
+ }
+ }
+
+ var getPromise =
+ typeof options.getPromise === 'function'
+ ? options.getPromise
+ : function() {};
+
+ var requirePromise = function(name) {
+ var Promise = getPromise();
+
+ if (!Promise) {
+ throw new Error(
+ name +
+ ' requires global Promise, or `Promise` configured with `jasmine.getEnv().configure()`'
+ );
+ }
+
+ return Promise;
+ };
+
+ /**
+ * Tell the spy to return a promise resolving to the specified value when invoked.
+ * @name SpyStrategy#resolveTo
+ * @since 3.5.0
+ * @function
+ * @param {*} value The value to return.
+ */
+ this.resolveTo = function(value) {
+ var Promise = requirePromise('resolveTo');
+ self.plan = function() {
+ return Promise.resolve(value);
+ };
+ return self.getSpy();
+ };
+
+ /**
+ * Tell the spy to return a promise rejecting with the specified value when invoked.
+ * @name SpyStrategy#rejectWith
+ * @since 3.5.0
+ * @function
+ * @param {*} value The value to return.
+ */
+ this.rejectWith = function(value) {
+ var Promise = requirePromise('rejectWith');
+
+ self.plan = function() {
+ return Promise.reject(value);
+ };
+ return self.getSpy();
+ };
+ }
+
+ function createCustomPlan(factory) {
+ return function() {
+ var plan = factory.apply(null, arguments);
+
+ if (!j$.isFunction_(plan)) {
+ throw new Error('Spy strategy must return a function');
+ }
+
+ this.plan = plan;
+ return this.getSpy();
+ };
+ }
+
+ /**
+ * Execute the current spy strategy.
+ * @name SpyStrategy#exec
+ * @since 2.0.0
+ * @function
+ */
+ SpyStrategy.prototype.exec = function(context, args, invokeNew) {
+ var contextArgs = [context].concat(
+ args ? Array.prototype.slice.call(args) : []
+ );
+ var target = this.plan.bind.apply(this.plan, contextArgs);
+
+ return invokeNew ? new target() : target();
+ };
+
+ /**
+ * Tell the spy to call through to the real implementation when invoked.
+ * @name SpyStrategy#callThrough
+ * @since 2.0.0
+ * @function
+ */
+ SpyStrategy.prototype.callThrough = function() {
+ this.plan = this.originalFn;
+ return this.getSpy();
+ };
+
+ /**
+ * Tell the spy to return the value when invoked.
+ * @name SpyStrategy#returnValue
+ * @since 2.0.0
+ * @function
+ * @param {*} value The value to return.
+ */
+ SpyStrategy.prototype.returnValue = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this.getSpy();
+ };
+
+ /**
+ * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked.
+ * @name SpyStrategy#returnValues
+ * @since 2.1.0
+ * @function
+ * @param {...*} values - Values to be returned on subsequent calls to the spy.
+ */
+ SpyStrategy.prototype.returnValues = function() {
+ var values = Array.prototype.slice.call(arguments);
+ this.plan = function() {
+ return values.shift();
+ };
+ return this.getSpy();
+ };
+
+ /**
+ * Tell the spy to throw an error when invoked.
+ * @name SpyStrategy#throwError
+ * @since 2.0.0
+ * @function
+ * @param {Error|Object|String} something Thing to throw
+ */
+ SpyStrategy.prototype.throwError = function(something) {
+ var error = j$.isString_(something) ? new Error(something) : something;
+ this.plan = function() {
+ throw error;
+ };
+ return this.getSpy();
+ };
+
+ /**
+ * Tell the spy to call a fake implementation when invoked.
+ * @name SpyStrategy#callFake
+ * @since 2.0.0
+ * @function
+ * @param {Function} fn The function to invoke with the passed parameters.
+ */
+ SpyStrategy.prototype.callFake = function(fn) {
+ if (!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) {
+ throw new Error(
+ 'Argument passed to callFake should be a function, got ' + fn
+ );
+ }
+ this.plan = fn;
+ return this.getSpy();
+ };
+
+ /**
+ * Tell the spy to do nothing when invoked. This is the default.
+ * @name SpyStrategy#stub
+ * @since 2.0.0
+ * @function
+ */
+ SpyStrategy.prototype.stub = function(fn) {
+ this.plan = function() {};
+ return this.getSpy();
+ };
+
+ SpyStrategy.prototype.isConfigured = function() {
+ return this.plan !== this._defaultPlan;
+ };
+
+ return SpyStrategy;
+};
+
+getJasmineRequireObj().StackTrace = function(j$) {
+ function StackTrace(error) {
+ var lines = error.stack.split('\n').filter(function(line) {
+ return line !== '';
+ });
+
+ var extractResult = extractMessage(error.message, lines);
+
+ if (extractResult) {
+ this.message = extractResult.message;
+ lines = extractResult.remainder;
+ }
+
+ var parseResult = tryParseFrames(lines);
+ this.frames = parseResult.frames;
+ this.style = parseResult.style;
+ }
+
+ var framePatterns = [
+ // PhantomJS on Linux, Node, Chrome, IE, Edge
+ // e.g. " at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)"
+ // Note that the "function name" can include a surprisingly large set of
+ // characters, including angle brackets and square brackets.
+ {
+ re: /^\s*at ([^\)]+) \(([^\)]+)\)$/,
+ fnIx: 1,
+ fileLineColIx: 2,
+ style: 'v8'
+ },
+
+ // NodeJS alternate form, often mixed in with the Chrome style
+ // e.g. " at /some/path:4320:20
+ { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' },
+
+ // PhantomJS on OS X, Safari, Firefox
+ // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27"
+ // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27"
+ {
+ re: /^(([^@\s]+)@)?([^\s]+)$/,
+ fnIx: 2,
+ fileLineColIx: 3,
+ style: 'webkit'
+ }
+ ];
+
+ // regexes should capture the function name (if any) as group 1
+ // and the file, line, and column as group 2.
+ function tryParseFrames(lines) {
+ var style = null;
+ var frames = lines.map(function(line) {
+ var convertedLine = first(framePatterns, function(pattern) {
+ var overallMatch = line.match(pattern.re),
+ fileLineColMatch;
+ if (!overallMatch) {
+ return null;
+ }
+
+ fileLineColMatch = overallMatch[pattern.fileLineColIx].match(
+ /^(.*):(\d+):\d+$/
+ );
+ if (!fileLineColMatch) {
+ return null;
+ }
+
+ style = style || pattern.style;
+ return {
+ raw: line,
+ file: fileLineColMatch[1],
+ line: parseInt(fileLineColMatch[2], 10),
+ func: overallMatch[pattern.fnIx]
+ };
+ });
+
+ return convertedLine || { raw: line };
+ });
+
+ return {
+ style: style,
+ frames: frames
+ };
+ }
+
+ function first(items, fn) {
+ var i, result;
+
+ for (i = 0; i < items.length; i++) {
+ result = fn(items[i]);
+
+ if (result) {
+ return result;
+ }
+ }
+ }
+
+ function extractMessage(message, stackLines) {
+ var len = messagePrefixLength(message, stackLines);
+
+ if (len > 0) {
+ return {
+ message: stackLines.slice(0, len).join('\n'),
+ remainder: stackLines.slice(len)
+ };
+ }
+ }
+
+ function messagePrefixLength(message, stackLines) {
+ if (!stackLines[0].match(/^\w*Error/)) {
+ return 0;
+ }
+
+ var messageLines = message.split('\n');
+ var i;
+
+ for (i = 1; i < messageLines.length; i++) {
+ if (messageLines[i] !== stackLines[i]) {
+ return 0;
+ }
+ }
+
+ return messageLines.length;
+ }
+
+ return StackTrace;
+};
+
+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.asyncExpectationFactory = attrs.asyncExpectationFactory;
+ this.expectationResultFactory = attrs.expectationResultFactory;
+ this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+ this.beforeFns = [];
+ this.afterFns = [];
+ this.beforeAllFns = [];
+ this.afterAllFns = [];
+
+ this.timer = attrs.timer || new j$.Timer();
+
+ this.children = [];
+
+ /**
+ * @typedef SuiteResult
+ * @property {Int} id - The unique id of this suite.
+ * @property {String} description - The description text passed to the {@link describe} that made this
suite.
+ * @property {String} fullName - The full description including all ancestors of this suite.
+ * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link
afterAll} for this suite.
+ * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on
this suite.
+ * @property {String} status - Once the suite has completed, this string represents the pass/fail status
of this suite.
+ * @property {number} duration - The time in ms for Suite execution, including any before/afterAll,
before/afterEach.
+ * @property {Object} properties - User-supplied properties, if any, that were set using {@link
Env#setSuiteProperty}
+ */
+ this.result = {
+ id: this.id,
+ description: this.description,
+ fullName: this.getFullName(),
+ failedExpectations: [],
+ deprecationWarnings: [],
+ duration: null,
+ properties: null
+ };
+ }
+
+ Suite.prototype.setSuiteProperty = function(key, value) {
+ this.result.properties = this.result.properties || {};
+ this.result.properties[key] = value;
+ };
+
+ Suite.prototype.expect = function(actual) {
+ return this.expectationFactory(actual, this);
+ };
+
+ Suite.prototype.expectAsync = function(actual) {
+ return this.asyncExpectationFactory(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.pend = function() {
+ 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.unshift(fn);
+ };
+
+ Suite.prototype.startTimer = function() {
+ this.timer.start();
+ };
+
+ Suite.prototype.endTimer = function() {
+ this.result.duration = this.timer.elapsed();
+ };
+
+ function removeFns(queueableFns) {
+ for (var i = 0; i < queueableFns.length; i++) {
+ queueableFns[i].fn = null;
+ }
+ }
+
+ Suite.prototype.cleanupBeforeAfter = function() {
+ removeFns(this.beforeAllFns);
+ removeFns(this.afterAllFns);
+ removeFns(this.beforeFns);
+ removeFns(this.afterFns);
+ };
+
+ Suite.prototype.addChild = function(child) {
+ this.children.push(child);
+ };
+
+ Suite.prototype.status = function() {
+ if (this.markedPending) {
+ return 'pending';
+ }
+
+ if (this.result.failedExpectations.length > 0) {
+ return 'failed';
+ } else {
+ return 'passed';
+ }
+ };
+
+ 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
+ ? this.parentSuite.clonedSharedUserContext()
+ : new j$.UserContext();
+ }
+
+ return this.sharedContext;
+ };
- spyOn: function(obj, methodName) {
- return env.spyOn(obj, methodName);
- },
+ Suite.prototype.clonedSharedUserContext = function() {
+ return j$.UserContext.fromExisting(this.sharedUserContext());
+ };
- jsApiReporter: new jasmine.JsApiReporter({
- timer: new jasmine.Timer()
- }),
+ Suite.prototype.onException = function() {
+ if (arguments[0] instanceof j$.errors.ExpectationFailed) {
+ return;
+ }
- jasmine: jasmine
+ var data = {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: arguments[0]
+ };
+ var failedExpectation = this.expectationResultFactory(data);
+
+ if (!this.parentSuite) {
+ failedExpectation.globalErrorType = 'afterAll';
+ }
+
+ this.result.failedExpectations.push(failedExpectation);
};
- jasmine.addCustomEqualityTester = function(tester) {
- env.addCustomEqualityTester(tester);
+ Suite.prototype.addExpectationResult = function() {
+ if (isFailure(arguments)) {
+ var data = arguments[1];
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ if (this.throwOnExpectationFailure) {
+ throw new j$.errors.ExpectationFailed();
+ }
+ }
};
- jasmine.addMatchers = function(matchers) {
- return env.addMatchers(matchers);
+ Suite.prototype.addDeprecationWarning = function(deprecation) {
+ if (typeof deprecation === 'string') {
+ deprecation = { message: deprecation };
+ }
+ this.result.deprecationWarnings.push(
+ this.expectationResultFactory(deprecation)
+ );
};
- jasmine.clock = function() {
- return env.clock;
+ function isFailure(args) {
+ return !args[0];
+ }
+
+ return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+ /* globals exports */
+ 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() {},
+ failSpecWithNoExpectations = !!attrs.failSpecWithNoExpectations,
+ orderChildren =
+ attrs.orderChildren ||
+ function(node) {
+ return node.children;
+ },
+ excludeNode =
+ attrs.excludeNode ||
+ function(node) {
+ return false;
+ },
+ stats = { valid: true },
+ processed = false,
+ defaultMin = Infinity,
+ defaultMax = 1 - Infinity;
+
+ this.processTree = function() {
+ processNode(tree, true);
+ 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, parentExcluded) {
+ var executableIndex = runnableIndex(node.id);
+
+ if (executableIndex !== undefined) {
+ parentExcluded = false;
+ }
+
+ if (!node.children) {
+ var excluded = parentExcluded || excludeNode(node);
+ stats[node.id] = {
+ excluded: excluded,
+ willExecute: !excluded && !node.markedPending,
+ 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, parentExcluded);
+
+ if (!stats.valid) {
+ return;
+ }
+
+ var childStats = stats[child.id];
+
+ hasExecutableChild = hasExecutableChild || childStats.willExecute;
+ }
+
+ stats[node.id] = {
+ excluded: parentExcluded,
+ willExecute: 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) {
+ var onStart = {
+ fn: function(next) {
+ nodeStart(node, next);
+ }
+ };
+
+ queueRunnerFactory({
+ onComplete: function() {
+ var args = Array.prototype.slice.call(arguments, [0]);
+ node.cleanupBeforeAfter();
+ nodeComplete(node, node.getResult(), function() {
+ done.apply(undefined, args);
+ });
+ },
+ queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)),
+ userContext: node.sharedUserContext(),
+ onException: function() {
+ node.onException.apply(node, arguments);
+ }
+ });
+ }
+ };
+ } else {
+ return {
+ fn: function(done) {
+ node.execute(
+ done,
+ stats[node.id].excluded,
+ failSpecWithNoExpectations
+ );
+ }
+ };
+ }
+ }
+
+ 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].willExecute) {
+ return result;
+ }
+
+ return node.beforeAllFns.concat(result).concat(node.afterAllFns);
+ }
+ }
+
+ return TreeProcessor;
+};
+
+getJasmineRequireObj().UserContext = function(j$) {
+ function UserContext() {}
+
+ UserContext.fromExisting = function(oldContext) {
+ var context = new UserContext();
+
+ for (var prop in oldContext) {
+ if (oldContext.hasOwnProperty(prop)) {
+ context[prop] = oldContext[prop];
+ }
+ }
+
+ return context;
};
- return jasmineInterface;
+ return UserContext;
};
getJasmineRequireObj().version = function() {
- return '2.5.2';
-};
+ return '3.6.0';
+};
\ No newline at end of file
diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build
index dae48e07..d78d0621 100644
--- a/installed-tests/js/meson.build
+++ b/installed-tests/js/meson.build
@@ -118,6 +118,7 @@ jasmine_tests = [
'Regress',
'Signals',
'System',
+ 'Timers',
'Tweener',
'WarnLib',
]
diff --git a/installed-tests/js/minijasmine.js b/installed-tests/js/minijasmine.js
index 43d7d242..3443631e 100644
--- a/installed-tests/js/minijasmine.js
+++ b/installed-tests/js/minijasmine.js
@@ -19,27 +19,12 @@ function _filterStack(stack) {
.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
-globalThis.setTimeout = _setTimeoutInternal.bind(undefined, GLib.SOURCE_REMOVE);
-globalThis.setInterval = _setTimeoutInternal.bind(undefined, GLib.SOURCE_CONTINUE);
-globalThis.clearTimeout = globalThis.clearInterval = _clearTimeoutInternal;
-
let jasmineRequire = imports.jasmine.getJasmineRequireObj();
let jasmineCore = jasmineRequire.core(jasmineRequire);
globalThis._jasmineEnv = jasmineCore.getEnv();
-
+globalThis._jasmineEnv.configure({
+ random: false
+});
globalThis._jasmineMain = GLib.MainLoop.new(null, false);
globalThis._jasmineRetval = 0;
diff --git a/installed-tests/js/testImporter.js b/installed-tests/js/testImporter.js
index b4bad76b..b121ba5c 100644
--- a/installed-tests/js/testImporter.js
+++ b/installed-tests/js/testImporter.js
@@ -38,6 +38,14 @@ describe('GI importer', function () {
});
});
+function formatImporter(obj) {
+ if (typeof obj === 'object' && obj.toString && (obj.toString()?.startsWith('[object GjsModule') ||
obj.toString()?.startsWith('[GjsFileImporter '))) {
+ return obj.toString();
+ }
+
+ return undefined;
+}
+
describe('Importer', function () {
let oldSearchPath;
let foobar, subA, subB, subFoobar;
@@ -56,6 +64,10 @@ describe('Importer', function () {
imports.searchPath = oldSearchPath;
});
+ beforeEach(function() {
+ jasmine.addCustomObjectFormatter(formatImporter);
+ });
+
it('exists', function () {
expect(imports).toBeDefined();
});
diff --git a/installed-tests/js/testLang.js b/installed-tests/js/testLang.js
index 9f007702..f92e9078 100644
--- a/installed-tests/js/testLang.js
+++ b/installed-tests/js/testLang.js
@@ -71,11 +71,10 @@ describe('Lang module', function () {
it('calls the bound function with the supplied this-object', function () {
let callback = Lang.bind(o, o.callback);
callback();
- expect(o.callback.calls.mostRecent()).toEqual({
- object: o,
- args: [],
- returnValue: true,
- });
+ const cb = o.callback.calls.mostRecent();
+ expect(cb.object).toBe(o);
+ expect(cb.args.length).toBe(0);
+ expect(cb.returnValue).toBe(true);
});
it('throws an error when no function supplied', function () {
diff --git a/installed-tests/js/testMainloop.js b/installed-tests/js/testMainloop.js
index 3ee10a71..f0e2863d 100644
--- a/installed-tests/js/testMainloop.js
+++ b/installed-tests/js/testMainloop.js
@@ -86,14 +86,14 @@ describe('Mainloop.idle_add()', function () {
});
});
- // Add an idle before exit, then never run main loop again.
- // This is to test that we remove idle callbacks when the associated
- // JSContext is blown away. The leak check in minijasmine will
- // fail if the idle function is not garbage collected.
- it('does not leak idle callbacks', function () {
- Mainloop.idle_add(() => {
- fail('This should never have been called');
- return true;
- });
- });
+ // // Add an idle before exit, then never run main loop again.
+ // // This is to test that we remove idle callbacks when the associated
+ // // JSContext is blown away. The leak check in minijasmine will
+ // // fail if the idle function is not garbage collected.
+ // it('does not leak idle callbacks', function () {
+ // Mainloop.idle_add(() => {
+ // fail('This should never have been called');
+ // return true;
+ // });
+ // });
});
diff --git a/installed-tests/js/testTimers.js b/installed-tests/js/testTimers.js
new file mode 100644
index 00000000..487b75e4
--- /dev/null
+++ b/installed-tests/js/testTimers.js
@@ -0,0 +1,317 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+const { GLib } = imports.gi;
+
+function deferred() {
+ let resolve_;
+ let reject_;
+ function resolve() {
+
+ resolve_();
+ }
+ function reject() {
+ reject_();
+ }
+ const promise = new Promise((res, rej) => {
+ resolve_ = res;
+ reject_ = rej;
+ });
+ return {
+ promise,
+ resolve,
+ reject
+ };
+}
+
+async function waitForMs(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+describe('Timers', function () {
+ it('times out successfully', async function timeoutSuccess() {
+
+ const { promise, resolve } = deferred();
+ let count = 0;
+ setTimeout(() => {
+ count++;
+ resolve();
+ }, 500);
+ await promise;
+
+ // count should increment
+ expect(count).toBe(1);
+
+
+ return 5;
+ });
+
+ it('has correct timeout args', async function timeoutArgs() {
+
+ const { promise, resolve } = deferred();
+ const arg = 1;
+
+ setTimeout(
+ (a, b, c) => {
+ expect(a).toBe(arg);
+ expect(b).toBe(arg.toString());
+ expect(c).toEqual([arg]);
+ resolve();
+ },
+ 10,
+ arg,
+ arg.toString(),
+ [arg]
+ );
+ await promise;
+ });
+
+ it('cancels successfully', async function timeoutCancelSuccess() {
+
+ let count = 0;
+ const id = setTimeout(() => {
+ count++;
+ }, 1);
+ // Cancelled, count should not increment
+ clearTimeout(id);
+ await waitForMs(600);
+ expect(count).toBe(0);
+ });
+
+ it('cancels multiple correctly', async function timeoutCancelMultiple() {
+ function uncalled() {
+ throw new Error("This function should not be called.");
+ }
+
+ // Set timers and cancel them in the same order.
+ const t1 = setTimeout(uncalled, 10);
+ const t2 = setTimeout(uncalled, 10);
+ const t3 = setTimeout(uncalled, 10);
+ clearTimeout(t1);
+ clearTimeout(t2);
+ clearTimeout(t3);
+
+ // Set timers and cancel them in reverse order.
+ const t4 = setTimeout(uncalled, 20);
+ const t5 = setTimeout(uncalled, 20);
+ const t6 = setTimeout(uncalled, 20);
+ clearTimeout(t6);
+ clearTimeout(t5);
+ clearTimeout(t4);
+
+ // Sleep until we're certain that the cancelled timers aren't gonna fire.
+ await waitForMs(50);
+ });
+
+ it('cancels invalid silent fail', async function timeoutCancelInvalidSilentFail() {
+ // Expect no panic
+ const { promise, resolve } = deferred();
+ let count = 0;
+ const id = setTimeout(() => {
+ count++;
+ // Should have no effect
+ clearTimeout(id);
+ resolve();
+ }, 500);
+ await promise;
+ expect(count).toBe(1);
+
+ // Should silently fail (no panic)
+ clearTimeout(2147483647);
+ });
+
+ it('interval success', async function intervalSuccess() {
+ const { promise, resolve } = deferred();
+ let count = 0;
+ const id = setInterval(() => {
+ count++;
+ clearInterval(id);
+ resolve();
+ }, 100);
+ await promise;
+ // Clear interval
+ clearInterval(id);
+ // count should increment twice
+ expect(count).toBe(1);
+ });
+
+ it('cancels interval successfully', async function intervalCancelSuccess() {
+ let count = 0;
+ const id = setInterval(() => {
+ count++;
+ }, 1);
+ clearInterval(id);
+ await waitForMs(500);
+ expect(count).toBe(0);
+ });
+
+ it('ordering interval', async function intervalOrdering() {
+ const timers = [];
+ let timeouts = 0;
+ function onTimeout() {
+ ++timeouts;
+ for (let i = 1; i < timers.length; i++) {
+ clearTimeout(timers[i]);
+ }
+ }
+ for (let i = 0; i < 10; i++) {
+ timers[i] = setTimeout(onTimeout, 1);
+ }
+ await waitForMs(500);
+ expect(timeouts).toBe(1);
+ });
+
+ it('cancel invalid silent fail', async function intervalCancelInvalidSilentFail() {
+ // Should silently fail (no panic)
+ clearInterval(2147483647);
+ });
+
+ it('fire immediately', async function fireCallbackImmediatelyWhenDelayOverMaxValue() {
+ GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+ '*does not fit into*');
+
+ let count = 0;
+ setTimeout(() => {
+ count++;
+ }, 2 ** 31);
+ await waitForMs(1);
+ expect(count).toBe(1);
+ });
+
+ it('callback this', async function timeoutCallbackThis() {
+ const { promise, resolve } = deferred();
+ const obj = {
+ foo() {
+ expect(this).toBe(window);
+ resolve();
+ }
+ };
+ setTimeout(obj.foo, 1);
+ await promise;
+ });
+
+ it('bind this', async function timeoutBindThis() {
+ function noop() { }
+
+ const thisCheckPassed = [null, undefined, window, globalThis];
+
+ const thisCheckFailed = [
+ 0,
+ "",
+ true,
+ false,
+ {},
+ [],
+ "foo",
+ () => { },
+ Object.prototype
+ ];
+
+ thisCheckPassed.forEach(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (thisArg) => {
+ expect(() => {
+ setTimeout.call(thisArg, noop, 1);
+ }).not.toThrow();
+ });
+
+ thisCheckFailed.forEach(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (thisArg) => {
+ expect(() => {
+ setTimeout.call(thisArg, noop, 1);
+ }).toThrowError(TypeError);
+ }
+ );
+ });
+
+ it('clearTimeout converts to number', async function clearTimeoutShouldConvertToNumber() {
+ let called = false;
+ const obj = {
+ valueOf() {
+ called = true;
+ return 1;
+ }
+ };
+ clearTimeout(obj);
+ expect(called).toBe(true);
+ });
+
+ it('throw on bigint', function setTimeoutShouldThrowWithBigint() {
+ expect(() => {
+ setTimeout(() => { }, 1n);
+ }).toThrowError(TypeError);
+ });
+
+ it('throw on bigint', function clearTimeoutShouldThrowWithBigint() {
+ expect(() => {
+ clearTimeout(1n);
+ }).toThrowError(TypeError);
+ });
+
+ it('', function testFunctionName() {
+ expect(clearTimeout.name).toBe("clearTimeout");
+ expect(clearInterval.name).toBe("clearInterval");
+ });
+
+ it('length', function testFunctionParamsLength() {
+ expect(setTimeout.length).toBe(1);
+ expect(setInterval.length).toBe(1);
+ expect(clearTimeout.length).toBe(0);
+ expect(clearInterval.length).toBe(0);
+ });
+
+ it('clear and interval', function clearTimeoutAndClearIntervalNotBeEquals() {
+ expect(clearTimeout).not.toBe(clearInterval);
+ });
+
+ it('microtask ordering', async function timerBasicMicrotaskOrdering() {
+ let s = "";
+ let count = 0;
+ const { promise, resolve } = deferred();
+ setTimeout(() => {
+ Promise.resolve().then(() => {
+ count++;
+ s += "de";
+ if (count === 2) {
+ resolve();
+ }
+ });
+ });
+ setTimeout(() => {
+ count++;
+ s += "no";
+ if (count === 2) {
+ resolve();
+ }
+ });
+ await promise;
+ expect(s).toBe("deno");
+ });
+
+ it('nested microtask ordering', async function timerNestedMicrotaskOrdering() {
+ let s = "";
+ const { promise, resolve } = deferred();
+ s += "0";
+ setTimeout(() => {
+ s += "4";
+ setTimeout(() => (s += "8"));
+ Promise.resolve().then(() => {
+ setTimeout(() => {
+ s += "9";
+ resolve();
+ });
+ });
+ });
+ setTimeout(() => (s += "5"));
+ Promise.resolve().then(() => (s += "2"));
+ Promise.resolve().then(() =>
+ setTimeout(() => {
+ s += "6";
+ Promise.resolve().then(() => (s += "7"));
+ })
+ );
+ Promise.resolve().then(() => Promise.resolve().then(() => (s += "3")));
+ s += "1";
+ await promise;
+ expect(s).toBe("0123456789");
+ });
+});
\ No newline at end of file
diff --git a/js.gresource.xml b/js.gresource.xml
index bdb6b665..debf87ba 100644
--- a/js.gresource.xml
+++ b/js.gresource.xml
@@ -35,5 +35,6 @@
<file>modules/core/_format.js</file>
<file>modules/core/_gettext.js</file>
<file>modules/core/_signals.js</file>
+ <file>modules/core/_timers.js</file>
</gresource>
</gresources>
diff --git a/meson.build b/meson.build
index 38eba45c..4cdec81f 100644
--- a/meson.build
+++ b/meson.build
@@ -405,6 +405,7 @@ libgjs_sources = [
'gjs/module.cpp', 'gjs/module.h',
'gjs/native.cpp', 'gjs/native.h',
'gjs/profiler.cpp', 'gjs/profiler-private.h',
+ 'gjs/promise.cpp', 'gjs/promise.h',
'gjs/stack.cpp',
'modules/console.cpp', 'modules/console.h',
'modules/modules.cpp', 'modules/modules.h',
diff --git a/modules/core/_timers.js b/modules/core/_timers.js
new file mode 100644
index 00000000..d06b2638
--- /dev/null
+++ b/modules/core/_timers.js
@@ -0,0 +1,149 @@
+const { GLib } = imports.gi;
+
+const jobs = imports._promiseNative;
+
+// It should not be possible to remove or destroy sources from outside this library.
+const ids = new Map();
+let idIncrementor = 1;
+
+/**
+ * @param {number} id
+ * @returns {number}
+ */
+function nextId(id) {
+ idIncrementor++;
+
+ ids.set(idIncrementor, id);
+
+ return idIncrementor;
+}
+
+const TIMEOUT_MAX = 2 ** 31 - 1;
+
+function checkThis(thisArg) {
+ if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
+ throw new TypeError("Illegal invocation");
+ }
+}
+
+function checkBigInt(n) {
+ if (typeof n === "bigint") {
+ throw new TypeError("Cannot convert a BigInt value to a number");
+ }
+}
+
+function ToNumber(interval) {
+ if (typeof interval === 'number') {
+ return interval;
+ } else if (typeof interval === 'object') {
+ return +(interval.valueOf()) || +interval;
+ }
+
+ return +interval;
+}
+
+function setTimeout(callback, delay = 0, ...args) {
+ checkThis(this);
+
+ checkBigInt(delay);
+ delay = wrapDelay(delay);
+ const cb = callback.bind(globalThis, ...args);
+ const id = nextId(GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, () => {
+ if (!ids.has(id)) {
+ return GLib.SOURCE_REMOVE;
+ }
+
+ cb();
+ ids.delete(id);
+ // Drain the microtask queue.
+ jobs.run();
+
+
+ return GLib.SOURCE_REMOVE;
+ }));
+
+ return id;
+}
+
+function wrapDelay(delay) {
+ if (delay > TIMEOUT_MAX) {
+ imports._print.warn(
+ `${delay} does not fit into` +
+ " a 32-bit signed integer." +
+ "\nTimeout duration was set to 1."
+ );
+ delay = 1;
+ }
+ return Math.max(0, delay | 0);
+}
+
+function setInterval(callback, delay = 0, ...args) {
+ checkThis(this);
+ checkBigInt(delay);
+ delay = wrapDelay(delay);
+ const cb = callback.bind(globalThis, ...args);
+ const id = nextId(GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, () => {
+ if (!ids.has(id)) {
+ return GLib.SOURCE_REMOVE;
+ }
+
+ cb();
+
+ // Drain the microtask queue.
+ jobs.run();
+
+ return GLib.SOURCE_CONTINUE;
+ }));
+
+ return id;
+}
+
+function setImmediate(callback, ...args) {
+ checkThis(this);
+
+ const cb = callback.bind(globalThis, ...args);
+
+ const id = nextId(GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ if (!ids.has(id)) {
+ return GLib.SOURCE_REMOVE;
+ }
+
+ cb();
+ ids.delete(id);
+ // Drain the microtask queue.
+ jobs.run();
+
+
+ return GLib.SOURCE_REMOVE;
+ }));
+
+ return id;
+}
+
+function clearTimer(id) {
+ // checkThis(this);
+
+ const _id = ToNumber(id);
+
+ if (!ids.has(_id)) {
+ return;
+ }
+
+ const cx = GLib.MainContext.default()
+ const source_id = ids.get(_id);
+ const source = cx.find_source_by_id(source_id);
+
+ if (source_id > 0 && source) {
+ GLib.source_remove(source_id);
+ source.destroy();
+ ids.delete(_id);
+ }
+}
+
+function clearTimeout(id = 0) {
+ clearTimer(id);
+}
+
+function clearInterval(id = 0) {
+ clearTimer(id);
+}
diff --git a/modules/print.cpp b/modules/print.cpp
index cb0f1e3b..965d6f95 100644
--- a/modules/print.cpp
+++ b/modules/print.cpp
@@ -58,6 +58,36 @@ static bool gjs_log(JSContext* cx, unsigned argc, JS::Value* vp) {
return true;
}
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_warn(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
+
+ if (argc != 1) {
+ gjs_throw(cx, "Must pass a single argument to warn()");
+ return false;
+ }
+
+ /* JS::ToString might throw, in which case we will only log that the value
+ * could not be converted to string */
+ JS::AutoSaveExceptionState exc_state(cx);
+ JS::RootedString jstr(cx, JS::ToString(cx, argv[0]));
+ exc_state.restore();
+
+ if (!jstr) {
+ g_message("JS LOG: <cannot convert value to string>");
+ return true;
+ }
+
+ JS::UniqueChars s(JS_EncodeStringToUTF8(cx, jstr));
+ if (!s)
+ return false;
+
+ g_warning("%s", s.get());
+
+ argv.rval().setUndefined();
+ return true;
+}
+
GJS_JSAPI_RETURN_CONVENTION
static bool gjs_log_error(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
@@ -144,6 +174,7 @@ static bool gjs_printerr(JSContext* context, unsigned argc, JS::Value* vp) {
// clang-format off
static constexpr JSFunctionSpec funcs[] = {
JS_FN("log", gjs_log, 1, GJS_MODULE_PROP_FLAGS),
+ JS_FN("warn", gjs_warn, 1, GJS_MODULE_PROP_FLAGS),
JS_FN("logError", gjs_log_error, 2, GJS_MODULE_PROP_FLAGS),
JS_FN("print", gjs_print, 0, GJS_MODULE_PROP_FLAGS),
JS_FN("printerr", gjs_printerr, 0, GJS_MODULE_PROP_FLAGS),
diff --git a/modules/script/_bootstrap/default.js b/modules/script/_bootstrap/default.js
index e31d80cb..13ad22f7 100644
--- a/modules/script/_bootstrap/default.js
+++ b/modules/script/_bootstrap/default.js
@@ -6,8 +6,39 @@
'use strict';
const {print, printerr, log, logError} = imports._print;
+ const {setTimeout, setInterval, setImmediate, clearTimeout, clearInterval} = imports._timers;
Object.defineProperties(exports, {
+ setTimeout: {
+ configurable: false,
+ enumerable: true,
+ writable: true,
+ value: setTimeout
+ },
+ setInterval: {
+ configurable: false,
+ enumerable: true,
+ writable: true,
+ value: setInterval
+ },
+ setImmediate: {
+ configurable: false,
+ enumerable: true,
+ writable: true,
+ value: setImmediate
+ },
+ clearTimeout: {
+ configurable: false,
+ enumerable: true,
+ writable: true,
+ value: clearTimeout
+ },
+ clearInterval: {
+ configurable: false,
+ enumerable: true,
+ writable: true,
+ value: clearInterval
+ },
print: {
configurable: false,
enumerable: true,
diff --git a/nest.js b/nest.js
new file mode 100644
index 00000000..0dabdc1a
--- /dev/null
+++ b/nest.js
@@ -0,0 +1,25 @@
+const GLib = imports.gi.GLib;
+
+const loop1 = GLib.MainLoop.new(null, false);
+const loop2 = GLib.MainLoop.new(null, false);
+
+GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ log('hi!');
+
+ GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ log('hi again!');
+ });
+
+
+ log('test 2');
+
+ loop2.run();
+
+ log('test 2 (after run)');
+});
+
+log('test 1');
+
+loop1.run();
+
+log('test 1 (after run)');
diff --git a/promise.js b/promise.js
new file mode 100644
index 00000000..a89415e5
--- /dev/null
+++ b/promise.js
@@ -0,0 +1,6 @@
+const System = imports.system;
+Promise.resolve().then(() => {
+ print('Should be printed');
+ System.exit(42);
+});
+Promise.resolve().then(() => print('Should not be printed'));
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]