123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- // Copyright 2016 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview A utility for wrapping a JSTD test object so that any test
- * methods are receive a queue that is compatible with JSTD but supports the
- * JsUnit async API of returning a promise in the test method.
- *
- * To convert a JSTD object call convertToAsyncTestObj on it and run with the
- * JsUnit test runner.
- */
- goog.provide('goog.testing.JsTdAsyncWrapper');
- goog.require('goog.Promise');
- /**
- * @param {Function|string} callback
- * @param {number=} opt_delay
- * @param {...*} var_args
- * @return {number}
- * @private
- */
- goog.testing.JsTdAsyncWrapper.REAL_SET_TIMEOUT_FN_ = goog.global.setTimeout;
- /**
- * Calls a function after a specified timeout. This uses the original setTimeout
- * to be resilient to tests that override it.
- * @param {Function} fn The function to call.
- * @param {number} timeout Timeout time in ms.
- * @private
- */
- goog.testing.JsTdAsyncWrapper.REAL_SET_TIMEOUT_ = function(fn, timeout) {
- // Setting timeout into a variable is necessary to invoke the function in the
- // default global context. Inlining breaks chrome since it requires setTimeout
- // to be called with the global context, and IE8 doesn't support the call
- // method on setTimeout.
- var setTimeoutFn = goog.testing.JsTdAsyncWrapper.REAL_SET_TIMEOUT_FN_;
- setTimeoutFn(fn, timeout);
- };
- /**
- * Wraps an object's methods by passing in a Queue that is based on the JSTD
- * async API. The queue exposes a promise that resolves when the queue
- * completes. This promise can be used in JsUnit tests.
- * @param {!Object} original The original JSTD test object. The object should
- * contain methods such as testXyz or setUp.
- * @return {!Object} A object that has all test methods wrapped in a fake
- * testing queue.
- */
- goog.testing.JsTdAsyncWrapper.convertToAsyncTestObj = function(original) {
- // Wraps a call to a test function and passes an instance of a fake queue
- // into the test function.
- var queueWrapperFn = function(fn) {
- return function() {
- var queue = new goog.testing.JsTdAsyncWrapper.Queue(this);
- fn.call(this, queue);
- return queue.startExecuting();
- };
- };
- var newTestObj = {};
- for (var prop in original) {
- // If this is a test or tearDown/setUp method wrap the method with a queue
- if (prop.indexOf('test') == 0 || prop == 'setUp' || prop == 'tearDown') {
- newTestObj[prop] = queueWrapperFn(original[prop]);
- } else {
- newTestObj[prop] = original[prop];
- }
- }
- return newTestObj;
- };
- /**
- * A queue that mirrors the JSTD Async Queue api but exposes a promise that
- * resolves once the queue is complete for compatibility with JsUnit.
- * @param {!Object} testObj The test object containing all test methods. This
- * object is passed into queue callbacks as the "this" object.
- * @constructor
- * @final
- */
- goog.testing.JsTdAsyncWrapper.Queue = function(testObj) {
- /**
- * The queue steps.
- * @private {!Array<!goog.testing.JsTdAsyncWrapper.Step_>}
- */
- this.steps_ = [];
- /**
- * A delegate that is used within a defer call.
- * @private {?goog.testing.JsTdAsyncWrapper.Queue}
- */
- this.delegate_ = null;
- /**
- * thisArg that should be used by default for addCallback function calls.
- * @private {!Object}
- */
- this.testObj_ = testObj;
- };
- /**
- * @param {string|function(!goog.testing.JsTdAsyncWrapper.Pool_=)} stepName
- * The name of the current testing step, or the fn parameter if
- * no stepName is desired.
- * @param {function(!goog.testing.JsTdAsyncWrapper.Pool_=)=} opt_fn A function
- * that will be called.
- */
- goog.testing.JsTdAsyncWrapper.Queue.prototype.defer = function(
- stepName, opt_fn) {
- var fn = opt_fn;
- if (!opt_fn && typeof stepName == 'function') {
- fn = stepName;
- stepName = '(Not named)';
- }
- // If another queue.defer is called within a pool callback it should be
- // executed after the current one. Any defer that is called within a defer
- // will be passed to a delegate and the current defer waits till all delegate
- // defer are resolved.
- if (this.delegate_) {
- this.delegate_.defer(stepName, fn);
- return;
- }
- this.steps_.push(new goog.testing.JsTdAsyncWrapper.Step_(
- /** @type {string} */ (stepName),
- /** @type {function(!goog.testing.JsTdAsyncWrapper.Pool_=)} */ (fn)));
- };
- /**
- * Starts the execution.
- * @return {!goog.Promise<void>}
- */
- goog.testing.JsTdAsyncWrapper.Queue.prototype.startExecuting = function() {
- return new goog.Promise(goog.bind(function(resolve, reject) {
- this.executeNextStep_(resolve, reject);
- }, this));
- };
- /**
- * Executes the next step on the queue waiting for all pool callbacks and then
- * starts executing any delegate queues before it finishes.
- * @param {function()} callback
- * @param {function(*)} errback
- * @private
- */
- goog.testing.JsTdAsyncWrapper.Queue.prototype.executeNextStep_ = function(
- callback, errback) {
- // Note: From this point on, we can no longer use goog.Promise (which uses
- // the goog.async.run queue) because it conflicts with MockClock, and we can't
- // use the native Promise because it is not supported on IE. So we revert to
- // using callbacks and setTimeout.
- if (!this.steps_.length) {
- callback();
- return;
- }
- var step = this.steps_.shift();
- this.delegate_ = new goog.testing.JsTdAsyncWrapper.Queue(this.testObj_);
- var pool = new goog.testing.JsTdAsyncWrapper.Pool_(
- this.testObj_, goog.bind(function() {
- goog.testing.JsTdAsyncWrapper.REAL_SET_TIMEOUT_(goog.bind(function() {
- this.executeDelegate_(callback, errback);
- }, this), 0);
- }, this), goog.bind(function(reason) {
- this.handleError_(errback, reason, step.name);
- }, this));
- try {
- step.fn.call(this.testObj_, pool);
- } catch (e) {
- this.handleError_(errback, e, step.name);
- }
- pool.maybeComplete();
- };
- /**
- * Execute the delegate queue.
- * @param {function()} callback
- * @param {function(*)} errback
- * @private
- */
- goog.testing.JsTdAsyncWrapper.Queue.prototype.executeDelegate_ = function(
- callback, errback) {
- // Wait till the delegate queue completes before moving on to the
- // next step.
- if (!this.delegate_) {
- this.executeNextStep_(callback, errback);
- return;
- }
- this.delegate_.executeNextStep_(goog.bind(function() {
- this.delegate_ = null;
- goog.testing.JsTdAsyncWrapper.REAL_SET_TIMEOUT_(goog.bind(function() {
- this.executeNextStep_(callback, errback);
- }, this), 0);
- }, this), errback);
- };
- /**
- * @param {function(*)} errback
- * @param {*} reason
- * @param {string} stepName
- * @private
- */
- goog.testing.JsTdAsyncWrapper.Queue.prototype.handleError_ = function(
- errback, reason, stepName) {
- var error = reason instanceof Error ? reason : Error(reason);
- error.message = 'In step ' + stepName + ', error: ' + error.message;
- errback(reason);
- };
- /**
- * A step to be executed.
- * @param {string} name
- * @param {function(!goog.testing.JsTdAsyncWrapper.Pool_=)} fn
- * @constructor
- * @private
- */
- goog.testing.JsTdAsyncWrapper.Step_ = function(name, fn) {
- /** @final {string} */
- this.name = name;
- /** @final {function(!goog.testing.JsTdAsyncWrapper.Pool_=)} */
- this.fn = fn;
- };
- /**
- * A fake pool that mimics the JSTD AsyncTestCase's pool object.
- * @param {!Object} testObj The test object containing all test methods. This
- * object is passed into queue callbacks as the "this" object.
- * @param {function()} callback
- * @param {function(*)} errback
- * @constructor
- * @private
- * @final
- */
- goog.testing.JsTdAsyncWrapper.Pool_ = function(testObj, callback, errback) {
- /** @private {number} */
- this.outstandingCallbacks_ = 0;
- /** @private {function()} */
- this.callback_ = callback;
- /** @private {function(*)} */
- this.errback_ = errback;
- /**
- * thisArg that should be used by default for defer function calls.
- * @private {!Object}
- */
- this.testObj_ = testObj;
- /** @private {boolean} */
- this.callbackCalled_ = false;
- };
- /**
- * @return {function()}
- */
- goog.testing.JsTdAsyncWrapper.Pool_.prototype.noop = function() {
- return this.addCallback(function() {});
- };
- /**
- * @param {function(...*):*} fn The function to add to the pool.
- * @param {?number=} opt_n The number of permitted uses of the given callback;
- * defaults to one.
- * @param {?number=} opt_timeout The timeout in milliseconds.
- * This is not supported in the adapter for now. Specifying this argument
- * will result in a test failure.
- * @param {?string=} opt_description The callback description.
- * @return {function()}
- */
- goog.testing.JsTdAsyncWrapper.Pool_.prototype.addCallback = function(
- fn, opt_n, opt_timeout, opt_description) {
- // TODO(mtragut): This could be fixed if required by test cases.
- if (opt_timeout || opt_description) {
- throw Error(
- 'Setting timeout or description in a pool callback is not supported.');
- }
- var numCallbacks = opt_n || 1;
- this.outstandingCallbacks_ = this.outstandingCallbacks_ + numCallbacks;
- return goog.bind(function() {
- try {
- fn.apply(this.testObj_, arguments);
- } catch (e) {
- if (opt_description) {
- e.message = opt_description + e.message;
- }
- this.errback_(e);
- }
- this.outstandingCallbacks_ = this.outstandingCallbacks_ - 1;
- this.maybeComplete();
- }, this);
- };
- /**
- * @param {function(...*):*} fn The function to add to the pool.
- * @param {?number=} opt_n The number of permitted uses of the given callback;
- * defaults to one.
- * @param {?number=} opt_timeout The timeout in milliseconds.
- * This is not supported in the adapter for now. Specifying this argument
- * will result in a test failure.
- * @param {?string=} opt_description The callback description.
- * @return {function()}
- */
- goog.testing.JsTdAsyncWrapper.Pool_.prototype.add =
- goog.testing.JsTdAsyncWrapper.Pool_.prototype.addCallback;
- /**
- * @param {string} msg The message to print if the error callback gets called.
- * @return {function()}
- */
- goog.testing.JsTdAsyncWrapper.Pool_.prototype.addErrback = function(msg) {
- return goog.bind(function() {
- var errorMsg = msg;
- if (arguments.length) {
- errorMsg += ' - Error callback called with params: ( ';
- for (var i = 0; i < arguments.length; i++) {
- var arg = arguments[i];
- errorMsg += arg + ' ';
- if (arg instanceof Error) {
- errorMsg += '\n' + arg.stack + '\n';
- }
- }
- errorMsg += ')';
- }
- this.errback_(errorMsg);
- }, this);
- };
- /**
- * Completes the pool if there are no outstanding callbacks.
- */
- goog.testing.JsTdAsyncWrapper.Pool_.prototype.maybeComplete = function() {
- if (this.outstandingCallbacks_ == 0 && !this.callbackCalled_) {
- this.callbackCalled_ = true;
- this.callback_();
- }
- };
|