summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorjrw <jrw@chromium.org>2015-03-13 17:16:38 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-14 00:17:48 +0000
commit7829d55746af0b4d0889d6b65f95cff89b4ef79a (patch)
tree3fb36e39a16940f173920fc435f4aebb87c99e90 /remoting
parent76120c694d8dcdc082866a8038ffc4b0867e1903 (diff)
downloadchromium_src-7829d55746af0b4d0889d6b65f95cff89b4ef79a.zip
chromium_src-7829d55746af0b4d0889d6b65f95cff89b4ef79a.tar.gz
chromium_src-7829d55746af0b4d0889d6b65f95cff89b4ef79a.tar.bz2
Added SpyPromise class for testing code that uses promises.
Review URL: https://codereview.chromium.org/990673003 Cr-Commit-Position: refs/heads/master@{#320614}
Diffstat (limited to 'remoting')
-rw-r--r--remoting/remoting_webapp_files.gypi3
-rw-r--r--remoting/webapp/base/js/base.js4
-rw-r--r--remoting/webapp/js_proto/qunit_proto.js13
-rw-r--r--remoting/webapp/unittests/spy_promise.js294
-rw-r--r--remoting/webapp/unittests/spy_promise_unittest.js141
5 files changed, 382 insertions, 73 deletions
diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi
index 741c062..e82ac95 100644
--- a/remoting/remoting_webapp_files.gypi
+++ b/remoting/remoting_webapp_files.gypi
@@ -62,6 +62,9 @@
],
# The unit test cases for the webapp
'remoting_webapp_unittests_js_files': [
+ # TODO(jrw): Move spy_promise to base.
+ 'webapp/unittests/spy_promise.js',
+ 'webapp/unittests/spy_promise_unittest.js',
'webapp/base/js/base_unittest.js',
'webapp/base/js/base_event_hook_unittest.js',
'webapp/base/js/ipc_unittest.js',
diff --git a/remoting/webapp/base/js/base.js b/remoting/webapp/base/js/base.js
index 35012ea..51fff37 100644
--- a/remoting/webapp/base/js/base.js
+++ b/remoting/webapp/base/js/base.js
@@ -11,7 +11,9 @@
'use strict';
-var base = {};
+/** @suppress {duplicate} */
+var base = base || {};
+
base.debug = function() {};
/**
diff --git a/remoting/webapp/js_proto/qunit_proto.js b/remoting/webapp/js_proto/qunit_proto.js
index f826af8..8eb9645 100644
--- a/remoting/webapp/js_proto/qunit_proto.js
+++ b/remoting/webapp/js_proto/qunit_proto.js
@@ -57,9 +57,9 @@ QUnit.module = function(desc, dict) {};
/**
* @param {*} a
* @param {*} b
- * @param {string} desc
+ * @param {string=} opt_desc
*/
-QUnit.notEqual = function(a, b, desc) {};
+QUnit.notEqual = function(a, b, opt_desc) {};
/**
* @param {boolean} cond
@@ -79,6 +79,15 @@ QUnit.test = function(desc, f) {};
/** @param {Function} f */
QUnit.testStart = function(f) {};
+/**
+ * @interface
+ */
+QUnit.Assert = function() {};
+
+/**
+ * @return {function():void}
+ */
+QUnit.Assert.prototype.async = function() {};
var deepEqual = QUnit.deepEqual;
var equal = QUnit.equal;
diff --git a/remoting/webapp/unittests/spy_promise.js b/remoting/webapp/unittests/spy_promise.js
new file mode 100644
index 0000000..3429d27
--- /dev/null
+++ b/remoting/webapp/unittests/spy_promise.js
@@ -0,0 +1,294 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/** @suppress {duplicate} */
+var base = base || {};
+
+(function() {
+/**
+ * A wrapper around a Promise object that keeps track of all
+ * outstanding promises. This function is written to serve as a
+ * drop-in replacement for the native Promise constructor. To create
+ * a SpyPromise from an existing native Promise, use
+ * SpyPromise.resolve.
+ *
+ * Note that this is a pseudo-constructor that actually returns a
+ * regular promise with appropriate handlers attached. This detail
+ * should be transparent when SpyPromise.activate has been called.
+ *
+ * The normal way to use this class is within a call to
+ * SpyPromise.run, for example:
+ *
+ * base.SpyPromise.run(function() {
+ * myCodeThatUsesPromises();
+ * });
+ * base.SpyPromise.settleAll().then(function() {
+ * console.log('All promises have been settled!');
+ * });
+ *
+ * @constructor
+ * @extends {Promise}
+ * @param {function(function(?):?, function(*):?):?} func A function
+ * of the same type used as an argument to the native Promise
+ * constructor, in other words, a function which is called
+ * immediately, and whose arguments are a resolve function and a
+ * reject function.
+ */
+base.SpyPromise = function(func) {
+ var unsettled = new RealPromise(func);
+ var unsettledId = remember(unsettled);
+ return unsettled.then(function(/** * */value) {
+ forget(unsettledId);
+ return value;
+ }, function(error) {
+ forget(unsettledId);
+ throw error;
+ });
+};
+
+/**
+ * The real promise constructor. Needed because it is normally hidden
+ * by SpyPromise.activate or SpyPromise.run.
+ * @const
+ */
+var RealPromise = Promise;
+
+/**
+ * The real window.setTimeout method. Needed because some test
+ * frameworks like to replace this method with a fake implementation.
+ * @const
+ */
+var realSetTimeout = window.setTimeout.bind(window);
+
+/**
+ * The number of unsettled promises.
+ * @type {number}
+ */
+base.SpyPromise.unsettledCount; // initialized by reset()
+
+/**
+ * A collection of all unsettled promises.
+ * @type {!Object<number,!Promise>}
+ */
+var unsettled; // initialized by reset()
+
+/**
+ * A counter used to assign ID numbers to new SpyPromise objects.
+ * @type {number}
+ */
+var nextPromiseId; // initialized by reset()
+
+/**
+ * A promise returned by SpyPromise.settleAll.
+ * @type {Promise<null>}
+ */
+var settleAllPromise; // initialized by reset()
+
+/**
+ * Records an unsettled promise.
+ *
+ * @param {!Promise} unsettledPromise
+ * @return {number} The ID number to be passed to forget_.
+ */
+function remember(unsettledPromise) {
+ var id = nextPromiseId++;
+ if (unsettled[id] != null) {
+ throw Error('Duplicate ID: ' + id);
+ }
+ base.SpyPromise.unsettledCount++;
+ unsettled[id] = unsettledPromise;
+ return id;
+};
+
+/**
+ * Forgets a promise. Called after the promise has been settled.
+ *
+ * @param {number} id
+ * @private
+ */
+function forget(id) {
+ base.debug.assert(unsettled[id] != null);
+ base.SpyPromise.unsettledCount--;
+ delete unsettled[id];
+};
+
+/**
+ * Forgets about all unsettled promises.
+ */
+base.SpyPromise.reset = function() {
+ base.SpyPromise.unsettledCount = 0;
+ unsettled = {};
+ nextPromiseId = 0;
+ settleAllPromise = null;
+};
+
+// Initialize static variables.
+base.SpyPromise.reset();
+
+/**
+ * Tries to wait until all promises has been settled.
+ *
+ * @param {number=} opt_maxTimeMs The maximum number of milliseconds
+ * (approximately) to wait (default: 1000).
+ * @return {!Promise<null>} A real promise that is resolved when all
+ * SpyPromises have been settled, or rejected after opt_maxTimeMs
+ * milliseconds have elapsed.
+ */
+base.SpyPromise.settleAll = function(opt_maxTimeMs) {
+ if (settleAllPromise) {
+ return settleAllPromise;
+ }
+
+ var maxDelay = opt_maxTimeMs == null ? 1000 : opt_maxTimeMs;
+
+ /**
+ * @param {number} count
+ * @param {number} totalDelay
+ * @return {!Promise<null>}
+ */
+ function loop(count, totalDelay) {
+ return new RealPromise(function(resolve, reject) {
+ if (base.SpyPromise.unsettledCount == 0) {
+ settleAllPromise = null;
+ resolve(null);
+ } else if (totalDelay > maxDelay) {
+ settleAllPromise = null;
+ base.SpyPromise.reset();
+ reject(new Error('base.SpyPromise.settleAll timed out'));
+ } else {
+ // This implements quadratic backoff according to Euler's
+ // triangular number formula.
+ var delay = count;
+
+ // Must jump through crazy hoops to get a real timer in a unit test.
+ realSetTimeout(function() {
+ resolve(loop(
+ count + 1,
+ delay + totalDelay));
+ }, delay);
+ }
+ });
+ };
+
+ // An extra promise needed here to prevent the loop function from
+ // finishing before settleAllPromise is set. If that happens,
+ // settleAllPromise will never be reset to null.
+ settleAllPromise = RealPromise.resolve().then(function() {
+ return loop(0, 0);
+ });
+ return settleAllPromise;
+};
+
+/**
+ * Only for testing this class. Do not use.
+ * @returns {boolean} True if settleAll is executing.
+ */
+base.SpyPromise.isSettleAllRunning = function() {
+ return settleAllPromise != null;
+};
+
+/**
+ * Wrapper for Promise.resolve.
+ *
+ * @param {*} value
+ * @return {!base.SpyPromise}
+ */
+base.SpyPromise.resolve = function(value) {
+ return new base.SpyPromise(function(resolve, reject) {
+ resolve(value);
+ });
+};
+
+/**
+ * Wrapper for Promise.reject.
+ *
+ * @param {*} value
+ * @return {!base.SpyPromise}
+ */
+base.SpyPromise.reject = function(value) {
+ return new base.SpyPromise(function(resolve, reject) {
+ reject(value);
+ });
+};
+
+/**
+ * Wrapper for Promise.all.
+ *
+ * @param {!Array<Promise>} promises
+ * @return {!base.SpyPromise}
+ */
+base.SpyPromise.all = function(promises) {
+ return base.SpyPromise.resolve(RealPromise.all(promises));
+};
+
+/**
+ * Wrapper for Promise.race.
+ *
+ * @param {!Array<Promise>} promises
+ * @return {!base.SpyPromise}
+ */
+base.SpyPromise.race = function(promises) {
+ return base.SpyPromise.resolve(RealPromise.race(promises));
+};
+
+/**
+ * Sets Promise = base.SpyPromise. Must not be called more than once
+ * without an intervening call to restore().
+ */
+base.SpyPromise.activate = function() {
+ if (settleAllPromise) {
+ throw Error('called base.SpyPromise.activate while settleAll is running');
+ }
+ if (Promise === base.SpyPromise) {
+ throw Error('base.SpyPromise is already active');
+ }
+ Promise = base.SpyPromise;
+};
+
+/**
+ * Restores the original value of Promise.
+ */
+base.SpyPromise.restore = function() {
+ if (settleAllPromise) {
+ throw Error('called base.SpyPromise.restore while settleAll is running');
+ }
+ if (Promise === base.SpyPromise) {
+ Promise = RealPromise;
+ } else if (Promise === RealPromise) {
+ throw new Error('base.SpyPromise is not active.');
+ } else {
+ throw new Error('Something fishy is going on.');
+ }
+};
+
+/**
+ * Calls func with Promise equal to base.SpyPromise.
+ *
+ * @param {function():void} func A function which is expected to
+ * create one or more promises.
+ * @param {number=} opt_timeoutMs An optional timeout specifying how
+ * long to wait for promise chains started in func to be settled.
+ * (default: 1000 ms)
+ * @return {!Promise<null>} A promise that is resolved after every
+ * promise chain started in func is fully settled, or rejected
+ * after a opt_timeoutMs. In any case, the original value of the
+ * Promise constructor is restored before this promise is settled.
+ */
+base.SpyPromise.run = function(func, opt_timeoutMs) {
+ base.SpyPromise.activate();
+ try {
+ func();
+ } finally {
+ return base.SpyPromise.settleAll(opt_timeoutMs).then(function() {
+ base.SpyPromise.restore();
+ return null;
+ }, function(error) {
+ base.SpyPromise.restore();
+ throw error;
+ });
+ }
+};
+})(); \ No newline at end of file
diff --git a/remoting/webapp/unittests/spy_promise_unittest.js b/remoting/webapp/unittests/spy_promise_unittest.js
index f8daada..20c201d 100644
--- a/remoting/webapp/unittests/spy_promise_unittest.js
+++ b/remoting/webapp/unittests/spy_promise_unittest.js
@@ -11,7 +11,7 @@ var originalGlobalPromise = Promise;
QUnit.module('spy_promise', {
beforeEach: function() {
assertInitialState();
- SpyPromise.reset(); // Defend against broken tests.
+ base.SpyPromise.reset(); // Defend against broken tests.
},
afterEach: function() {
assertInitialState();
@@ -20,12 +20,12 @@ QUnit.module('spy_promise', {
function assertInitialState() {
QUnit.equal(Promise, originalGlobalPromise);
- QUnit.equal(
- SpyPromise['settleAllPromise_'], null,
+ QUnit.ok(
+ !base.SpyPromise.isSettleAllRunning(),
'settleAll should not be running');
QUnit.equal(
- SpyPromise.unsettledCount, 0,
- 'SpyPromise.unsettledCount should be zero ' +
+ base.SpyPromise.unsettledCount, 0,
+ 'base.SpyPromise.unsettledCount should be zero ' +
'before/after any test finishes');
}
@@ -33,141 +33,142 @@ function assertInitialState() {
* @return {!Promise}
*/
function finish() {
- return SpyPromise.settleAll().then(function() {
+ return base.SpyPromise.settleAll().then(function() {
QUnit.equal(
- SpyPromise.unsettledCount, 0,
- 'SpyPromise.unsettledCount should be zero after settleAll finishes.');
+ base.SpyPromise.unsettledCount, 0,
+ 'base.SpyPromise.unsettledCount should be zero ' +
+ 'after settleAll finishes.');
});
};
-QUnit.test('run', function(assert) {
+QUnit.test('run', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- QUnit.notEqual(SpyPromise, originalGlobalPromise);
- return SpyPromise.run(function() {
- QUnit.equal(Promise, SpyPromise);
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.notEqual(base.SpyPromise, originalGlobalPromise);
+ return base.SpyPromise.run(function() {
+ QUnit.equal(Promise, base.SpyPromise);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
var dummy1 = new Promise(function(resolve) { resolve(null); });
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
}).then(function() {
QUnit.equal(Promise, originalGlobalPromise);
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
done();
});
});
QUnit.test('activate/restore', function() {
- QUnit.notEqual(SpyPromise, originalGlobalPromise);
- SpyPromise.activate();
- QUnit.notEqual(SpyPromise, originalGlobalPromise);
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.notEqual(base.SpyPromise, originalGlobalPromise);
+ base.SpyPromise.activate();
+ QUnit.notEqual(base.SpyPromise, originalGlobalPromise);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
var dummy1 = new Promise(function(resolve) { resolve(null); });
- QUnit.equal(SpyPromise.unsettledCount, 1);
- SpyPromise.restore();
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
+ base.SpyPromise.restore();
QUnit.equal(Promise, originalGlobalPromise);
return finish();
});
-QUnit.test('new/then', function(assert) {
+QUnit.test('new/then', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- new SpyPromise(function(resolve, reject) {
+ new base.SpyPromise(function(resolve, reject) {
resolve('hello');
}).then(function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.equal(value, 'hello');
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('new/catch', function(assert) {
+QUnit.test('new/catch', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- new SpyPromise(function(resolve, reject) {
+ new base.SpyPromise(function(resolve, reject) {
reject('hello');
}).catch(function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.equal(value, 'hello');
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('new+throw/catch', function(assert) {
+QUnit.test('new+throw/catch', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- new SpyPromise(function(resolve, reject) {
+ new base.SpyPromise(function(resolve, reject) {
throw 'hello';
}).catch(function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.equal(value, 'hello');
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('resolve/then', function(assert) {
+QUnit.test('resolve/then', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.resolve('hello').then(function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ base.SpyPromise.resolve('hello').then(function(/**string*/ value) {
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.equal(value, 'hello');
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('reject/then', function(assert) {
+QUnit.test('reject/then', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.reject('hello').then(null, function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ base.SpyPromise.reject('hello').then(null, function(/**string*/ value) {
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.equal(value, 'hello');
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('reject/catch', function(assert) {
+QUnit.test('reject/catch', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.reject('hello').catch(function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ base.SpyPromise.reject('hello').catch(function(/**string*/ value) {
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.equal(value, 'hello');
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('all', function(assert) {
+QUnit.test('all', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.all([Promise.resolve(1), Promise.resolve(2)]).
+ base.SpyPromise.all([Promise.resolve(1), Promise.resolve(2)]).
then(function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.deepEqual(value, [1, 2]);
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('race', function(assert) {
+QUnit.test('race', function(/** QUnit.Assert */ assert) {
var done = assert.async();
var fast = Promise.resolve('fast');
var slow = new Promise(function() {}); // never settled
- SpyPromise.race([fast, slow]).
+ base.SpyPromise.race([fast, slow]).
then(function(/**string*/ value) {
- QUnit.equal(SpyPromise.unsettledCount, 0);
+ QUnit.equal(base.SpyPromise.unsettledCount, 0);
QUnit.equal(value, 'fast');
done();
});
- QUnit.equal(SpyPromise.unsettledCount, 1);
+ QUnit.equal(base.SpyPromise.unsettledCount, 1);
return finish();
});
-QUnit.test('resolve/then/then', function(assert) {
+QUnit.test('resolve/then/then', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.resolve('hello').then(function(/**string*/ value) {
+ base.SpyPromise.resolve('hello').then(function(/**string*/ value) {
QUnit.equal(value, 'hello');
return 'goodbye';
}).then(function(/**string*/ value) {
@@ -178,9 +179,9 @@ QUnit.test('resolve/then/then', function(assert) {
});
-QUnit.test('resolve/then+throw/catch', function(assert) {
+QUnit.test('resolve/then+throw/catch', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.resolve('hello').then(function(/**string*/ value) {
+ base.SpyPromise.resolve('hello').then(function(/**string*/ value) {
QUnit.equal(value, 'hello');
throw 'goodbye';
}).catch(function(/**string*/ value) {
@@ -190,9 +191,9 @@ QUnit.test('resolve/then+throw/catch', function(assert) {
return finish();
});
-QUnit.test('reject/catch/then', function(assert) {
+QUnit.test('reject/catch/then', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.reject('hello').catch(function(/**string*/ value) {
+ base.SpyPromise.reject('hello').catch(function(/**string*/ value) {
QUnit.equal(value, 'hello');
return 'goodbye';
}).then(function(/**string*/ value) {
@@ -203,9 +204,9 @@ QUnit.test('reject/catch/then', function(assert) {
});
-QUnit.test('reject/catch+throw/catch', function(assert) {
+QUnit.test('reject/catch+throw/catch', function(/** QUnit.Assert */ assert) {
var done = assert.async();
- SpyPromise.reject('hello').catch(function(/**string*/ value) {
+ base.SpyPromise.reject('hello').catch(function(/**string*/ value) {
QUnit.equal(value, 'hello');
throw 'goodbye';
}).catch(function(/**string*/ value) {
@@ -215,32 +216,32 @@ QUnit.test('reject/catch+throw/catch', function(assert) {
return finish();
});
-QUnit.test('settleAll timeout = 100', function(assert) {
+QUnit.test('settleAll timeout = 100', function(/** QUnit.Assert */ assert) {
var done = assert.async();
var startTime = Date.now();
- var neverResolved = new SpyPromise(function() {});
- return SpyPromise.settleAll(100).catch(function(error) {
+ var neverResolved = new base.SpyPromise(function() {});
+ return base.SpyPromise.settleAll(100).catch(function(error) {
QUnit.ok(error instanceof Error);
QUnit.ok(startTime + 200 < Date.now());
done();
});
});
-QUnit.test('settleAll timeout = 500', function(assert) {
+QUnit.test('settleAll timeout = 500', function(/** QUnit.Assert */ assert) {
var done = assert.async();
var startTime = Date.now();
- var neverResolved = new SpyPromise(function() {});
- return SpyPromise.settleAll(500).catch(function(error) {
+ var neverResolved = new base.SpyPromise(function() {});
+ return base.SpyPromise.settleAll(500).catch(function(error) {
QUnit.ok(startTime + 750 < Date.now());
done();
});
});
-QUnit.test('settleAll timeout = 1000', function(assert) {
+QUnit.test('settleAll timeout = 1000', function(/** QUnit.Assert */ assert) {
var done = assert.async();
var startTime = Date.now();
- var neverResolved = new SpyPromise(function() {});
- return SpyPromise.settleAll(1000).catch(function(error) {
+ var neverResolved = new base.SpyPromise(function() {});
+ return base.SpyPromise.settleAll(1000).catch(function(error) {
QUnit.ok(startTime + 1500 < Date.now());
done();
});