michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: 'use strict'; michael@0: michael@0: const { Cc, Cu, Ci } = require('chrome'); michael@0: const { setTimeout } = require('sdk/timers'); michael@0: const { prefixURI, name } = require('@loader/options'); michael@0: const addonPromiseURI = prefixURI + name + '/lib/sdk/core/promise.js'; michael@0: const builtPromiseURI = 'resource://gre/modules/commonjs/sdk/core/promise.js'; michael@0: let { Promise, defer, resolve, reject, all, promised } = require('sdk/core/promise'); michael@0: michael@0: exports['test all observers are notified'] = function(assert, done) { michael@0: let expected = 'Taram pam param!'; michael@0: let deferred = defer(); michael@0: let pending = 10, i = 0; michael@0: michael@0: function resolved(value) { michael@0: assert.equal(value, expected, 'value resolved as expected: #' + pending); michael@0: if (!--pending) done(); michael@0: } michael@0: michael@0: while (i++ < pending) deferred.promise.then(resolved); michael@0: michael@0: deferred.resolve(expected); michael@0: }; michael@0: michael@0: exports['test exceptions dont stop notifications'] = function(assert, done) { michael@0: let threw = false, boom = Error('Boom!'); michael@0: let deferred = defer(); michael@0: michael@0: let promise2 = deferred.promise.then(function() { michael@0: threw = true; michael@0: throw boom; michael@0: }); michael@0: michael@0: deferred.promise.then(function() { michael@0: assert.ok(threw, 'observer is called even though previos one threw'); michael@0: promise2.then(function() { michael@0: assert.fail('should not resolve'); michael@0: }, function(reason) { michael@0: assert.equal(reason, boom, 'rejects to thrown error'); michael@0: done(); michael@0: }); michael@0: }); michael@0: michael@0: deferred.resolve('go!'); michael@0: }; michael@0: michael@0: exports['test subsequent resolves are ignored'] = function(assert, done) { michael@0: let deferred = defer(); michael@0: deferred.resolve(1); michael@0: deferred.resolve(2); michael@0: deferred.reject(3); michael@0: michael@0: deferred.promise.then(function(actual) { michael@0: assert.equal(actual, 1, 'resolves to first value'); michael@0: }, function() { michael@0: assert.fail('must not reject'); michael@0: }); michael@0: deferred.promise.then(function(actual) { michael@0: assert.equal(actual, 1, 'subsequent resolutions are ignored'); michael@0: done(); michael@0: }, function() { michael@0: assert.fail('must not reject'); michael@0: }); michael@0: }; michael@0: michael@0: exports['test subsequent rejections are ignored'] = function(assert, done) { michael@0: let deferred = defer(); michael@0: deferred.reject(1); michael@0: deferred.resolve(2); michael@0: deferred.reject(3); michael@0: michael@0: deferred.promise.then(function(actual) { michael@0: assert.fail('must not resolve'); michael@0: }, function(actual) { michael@0: assert.equal(actual, 1, 'must reject to first'); michael@0: }); michael@0: deferred.promise.then(function(actual) { michael@0: assert.fail('must not resolve'); michael@0: }, function(actual) { michael@0: assert.equal(actual, 1, 'must reject to first'); michael@0: done(); michael@0: }); michael@0: }; michael@0: michael@0: exports['test error recovery'] = function(assert, done) { michael@0: let boom = Error('Boom!'); michael@0: let deferred = defer(); michael@0: michael@0: deferred.promise.then(function() { michael@0: assert.fail('rejected promise should not resolve'); michael@0: }, function(reason) { michael@0: assert.equal(reason, boom, 'rejection reason delivered'); michael@0: return 'recovery'; michael@0: }).then(function(value) { michael@0: assert.equal(value, 'recovery', 'error handled by a handler'); michael@0: done(); michael@0: }); michael@0: michael@0: deferred.reject(boom); michael@0: }; michael@0: michael@0: exports['test error recovery with promise'] = function(assert, done) { michael@0: let deferred = defer(); michael@0: michael@0: deferred.promise.then(function() { michael@0: assert.fail('must reject'); michael@0: }, function(actual) { michael@0: assert.equal(actual, 'reason', 'rejected'); michael@0: let deferred = defer(); michael@0: deferred.resolve('recovery'); michael@0: return deferred.promise; michael@0: }).then(function(actual) { michael@0: assert.equal(actual, 'recovery', 'recorvered via promise'); michael@0: let deferred = defer(); michael@0: deferred.reject('error'); michael@0: return deferred.promise; michael@0: }).then(null, function(actual) { michael@0: assert.equal(actual, 'error', 'rejected via promise'); michael@0: let deferred = defer(); michael@0: deferred.reject('end'); michael@0: return deferred.promise; michael@0: }).then(null, function(actual) { michael@0: assert.equal(actual, 'end', 'rejeced via promise'); michael@0: done(); michael@0: }); michael@0: michael@0: deferred.reject('reason'); michael@0: }; michael@0: michael@0: exports['test propagation'] = function(assert, done) { michael@0: let d1 = defer(), d2 = defer(), d3 = defer(); michael@0: michael@0: d1.promise.then(function(actual) { michael@0: assert.equal(actual, 'expected', 'resolves to expected value'); michael@0: done(); michael@0: }); michael@0: michael@0: d1.resolve(d2.promise); michael@0: d2.resolve(d3.promise); michael@0: d3.resolve('expected'); michael@0: }; michael@0: michael@0: exports['test chaining'] = function(assert, done) { michael@0: let boom = Error('boom'), brax = Error('braxXXx'); michael@0: let deferred = defer(); michael@0: michael@0: deferred.promise.then().then().then(function(actual) { michael@0: assert.equal(actual, 2, 'value propagates unchanged'); michael@0: return actual + 2; michael@0: }).then(null, function(reason) { michael@0: assert.fail('should not reject'); michael@0: }).then(function(actual) { michael@0: assert.equal(actual, 4, 'value propagates through if not handled'); michael@0: throw boom; michael@0: }).then(function(actual) { michael@0: assert.fail('exception must reject promise'); michael@0: }).then().then(null, function(actual) { michael@0: assert.equal(actual, boom, 'reason propagates unchanged'); michael@0: throw brax; michael@0: }).then().then(null, function(actual) { michael@0: assert.equal(actual, brax, 'reason changed becase of exception'); michael@0: return 'recovery'; michael@0: }).then(function(actual) { michael@0: assert.equal(actual, 'recovery', 'recovered from error'); michael@0: done(); michael@0: }); michael@0: michael@0: deferred.resolve(2); michael@0: }; michael@0: michael@0: exports['test reject'] = function(assert, done) { michael@0: let expected = Error('boom'); michael@0: michael@0: reject(expected).then(function() { michael@0: assert.fail('should reject'); michael@0: }, function(actual) { michael@0: assert.equal(actual, expected, 'rejected with expected reason'); michael@0: }).then(done, assert.fail); michael@0: }; michael@0: michael@0: exports['test resolve to rejected'] = function(assert, done) { michael@0: let expected = Error('boom'); michael@0: let deferred = defer(); michael@0: michael@0: deferred.promise.then(function() { michael@0: assert.fail('should reject'); michael@0: }, function(actual) { michael@0: assert.equal(actual, expected, 'rejected with expected failure'); michael@0: }).then(done, assert.fail); michael@0: michael@0: deferred.resolve(reject(expected)); michael@0: }; michael@0: michael@0: exports['test resolve'] = function(assert, done) { michael@0: let expected = 'value'; michael@0: resolve(expected).then(function(actual) { michael@0: assert.equal(actual, expected, 'resolved as expected'); michael@0: }).catch(assert.fail).then(done); michael@0: }; michael@0: michael@0: exports['test promised with normal args'] = function(assert, done) { michael@0: let sum = promised((x, y) => x + y ); michael@0: michael@0: sum(7, 8).then(function(actual) { michael@0: assert.equal(actual, 7 + 8, 'resolves as expected'); michael@0: }).catch(assert.fail).then(done); michael@0: }; michael@0: michael@0: exports['test promised with promise args'] = function(assert, done) { michael@0: let sum = promised((x, y) => x + y ); michael@0: let deferred = defer(); michael@0: michael@0: sum(11, deferred.promise).then(function(actual) { michael@0: assert.equal(actual, 11 + 24, 'resolved as expected'); michael@0: }).catch(assert.fail).then(done); michael@0: michael@0: deferred.resolve(24); michael@0: }; michael@0: michael@0: exports['test promised error handleing'] = function(assert, done) { michael@0: let expected = Error('boom'); michael@0: let f = promised(function() { michael@0: throw expected; michael@0: }); michael@0: michael@0: f().then(function() { michael@0: assert.fail('should reject'); michael@0: }, function(actual) { michael@0: assert.equal(actual, expected, 'rejected as expected'); michael@0: }).catch(assert.fail).then(done); michael@0: }; michael@0: michael@0: exports['test errors in promise resolution handlers are propagated'] = function(assert, done) { michael@0: var expected = Error('Boom'); michael@0: var { promise, resolve } = defer(); michael@0: michael@0: promise.then(function() { michael@0: throw expected; michael@0: }).then(function() { michael@0: return undefined; michael@0: }).then(null, function(actual) { michael@0: assert.equal(actual, expected, 'rejected as expected'); michael@0: }).then(done, assert.fail); michael@0: michael@0: resolve({}); michael@0: }; michael@0: michael@0: exports['test return promise form promised'] = function(assert, done) { michael@0: let f = promised(function() { michael@0: return resolve(17); michael@0: }); michael@0: michael@0: f().then(function(actual) { michael@0: assert.equal(actual, 17, 'resolves to a promise resolution'); michael@0: }).catch(assert.fail).then(done); michael@0: }; michael@0: michael@0: exports['test promised returning failure'] = function(assert, done) { michael@0: let expected = Error('boom'); michael@0: let f = promised(function() { michael@0: return reject(expected); michael@0: }); michael@0: michael@0: f().then(function() { michael@0: assert.fail('must reject'); michael@0: }, function(actual) { michael@0: assert.equal(actual, expected, 'rejects with expected reason'); michael@0: }).catch(assert.fail).then(done); michael@0: }; michael@0: michael@0: /* michael@0: * Changed for compliance in Bug 881047, promises are now always async michael@0: */ michael@0: exports['test promises are always async'] = function (assert, done) { michael@0: let runs = 0; michael@0: resolve(1) michael@0: .then(val => ++runs) michael@0: .catch(assert.fail).then(done); michael@0: assert.equal(runs, 0, 'resolutions are called in following tick'); michael@0: }; michael@0: michael@0: /* michael@0: * Changed for compliance in Bug 881047, promised's are now non greedy michael@0: */ michael@0: exports['test promised are not greedy'] = function(assert, done) { michael@0: let runs = 0; michael@0: promised(() => ++runs)() michael@0: .catch(assert.fail).then(done); michael@0: assert.equal(runs, 0, 'promised does not run task right away'); michael@0: }; michael@0: michael@0: exports['test arrays should not flatten'] = function(assert, done) { michael@0: let a = defer(); michael@0: let b = defer(); michael@0: michael@0: let combine = promised(function(str, arr) { michael@0: assert.equal(str, 'Hello', 'Array was not flattened'); michael@0: assert.deepEqual(arr, [ 'my', 'friend' ]); michael@0: }); michael@0: michael@0: combine(a.promise, b.promise).catch(assert.fail).then(done); michael@0: michael@0: michael@0: a.resolve('Hello'); michael@0: b.resolve([ 'my', 'friend' ]); michael@0: }; michael@0: michael@0: exports['test `all` for all promises'] = function (assert, done) { michael@0: all([ michael@0: resolve(5), resolve(7), resolve(10) michael@0: ]).then(function (val) { michael@0: assert.equal( michael@0: val[0] === 5 && michael@0: val[1] === 7 && michael@0: val[2] === 10 michael@0: , true, 'return value contains resolved promises values'); michael@0: done(); michael@0: }, function () { michael@0: assert.fail('should not call reject function'); michael@0: }); michael@0: }; michael@0: michael@0: exports['test `all` aborts upon first reject'] = function (assert, done) { michael@0: all([ michael@0: resolve(5), reject('error'), delayedResolve() michael@0: ]).then(function (val) { michael@0: assert.fail('Successful resolve function should not be called'); michael@0: }, function (reason) { michael@0: assert.equal(reason, 'error', 'should reject the `all` promise'); michael@0: done(); michael@0: }); michael@0: michael@0: function delayedResolve () { michael@0: let deferred = defer(); michael@0: setTimeout(deferred.resolve, 50); michael@0: return deferred.promise; michael@0: } michael@0: }; michael@0: michael@0: exports['test `all` with array containing non-promise'] = function (assert, done) { michael@0: all([ michael@0: resolve(5), resolve(10), 925 michael@0: ]).then(function (val) { michael@0: assert.equal(val[2], 925, 'non-promises should pass-through value'); michael@0: done(); michael@0: }, function () { michael@0: assert.fail('should not be rejected'); michael@0: }); michael@0: }; michael@0: michael@0: exports['test `all` should resolve with an empty array'] = function (assert, done) { michael@0: all([]).then(function (val) { michael@0: assert.equal(Array.isArray(val), true, 'should return array in resolved'); michael@0: assert.equal(val.length, 0, 'array should be empty in resolved'); michael@0: done(); michael@0: }, function () { michael@0: assert.fail('should not be rejected'); michael@0: }); michael@0: }; michael@0: michael@0: exports['test `all` with multiple rejected'] = function (assert, done) { michael@0: all([ michael@0: reject('error1'), reject('error2'), reject('error3') michael@0: ]).then(function (value) { michael@0: assert.fail('should not be successful'); michael@0: }, function (reason) { michael@0: assert.equal(reason, 'error1', 'should reject on first promise reject'); michael@0: done(); michael@0: }); michael@0: }; michael@0: michael@0: exports['test Promise constructor resolve'] = function (assert, done) { michael@0: var isAsync = true; michael@0: new Promise(function (resolve, reject) { michael@0: resolve(5); michael@0: }).then(x => { michael@0: isAsync = false; michael@0: assert.equal(x, 5, 'Promise constructor resolves correctly'); michael@0: }).catch(assert.fail).then(done); michael@0: assert.ok(isAsync, 'Promise constructor runs async'); michael@0: }; michael@0: michael@0: exports['test Promise constructor reject'] = function (assert, done) { michael@0: new Promise(function (resolve, reject) { michael@0: reject(new Error('deferred4life')); michael@0: }).then(assert.fail, (err) => { michael@0: assert.equal(err.message, 'deferred4life', 'Promise constructor rejects correctly'); michael@0: }).catch(assert.fail).then(done); michael@0: }; michael@0: michael@0: exports['test JSM Load and API'] = function (assert, done) { michael@0: // Use addon URL when loading from cfx/local: michael@0: // resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js michael@0: // Use built URL when testing on try, etc. michael@0: // resource://gre/modules/commonjs/sdk/core/promise.js michael@0: try { michael@0: var { Promise } = Cu.import(addonPromiseURI, {}); michael@0: } catch (e) { michael@0: var { Promise } = Cu.import(builtPromiseURI, {}); michael@0: } michael@0: testEnvironment(Promise, assert, done, 'JSM'); michael@0: }; michael@0: michael@0: exports['test mozIJSSubScriptLoader exporting'] = function (assert, done) { michael@0: let { Services } = Cu.import('resource://gre/modules/Services.jsm', {}); michael@0: let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); michael@0: let Promise = new Cu.Sandbox(systemPrincipal); michael@0: let loader = Cc['@mozilla.org/moz/jssubscript-loader;1'] michael@0: .getService(Ci.mozIJSSubScriptLoader); michael@0: michael@0: // Use addon URL when loading from cfx/local: michael@0: // resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js michael@0: // Use built URL when testing on try, etc. michael@0: // resource://gre/modules/commonjs/sdk/core/promise.js michael@0: try { michael@0: loader.loadSubScript(addonPromiseURI, Promise); michael@0: } catch (e) { michael@0: loader.loadSubScript(builtPromiseURI, Promise); michael@0: } michael@0: michael@0: testEnvironment(Promise, assert, done, 'mozIJSSubScript'); michael@0: }; michael@0: michael@0: function testEnvironment ({all, resolve, defer, reject, promised}, assert, done, type) { michael@0: all([resolve(5), resolve(10), 925]).then(val => { michael@0: assert.equal(val[0], 5, 'promise#all works ' + type); michael@0: assert.equal(val[1], 10, 'promise#all works ' + type); michael@0: assert.equal(val[2], 925, 'promise#all works ' + type); michael@0: return resolve(1000); michael@0: }).then(value => { michael@0: assert.equal(value, 1000, 'promise#resolve works ' + type); michael@0: return reject('testing reject'); michael@0: }).then(null, reason => { michael@0: assert.equal(reason, 'testing reject', 'promise#reject works ' + type); michael@0: let deferred = defer(); michael@0: setTimeout(() => deferred.resolve('\\m/'), 10); michael@0: return deferred.promise; michael@0: }).then(value => { michael@0: assert.equal(value, '\\m/', 'promise#defer works ' + type); michael@0: return promised(x => x * x)(5); michael@0: }).then(value => { michael@0: assert.equal(value, 25, 'promise#promised works ' + type); michael@0: }).then(done, assert.fail); michael@0: } michael@0: michael@0: require("sdk/test").run(exports);