/**
* @copyright 2018 Tridium, Inc. All Rights Reserved.
*/
/*jshint browser: true *//* eslint-env browser */
define([ 'Promise',
'nmodule/js/rc/asyncUtils/asyncUtils' ], function (
Promise,
asyncUtils) {
'use strict';
/**
* API Status: **Private**
*
* Module with utility functions for running async Jasmine specs.
*
* @exports nmodule/js/rc/jasmine/promiseUtils
*/
const promiseUtils = {};
const WAIT_FOR_TRUE_TIMEOUT = 5000;
const IS_TRACKING_RETURN_VALUES = Symbol('isTrackingReturnValues');
// this doesn't seem to be a memory hog, but we can turn this off if needed.
let alwaysTrackReturnValues = true;
let promiseAPIEnforced;
function deferred() {
var doResolve,
doReject,
// eslint-disable-next-line promise/avoid-new
promise = new Promise(function (resolve, reject) {
doResolve = resolve;
doReject = reject;
});
return {
resolve: function (val) {
doResolve(val);
return promise;
},
reject: function (err) {
doReject(err);
return promise;
},
promise: promise
};
}
////////////////////////////////////////////////////////////////
// Custom matcher support
////////////////////////////////////////////////////////////////
function hasClass(cx, expected) {
var jq = cx.actual;
cx.message = function () {
let msg = 'Expected ' + (this.isNot ? 'no ' : '') + 'class "' + expected + '" ';
if (jq && jq[0]) {
msg += 'but class list was "' + jq[0].classList + '".';
} else {
msg += 'but no DOM was found.';
}
return msg;
};
return jq && jq.hasClass(expected);
}
function isTag(cx, expected) {
var actual = cx.actual,
tagName = actual && String(actual.prop('tagName')).toLowerCase();
cx.message = function () {
return 'Expected tag name to be "' + expected + '" but was "' +
tagName + '".';
};
return tagName === expected.toLowerCase();
}
/**
* Workaround because :focus selector is wonky especially in PhantomJS:
* https://github.com/ariya/phantomjs/issues/10427
*/
function isFocused(cx) {
var actual = cx.actual,
elem = actual[0];
cx.message = function () {
return 'Expected element ' + actual.prop('tagName') +
(this.isNot ? ' not' : '') + ' to have focus.';
};
return elem === elem.ownerDocument.activeElement;
}
function isTypeOneOf(cx, types) {
var actual = cx.actual,
type = actual && actual.prop('type'),
i;
cx.message = function () {
return 'Expected input type ' + (cx.isNot ? 'not ' : '') +
'to be one of (' + types.join() + '), but was "' + type + '".';
};
for (i = 0; i < types.length; i++) {
if (type === types[i]) {
return true;
}
}
}
function isEquivalentTo(cx, expected, message) {
function encodeToString(val) {
if (val && typeof val.getType === 'function') {
var type = val.getType();
if (type.isSimple()) {
return 'the ' + type.getTypeName() + ' "' + val.encodeToString() + '"';
} else if (type.isComplex()) {
return jasmine.pp(val);
} else {
return type + ' instance';
}
}
return jasmine.pp(val);
}
var actual = cx.actual;
var expectedString = encodeToString(expected);
if (actual === null || actual === undefined || typeof actual.equivalent !== 'function') {
cx.message = function () {
var msg = 'Expected ' + actual + ' to be a Baja value equivalent to ' + expectedString + '.';
return message ? msg + ' ' + message : msg;
};
return false;
} else {
cx.message = function () {
var actualString = encodeToString(actual);
return 'Expected ' + actualString + (cx.isNot ? 'not ' : '') +
' to be equivalent to ' + expectedString + '.' + (message ? ' ' + message : '');
};
return actual.equivalent(expected);
}
}
function isAnError(cx, expected) {
var actual = cx.actual;
if (!(actual instanceof Error)) {
cx.message = function () {
return 'Expected ' + actual + (cx.isNot ? 'not ' : '') +
'to be an error.';
};
return false;
}
if (typeof expected === 'string') {
cx.message = function () {
return 'Expected ' + (cx.isNot ? 'not ' : '') + 'to get error ' +
'message "' + expected + '" but was "' + actual.message + '".';
};
return actual.message === expected;
}
return true;
}
function expectTrigger(func, not, dom, event, args, callback) {
var expectedHandlerArgs = args,
actualHandlerArgs = [],
triggered = 'not triggered',
prom;
if (!dom || !event) {
throw new Error('dom and event arguments required for toTrigger()');
}
if (typeof func !== 'function') {
throw new Error('must call toTrigger with a function');
}
dom.on(event, function (e) {
triggered = 'triggered';
actualHandlerArgs.push(Array.prototype.slice.call(arguments, 1, expectedHandlerArgs.length + 1));
});
try {
prom = Promise.resolve(func());
} catch (e) {
prom = Promise.reject(e);
}
promiseUtils.executePromise(prom
.finally(function () {
var timeout;
if (not) {
timeout = expectedHandlerArgs[0];
if (typeof timeout !== 'number') {
timeout = 50;
}
waits(timeout);
runs(function () {
expect(triggered).toBe('not triggered');
});
} else {
waitsFor(function () {
return triggered === 'triggered';
}, 1000, event + ' event to be triggered');
runs(function () {
expect(actualHandlerArgs).toContain(expectedHandlerArgs);
if (callback) {
callback();
}
});
}
}));
}
function monitorState(promise) {
var prom = Promise.resolve(promise)
.then(function (result) {
prom.$state = 'resolved';
return result;
})
.catch(function (err) {
prom.$state = 'rejected';
throw err;
});
return prom;
}
function failWithRejection(err) {
if (err instanceof Error) {
err = {
message: err.message,
stack: err.stack
};
}
expect(err).toBe(undefined);
}
/**
* Custom matchers for use in Jasmine specs. Add them to your tests by using
* {@link module:nmodule/js/rc/jasmine/promiseUtils.addCustomMatchers}.
*
* (This namespace is for documentation purposes only.)
*
* @mixin
* @alias CustomMatchers
* @private
*/
var customMatchers = {
/**
* Expect that the value is an `Error`, optionally matching the error
* message.
*
* @param {Error} actual
* @param {String} [expected] expected error string
* @example
* var err = new Error('my message');
*
* expect(err).toBeAnError(); //don't care what the message is
* expect(err).toBeAnError('my message');
*/
toBeAnError: function (expected) {
return isAnError(this, expected);
},
/**
* Expect that the value is equivalent to another, using the BajaScript
* `.equivalent()` function. The value must be a `baja.Object` for the
* test to pass.
*
* @param {baja.Object} actual
* @param {*} expected
* @param {string} [message]
* @example
* var rt1 = baja.RelTime.make(12345),
* rt2 = baja.RelTime.make(12345);
* expect(rt1).toBeEquivalentTo(rt2);
*/
toBeEquivalentTo: function (expected, message) {
return isEquivalentTo(this, expected, message);
},
/**
* Expect that the promise is completed with a `rejected` state.
*
* @param {module:nmodule/js/rc/jasmine/promiseUtils~PromiseResolutionParams} [params]
* @example
* var prom = new Promise(function (resolve, reject) {
* afterAWhile(reject);
* });
* expect(prom).toBeRejected(); //jasmine will wait the default timeout
* expect(prom).toBeRejected({ within: 1000 }); //fail spec if it hasn't rejected in 1 second
*/
toBeRejected: function (params) {
var actual = monitorState(this.actual);
// noinspection JSIgnoredPromiseFromCall
jasmineWaitsForPromise(actual
.then(function () {
expect('resolved').not.toBe('resolved');
})
.catch(function () {
})
.finally(function () {
expect(actual.$state).toBe('rejected');
}), params);
return true;
},
/**
* Expect that the promise is completed with a `rejected` state and an
* expected value passed to the failure handler.
*
* @param {*} expected
* @param {module:nmodule/js/rc/jasmine/promiseUtils~PromiseResolutionParams} [params]
* @example
* var prom = new Promise(function (resolve, reject) {
* afterAWhile(function () {
* reject(new Error('sock mismatch'));
* });
* });
* // jasmine will wait the default timeout
* expect(prom).toBeRejectedWith(new Error('sock mismatch'));
*
* // fail spec if it hasn't rejected in 1 second
* expect(prom).toBeRejectedWith(new Error('sock mismatch'), { within: 1000 });
*/
toBeRejectedWith: function (expected, params) {
var actual = monitorState(this.actual);
// noinspection JSIgnoredPromiseFromCall
jasmineWaitsForPromise(actual
.finally(function () {
expect(actual.$state).toBe('rejected');
})
.catch(function (err) {
if (expected instanceof Error) {
expect(err).toEqual(jasmine.any(Error));
expect(String(err)).toBe(String(expected));
} else {
expect(err).toEqual(expected);
}
}), params);
return true;
},
/**
* Expect that the promise is completed with a `resolved` state.
*
* @param {module:nmodule/js/rc/jasmine/promiseUtils~PromiseResolutionParams} [params]
* @example
* var prom = new Promise(function (resolve, reject) {
* afterAWhile(resolve);
* });
* expect(prom).toBeResolved(); //jasmine will wait the default timeout
* expect(prom).toBeResolved({ within: 1000 }); //fail spec if it hasn't resolved in 1 second
*/
toBeResolved: function (params) {
var actual = monitorState(this.actual);
// noinspection JSIgnoredPromiseFromCall
jasmineWaitsForPromise(actual
.catch(failWithRejection)
.finally(function () {
expect(actual.$state).toBe('resolved');
}), params);
return true;
},
/**
* Expect that the promise is completed with a `resolved` state and an
* expected value passed to the success handler.
*
* If the value is a `baja.Object` then the comparison will be done with
* `toBeEquivalentTo()`, otherwise the standard Jasmine `toEqual()`.
*
* @param {*} expected
* @param {module:nmodule/js/rc/jasmine/promiseUtils~PromiseResolutionParams} [params]
* @example
* var prom = new Promise(function (resolve, reject) {
* afterAWhile(function () {
* df.resolve('socks valid');
* });
* });
* expect(prom).toBeResolvedWith('socks valid'); //jasmine will wait
* expect(prom).toBeResolvedWith('socks valid', { within: 1000 }); // fail spec if it hasn't
* // resolved within 1 second
*/
toBeResolvedWith: function (expected, params) {
var actual = monitorState(this.actual);
// noinspection JSIgnoredPromiseFromCall
jasmineWaitsForPromise(actual
.then(function (result) {
if (result && typeof result.equivalent === 'function') {
expect(result).toBeEquivalentTo(expected);
} else {
expect(result).toEqual(expected);
}
})
.catch(failWithRejection)
.finally(function () {
expect(actual.$state).toBe('resolved');
}), params);
return true;
},
/**
* Expect that the jQuery element has the given CSS class.
*
* @param {JQuery} actual
* @param {String} expected CSS class name
* @example
* var elem = $('<div class="socks shoes"/>');
* expect(elem).toHaveClass('socks');
*/
toHaveClass: function (expected) {
return hasClass(this, expected);
},
/**
* Expect that the jQuery element has the given tag name.
*
* @param {JQuery} actual
* @param {String} expected HTML tag name (case insensitive)
* @example
* var elem = $('<table/>');
* expect(elem).toBeTag('table');
*/
toBeTag: function (expected) {
return isTag(this, expected);
},
/**
* Expect that the jQuery element is an `input` tag, and has the given
* `type` attribute (note that in browsers that do not support the new
* HTML5 input types, this will fall back to `text` so that the tests
* continue to pass).
*
* @param {JQuery} actual
* @param {String} expected input `type` attribute
* @example
* var input = $('<input type="date"/>');
* expect(input).toBeInputType('date');
*/
toBeInputType: function (expected) {
var types = expected === 'text' ? [ 'text' ] : [ expected, 'text' ];
return isTag(this, 'input') && isTypeOneOf(this, types);
},
/**
* Expect that the jQuery element currently has focus.
*
* @param {JQuery} actual
* @example
* var myInput = $('#myInput');
* myInput.focus();
* expect(myInput).toHaveFocus();
*/
toHaveFocus: function () {
return isFocused(this);
},
/**
* Verifies that a function causes a certain event to be triggered on a
* DOM element.
*
* @param {JQuery} dom the DOM element on which to listen for events
* @param {String} event the name of the event to listen for
* @example
* <caption>Just check that the event was triggered.</caption>
* var dom = $('<div/>');
* function trigger() { dom.trigger('helloEvent'); }
* expect(trigger).toTrigger(dom, 'helloEvent');
*
* @example
* <caption>You can also check for any additional arguments to be passed.
* </caption>
*
* var dom = $('<div/>');
* function trigger() { dom.trigger('helloEvent', 'this too'); }
* expect(trigger).toTrigger(dom, 'helloEvent', 'this too');
*
* @example
* <caption>It also works with functions that return a promise. (But not
* with a promise passed directly, since you can't tell if it will work
* synchronously or asynchronously until called. Thanks jQuery!)</caption>
*
* var dom = $('<div/>');
* function trigger() {
* return $.Deferred().resolve().then(function () {
* dom.trigger('helloEvent');
* });
* }
* expect(trigger).toTrigger(dom, 'helloEvent');
*
* @example
* <caption>Ensure that a certain event is *not* triggered. By default
* it will wait 50 ms without a trigger before passing the test. To
* wait a different amount of time just pass it as the third argument.
* </caption>
*
* var dom = $('<div/>');
* function trigger() { dom.trigger('helloEvent', 'this too'); }
* //just wait 25ms before passing the test.
* expect(trigger).not.toTrigger(dom, 'someOtherEvent', 25);
*/
toTrigger: function (dom, event) {
var func = this.actual,
not = this.isNot,
args = Array.prototype.slice.call(arguments, 2);
expectTrigger(func, not, dom, event, args);
return !not;
},
/**
* Works exactly the same as `toTrigger`, but verifies that the event
* triggers exactly once. Useful for verifying that events bubbling up from
* child elements get appropriately caught and swallowed up by a delegate
* handler.
*
* @param {JQuery} dom
* @param {JQuery.Event} event
*/
toTriggerOne: function (dom, event) {
var func = this.actual,
not = this.isNot,
args = Array.prototype.slice.call(arguments, 2),
count = 0;
dom.on(event, function () {
count++;
});
expectTrigger(func, not, dom, event, args, function () {
expect(count).toBe(1);
});
return !not;
}
};
// tell JSDoc to consider jasmine Matchers augmented by our custom matchers.
/**
* @member
* @alias jasmine.Matchers#
* @mixes CustomMatchers
*/
/**
* @member
* @alias jasmine.Matchers#not
* @mixes CustomMatchers
*/
////////////////////////////////////////////////////////////////
// Module exports
////////////////////////////////////////////////////////////////
/**
* Adds {@link CustomMatchers|custom matchers} to the Jasmine instance
* (what is bound to `this` in a `beforeEach` function, for instance).
*
* @see CustomMatchers
* @param [spec] The current Jasmine spec; if not given,
* `jasmine.getEnv().currentSpec` will be used
* @example
* beforeEach(function () {
* promiseUtils.addCustomMatchers(this);
* });
* //or
* beforeEach(promiseUtils.addCustomMatchers);
*/
promiseUtils.addCustomMatchers = function addCustomMatchers(spec) {
(spec || jasmine.getEnv().currentSpec).addMatchers(customMatchers);
jasmine.Spy.prototype.andResolve = function (value) {
this.plan = function () { return Promise.resolve(value); };
return this;
};
jasmine.Spy.prototype.andReject = function () {
return this.andRejectWith(new Error());
};
jasmine.Spy.prototype.andRejectWith = function (err) {
var error = err instanceof Error ? err : new Error(err);
this.plan = function () { return Promise.reject(error); };
return this;
};
};
/**
* Runs a promise, using the Jasmine `runs/waitsFor` functions to ensure its
* completion. This method only cares that the promise is settled (resolved
* or rejected) - if you wish to assert that the promise resolves
* successfully, use `doPromise` instead.
*
* @param {Promise} promise
* @param {String} [timeoutMessage] optional message to present if timeout occurs
* @param {Number} [within=5000] optional timeout in milliseconds
* @returns {Promise} promise that may be resolved or rejected
*/
promiseUtils.executePromise = function executePromise(promise, timeoutMessage, within) {
return jasmineWaitsForPromise(promise, { timeoutMessage, within });
};
/**
* @typedef {object} module:nmodule/js/rc/jasmine/promiseUtils~PromiseResolutionParams
* @property {number} [within] promise must settle within this many milliseconds
* @property {string} [timeoutMessage] fail the spec with this message if the promise does not
* settle in time
*/
/**
* @param {Promise} promise
* @param {module:nmodule/js/rc/jasmine/promiseUtils~PromiseResolutionParams} [params]
* @returns {Promise}
*/
function jasmineWaitsForPromise(promise, { timeoutMessage, within } = {}) {
var done,
result,
fail = false,
df = deferred();
if (!promise || (typeof promise.then !== 'function')) {
return df.reject('must call executePromise with a promise instance');
}
runs(function () {
promise
.then(function (r) {
done = true;
result = r;
}, function (err) {
done = true;
fail = true;
result = err;
});
});
waitsFor(function () {
return done;
}, timeoutMessage, within);
runs(function () {
if (fail) {
df.reject(result);
} else {
df.resolve(result);
}
});
return df.promise;
}
/**
* Run a promise, using `setTimeout` to check for the truthiness of the
* condition function. This will not use `waitsFor/runs` and as such can be
* used in conjunction with `doPromise/executePromise`.
*
* @param {Function} func resolve the promise when this function returns a
* truthy value, or a promise that resolves to a truthy value
* @param {String} [msg] the message to reject with upon timeout
* @param {Number} [timeout] the time, in milliseconds, after which to give
* up waiting and reject
* @returns {Promise}
*/
promiseUtils.waitForTrue = function (func, msg, timeout) {
timeout = timeout || WAIT_FOR_TRUE_TIMEOUT;
return asyncUtils.waitForTrue(func, timeout)
.catch(function () {
throw new Error('timed out after ' + timeout + ' msec waiting for ' +
(msg || 'something to happen'));
});
};
/**
* Return a promise that waits a certain number of milliseconds before
* resolving. This will not use `waitsFor/runs` and as such can be
* used in conjunction with `doPromise/executePromise`.
*
* @param {Number} [interval=0]
* @returns {Promise}
*/
promiseUtils.waitInterval = function (interval) {
// eslint-disable-next-line promise/avoid-new
return new Promise(function (resolve, reject) {
setTimeout(resolve, interval || 0);
});
};
/**
* Return a promise that resolves after the given Jasmine spy has been called
* the specified number of times.
*
* @param {Function} func a Jasmine spy
* @param {Number} [times=1] the number of times to expect the function to
* have been called. Defaults to 1.
* @param {Number} [timeout=5000] the time, in milliseconds, after which to give
* up waiting and reject.
* @returns {Promise}
*/
promiseUtils.waitForCalled = function (func, times, timeout) {
times = times || 1;
timeout = timeout || 5000;
var msg = (func.identity || 'spy') + ' to be called ' + times +
(times === 1 ? ' time' : ' times');
return promiseUtils.waitForTrue(function () {
return func.callCount >= times;
}, msg, timeout);
};
/**
* This will both wait for a spy function to be called, and for the promise it returns to be
* resolved.
*
* The spy must be tracking return values already - use `.andTrackReturnValues()`. This is so the
* matcher can monitor the resolution status of the returned promises.
*
* @param func
* @param {Number} [times=1] the number of times to expect the function to
* have been called. Defaults to 1.
* @param {Number} [timeout=5000] the time, in milliseconds, after which to give
* up waiting and reject.
* @returns {Promise.<*|Array.<*>>} if `times` is omitted, this will resolve to the result
* of the first call to the function. Otherwise, this will resolve to an array (of `times` length)
* of that many calls to the function.
*/
promiseUtils.waitForResolved = function (func, times, timeout) {
if (!promiseUtils.isTrackingReturnValues(func)) {
throw new Error('spy function tracking return values required (use .andTrackReturnValues)');
}
const df = deferred();
let done;
promiseUtils.waitForTrue(() => func.calls.length >= (times || 1))
.then(() => {
const calls = func.calls;
if (times === undefined) {
return calls[0].result;
} else {
return Promise.all(calls.map((call) => call.result));
}
})
.then((r) => {
done = true;
df.resolve(r);
}, df.reject);
return promiseUtils.waitForTrue(
() => done,
func.identity + ' to be called and resolved ' + (times || 1) + ' times',
timeout
)
.then(() => df.promise);
};
/**
* Runs a promise, using the Jasmine `runs/waitsFor` functions to ensure its
* completion. This function will verify that the promise is resolved -
* failing the promise will fail the test.
*
* @param {Promise} promise
* @param {String} [timeoutMessage] optional message to present if timeout occurs
* @param {Number} [timeout=5000] optional timeout in milliseconds
* @returns {Promise} promise that is verified to have been resolved
* (if the input promise rejects, the test will fail).
* @example
* promiseUtils.doPromise(editor.read()
* .then(function (result) {
* expect(result).toBe('my expected read value');
* }, function (err) {
* //not necessary to assert anything here - failing the promise will
* //automatically fail the test.
* //if you want to verify fail behavior, use toBeRejected() or
* //toBeRejectedWith() custom matchers.
* }));
*/
promiseUtils.doPromise = function doPromise(promise, timeoutMessage, timeout) {
promise = monitorState(promise);
var prom = promiseUtils.executePromise(promise
.finally(function () {
expect(promise.$state).toBe('resolved');
})
.catch(failWithRejection), timeoutMessage, timeout);
if (promiseAPIEnforced) {
prom.then = function () {
throw new Error('cannot call .then() on result of doPromise ' +
'(double check your parentheses!)');
};
}
return prom;
};
var origExecute = jasmine.Block.prototype.execute;
/**
* Ensure that the following contract is followed when using `doPromise` and
* `executePromise`:
*
* - You may not call `.then()` on the result of `doPromise()`.
*
* This ensures that `doPromise()` works correctly with the `runs/waits` async
* API presented by Jasmine 1.3.
*/
promiseUtils.enforcePromiseAPI = function () {
promiseAPIEnforced = true;
};
var waitForReturnedPromisesCalled;
/**
* Alters the default behavior of `it()`. If a `Promise` is returned from an
* `it()` call, Jasmine will wait for that promise to resolve (up to the
* default timeout) before completing the spec.
*
* @example
* promiseUtils.waitForReturnedPromises();
*
* it('waits for a returned promise to resolve', function () {
* return promiseUtils.waitInterval(1000)
* .then(function () {
* expect('a').toBe('b'); //correctly fails, because Jasmine waited
* });
* });
*/
promiseUtils.waitForReturnedPromises = function () {
if (waitForReturnedPromisesCalled) { return; }
waitForReturnedPromisesCalled = true;
var Block = jasmine.Block,
execute = Block.prototype.execute;
Block.prototype.execute = function () {
var func = this.func;
this.func = function () {
var result = func.apply(this, arguments);
if (typeof (result && result.then) === 'function') {
promiseUtils.doPromise(result);
}
return result;
};
return execute.apply(this, arguments);
};
};
/**
* Jasmine does not store return values by default. Patch it in so that each call object, in
* addition to `object` and `args`, stores a `result` property.
* @since Niagara 4.13
*/
promiseUtils.trackSpyReturnValues = function () {
jasmine.Spy.prototype.andTrackReturnValues = function () {
// here, we need the that = this pattern because the plan must reference both this (the spy)
// and this (the object on which the spy is called)
const that = this;
if (that[IS_TRACKING_RETURN_VALUES]) {
return that;
}
that[IS_TRACKING_RETURN_VALUES] = true;
let plan = that.plan;
Object.defineProperty(that, 'plan', {
enumerable: true,
configurable: true,
get() {
return function () {
const result = plan.apply(this, arguments);
const calls = that.calls;
calls[calls.length - 1].result = result;
that.mostRecentCall.result = result;
return result;
};
},
set(p) {
plan = p;
}
});
return that;
};
const { createSpy } = jasmine;
jasmine.createSpy = function () {
const spy = createSpy.apply(this, arguments);
if (alwaysTrackReturnValues) {
spy.andTrackReturnValues();
}
return spy;
};
};
/**
* @param {function} spy
* @returns {boolean} true if this is a spy function that is tracking return values
* @since Niagara 4.13
*/
promiseUtils.isTrackingReturnValues = function (spy) {
return typeof spy === 'function' && !!spy[IS_TRACKING_RETURN_VALUES];
};
/**
* @private
* @param {boolean} track
*/
promiseUtils.$alwaysTrackReturnValues = function (track) {
alwaysTrackReturnValues = track;
};
/**
* By default, promiseUtils augments Jasmine block execution to support
* returning promises from blocks and validating manual calls to
* `doPromise`, `executePromise`, and promise matchers.
*
* Call this to restore original Jasmine block execution. There will not
* typically be a reason to call this in practice, but it is provided just in
* case.
*/
promiseUtils.noConflict = function () {
jasmine.Block.prototype.execute = origExecute;
waitForReturnedPromisesCalled = false;
promiseAPIEnforced = false;
};
/**
* when doing expect(method).toHaveBeenCalledWith(component), jasmine JSON
* stringifies the component to print the error. a component's JSON structure
* is so huge that this will actually lock up the browser and kill tests. i
* found myself having to do
* expect(method.mostRecentCall.args[0] === component).toBe(true).
* yuck. let's simplify the pretty printing a bit.
*/
promiseUtils.prettyPrintBajaObjects = function () {
var StringPrettyPrinter = jasmine.StringPrettyPrinter;
var emitObject = StringPrettyPrinter.prototype.emitObject;
StringPrettyPrinter.prototype.emitObject = function (obj, name) {
if (obj === null ||
obj === undefined ||
typeof obj.getType !== 'function' ||
typeof obj.equivalent !== 'function') {
//probably not a baja object
return emitObject.call(this, obj);
}
const addSpacers = () => {
for (let i = 0; i < this.$depth; ++i) { this.append(' '); }
};
this.$depth = this.$depth || 0;
addSpacers();
var type = obj.getType();
if (type.is('baja:Complex')) {
name = name || obj.getName() || '';
var props = obj.getSlots().properties().toArray();
this.append(name + '[' + type + ']');
if (props.length) {
this.append(': {');
this.$depth += 2;
props.forEach((slot) => {
this.append('\n');
this.emitObject(obj.get(slot), String(slot));
});
this.$depth -= 2;
addSpacers();
this.append('\n}');
} else {
this.append(' ');
}
} else if (type.is('baja:Simple')) {
name = name || '';
this.append(name + '[' + type + ']: {"' + obj.encodeToString() + '"} ');
}
};
};
promiseUtils.waitForReturnedPromises();
promiseUtils.trackSpyReturnValues();
promiseUtils.enforcePromiseAPI();
promiseUtils.prettyPrintBajaObjects();
return promiseUtils;
});