1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/test/test-promise.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,450 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +'use strict'; 1.9 + 1.10 +const { Cc, Cu, Ci } = require('chrome'); 1.11 +const { setTimeout } = require('sdk/timers'); 1.12 +const { prefixURI, name } = require('@loader/options'); 1.13 +const addonPromiseURI = prefixURI + name + '/lib/sdk/core/promise.js'; 1.14 +const builtPromiseURI = 'resource://gre/modules/commonjs/sdk/core/promise.js'; 1.15 +let { Promise, defer, resolve, reject, all, promised } = require('sdk/core/promise'); 1.16 + 1.17 +exports['test all observers are notified'] = function(assert, done) { 1.18 + let expected = 'Taram pam param!'; 1.19 + let deferred = defer(); 1.20 + let pending = 10, i = 0; 1.21 + 1.22 + function resolved(value) { 1.23 + assert.equal(value, expected, 'value resolved as expected: #' + pending); 1.24 + if (!--pending) done(); 1.25 + } 1.26 + 1.27 + while (i++ < pending) deferred.promise.then(resolved); 1.28 + 1.29 + deferred.resolve(expected); 1.30 +}; 1.31 + 1.32 +exports['test exceptions dont stop notifications'] = function(assert, done) { 1.33 + let threw = false, boom = Error('Boom!'); 1.34 + let deferred = defer(); 1.35 + 1.36 + let promise2 = deferred.promise.then(function() { 1.37 + threw = true; 1.38 + throw boom; 1.39 + }); 1.40 + 1.41 + deferred.promise.then(function() { 1.42 + assert.ok(threw, 'observer is called even though previos one threw'); 1.43 + promise2.then(function() { 1.44 + assert.fail('should not resolve'); 1.45 + }, function(reason) { 1.46 + assert.equal(reason, boom, 'rejects to thrown error'); 1.47 + done(); 1.48 + }); 1.49 + }); 1.50 + 1.51 + deferred.resolve('go!'); 1.52 +}; 1.53 + 1.54 +exports['test subsequent resolves are ignored'] = function(assert, done) { 1.55 + let deferred = defer(); 1.56 + deferred.resolve(1); 1.57 + deferred.resolve(2); 1.58 + deferred.reject(3); 1.59 + 1.60 + deferred.promise.then(function(actual) { 1.61 + assert.equal(actual, 1, 'resolves to first value'); 1.62 + }, function() { 1.63 + assert.fail('must not reject'); 1.64 + }); 1.65 + deferred.promise.then(function(actual) { 1.66 + assert.equal(actual, 1, 'subsequent resolutions are ignored'); 1.67 + done(); 1.68 + }, function() { 1.69 + assert.fail('must not reject'); 1.70 + }); 1.71 +}; 1.72 + 1.73 +exports['test subsequent rejections are ignored'] = function(assert, done) { 1.74 + let deferred = defer(); 1.75 + deferred.reject(1); 1.76 + deferred.resolve(2); 1.77 + deferred.reject(3); 1.78 + 1.79 + deferred.promise.then(function(actual) { 1.80 + assert.fail('must not resolve'); 1.81 + }, function(actual) { 1.82 + assert.equal(actual, 1, 'must reject to first'); 1.83 + }); 1.84 + deferred.promise.then(function(actual) { 1.85 + assert.fail('must not resolve'); 1.86 + }, function(actual) { 1.87 + assert.equal(actual, 1, 'must reject to first'); 1.88 + done(); 1.89 + }); 1.90 +}; 1.91 + 1.92 +exports['test error recovery'] = function(assert, done) { 1.93 + let boom = Error('Boom!'); 1.94 + let deferred = defer(); 1.95 + 1.96 + deferred.promise.then(function() { 1.97 + assert.fail('rejected promise should not resolve'); 1.98 + }, function(reason) { 1.99 + assert.equal(reason, boom, 'rejection reason delivered'); 1.100 + return 'recovery'; 1.101 + }).then(function(value) { 1.102 + assert.equal(value, 'recovery', 'error handled by a handler'); 1.103 + done(); 1.104 + }); 1.105 + 1.106 + deferred.reject(boom); 1.107 +}; 1.108 + 1.109 +exports['test error recovery with promise'] = function(assert, done) { 1.110 + let deferred = defer(); 1.111 + 1.112 + deferred.promise.then(function() { 1.113 + assert.fail('must reject'); 1.114 + }, function(actual) { 1.115 + assert.equal(actual, 'reason', 'rejected'); 1.116 + let deferred = defer(); 1.117 + deferred.resolve('recovery'); 1.118 + return deferred.promise; 1.119 + }).then(function(actual) { 1.120 + assert.equal(actual, 'recovery', 'recorvered via promise'); 1.121 + let deferred = defer(); 1.122 + deferred.reject('error'); 1.123 + return deferred.promise; 1.124 + }).then(null, function(actual) { 1.125 + assert.equal(actual, 'error', 'rejected via promise'); 1.126 + let deferred = defer(); 1.127 + deferred.reject('end'); 1.128 + return deferred.promise; 1.129 + }).then(null, function(actual) { 1.130 + assert.equal(actual, 'end', 'rejeced via promise'); 1.131 + done(); 1.132 + }); 1.133 + 1.134 + deferred.reject('reason'); 1.135 +}; 1.136 + 1.137 +exports['test propagation'] = function(assert, done) { 1.138 + let d1 = defer(), d2 = defer(), d3 = defer(); 1.139 + 1.140 + d1.promise.then(function(actual) { 1.141 + assert.equal(actual, 'expected', 'resolves to expected value'); 1.142 + done(); 1.143 + }); 1.144 + 1.145 + d1.resolve(d2.promise); 1.146 + d2.resolve(d3.promise); 1.147 + d3.resolve('expected'); 1.148 +}; 1.149 + 1.150 +exports['test chaining'] = function(assert, done) { 1.151 + let boom = Error('boom'), brax = Error('braxXXx'); 1.152 + let deferred = defer(); 1.153 + 1.154 + deferred.promise.then().then().then(function(actual) { 1.155 + assert.equal(actual, 2, 'value propagates unchanged'); 1.156 + return actual + 2; 1.157 + }).then(null, function(reason) { 1.158 + assert.fail('should not reject'); 1.159 + }).then(function(actual) { 1.160 + assert.equal(actual, 4, 'value propagates through if not handled'); 1.161 + throw boom; 1.162 + }).then(function(actual) { 1.163 + assert.fail('exception must reject promise'); 1.164 + }).then().then(null, function(actual) { 1.165 + assert.equal(actual, boom, 'reason propagates unchanged'); 1.166 + throw brax; 1.167 + }).then().then(null, function(actual) { 1.168 + assert.equal(actual, brax, 'reason changed becase of exception'); 1.169 + return 'recovery'; 1.170 + }).then(function(actual) { 1.171 + assert.equal(actual, 'recovery', 'recovered from error'); 1.172 + done(); 1.173 + }); 1.174 + 1.175 + deferred.resolve(2); 1.176 +}; 1.177 + 1.178 +exports['test reject'] = function(assert, done) { 1.179 + let expected = Error('boom'); 1.180 + 1.181 + reject(expected).then(function() { 1.182 + assert.fail('should reject'); 1.183 + }, function(actual) { 1.184 + assert.equal(actual, expected, 'rejected with expected reason'); 1.185 + }).then(done, assert.fail); 1.186 +}; 1.187 + 1.188 +exports['test resolve to rejected'] = function(assert, done) { 1.189 + let expected = Error('boom'); 1.190 + let deferred = defer(); 1.191 + 1.192 + deferred.promise.then(function() { 1.193 + assert.fail('should reject'); 1.194 + }, function(actual) { 1.195 + assert.equal(actual, expected, 'rejected with expected failure'); 1.196 + }).then(done, assert.fail); 1.197 + 1.198 + deferred.resolve(reject(expected)); 1.199 +}; 1.200 + 1.201 +exports['test resolve'] = function(assert, done) { 1.202 + let expected = 'value'; 1.203 + resolve(expected).then(function(actual) { 1.204 + assert.equal(actual, expected, 'resolved as expected'); 1.205 + }).catch(assert.fail).then(done); 1.206 +}; 1.207 + 1.208 +exports['test promised with normal args'] = function(assert, done) { 1.209 + let sum = promised((x, y) => x + y ); 1.210 + 1.211 + sum(7, 8).then(function(actual) { 1.212 + assert.equal(actual, 7 + 8, 'resolves as expected'); 1.213 + }).catch(assert.fail).then(done); 1.214 +}; 1.215 + 1.216 +exports['test promised with promise args'] = function(assert, done) { 1.217 + let sum = promised((x, y) => x + y ); 1.218 + let deferred = defer(); 1.219 + 1.220 + sum(11, deferred.promise).then(function(actual) { 1.221 + assert.equal(actual, 11 + 24, 'resolved as expected'); 1.222 + }).catch(assert.fail).then(done); 1.223 + 1.224 + deferred.resolve(24); 1.225 +}; 1.226 + 1.227 +exports['test promised error handleing'] = function(assert, done) { 1.228 + let expected = Error('boom'); 1.229 + let f = promised(function() { 1.230 + throw expected; 1.231 + }); 1.232 + 1.233 + f().then(function() { 1.234 + assert.fail('should reject'); 1.235 + }, function(actual) { 1.236 + assert.equal(actual, expected, 'rejected as expected'); 1.237 + }).catch(assert.fail).then(done); 1.238 +}; 1.239 + 1.240 +exports['test errors in promise resolution handlers are propagated'] = function(assert, done) { 1.241 + var expected = Error('Boom'); 1.242 + var { promise, resolve } = defer(); 1.243 + 1.244 + promise.then(function() { 1.245 + throw expected; 1.246 + }).then(function() { 1.247 + return undefined; 1.248 + }).then(null, function(actual) { 1.249 + assert.equal(actual, expected, 'rejected as expected'); 1.250 + }).then(done, assert.fail); 1.251 + 1.252 + resolve({}); 1.253 +}; 1.254 + 1.255 +exports['test return promise form promised'] = function(assert, done) { 1.256 + let f = promised(function() { 1.257 + return resolve(17); 1.258 + }); 1.259 + 1.260 + f().then(function(actual) { 1.261 + assert.equal(actual, 17, 'resolves to a promise resolution'); 1.262 + }).catch(assert.fail).then(done); 1.263 +}; 1.264 + 1.265 +exports['test promised returning failure'] = function(assert, done) { 1.266 + let expected = Error('boom'); 1.267 + let f = promised(function() { 1.268 + return reject(expected); 1.269 + }); 1.270 + 1.271 + f().then(function() { 1.272 + assert.fail('must reject'); 1.273 + }, function(actual) { 1.274 + assert.equal(actual, expected, 'rejects with expected reason'); 1.275 + }).catch(assert.fail).then(done); 1.276 +}; 1.277 + 1.278 +/* 1.279 + * Changed for compliance in Bug 881047, promises are now always async 1.280 + */ 1.281 +exports['test promises are always async'] = function (assert, done) { 1.282 + let runs = 0; 1.283 + resolve(1) 1.284 + .then(val => ++runs) 1.285 + .catch(assert.fail).then(done); 1.286 + assert.equal(runs, 0, 'resolutions are called in following tick'); 1.287 +}; 1.288 + 1.289 +/* 1.290 + * Changed for compliance in Bug 881047, promised's are now non greedy 1.291 + */ 1.292 +exports['test promised are not greedy'] = function(assert, done) { 1.293 + let runs = 0; 1.294 + promised(() => ++runs)() 1.295 + .catch(assert.fail).then(done); 1.296 + assert.equal(runs, 0, 'promised does not run task right away'); 1.297 +}; 1.298 + 1.299 +exports['test arrays should not flatten'] = function(assert, done) { 1.300 + let a = defer(); 1.301 + let b = defer(); 1.302 + 1.303 + let combine = promised(function(str, arr) { 1.304 + assert.equal(str, 'Hello', 'Array was not flattened'); 1.305 + assert.deepEqual(arr, [ 'my', 'friend' ]); 1.306 + }); 1.307 + 1.308 + combine(a.promise, b.promise).catch(assert.fail).then(done); 1.309 + 1.310 + 1.311 + a.resolve('Hello'); 1.312 + b.resolve([ 'my', 'friend' ]); 1.313 +}; 1.314 + 1.315 +exports['test `all` for all promises'] = function (assert, done) { 1.316 + all([ 1.317 + resolve(5), resolve(7), resolve(10) 1.318 + ]).then(function (val) { 1.319 + assert.equal( 1.320 + val[0] === 5 && 1.321 + val[1] === 7 && 1.322 + val[2] === 10 1.323 + , true, 'return value contains resolved promises values'); 1.324 + done(); 1.325 + }, function () { 1.326 + assert.fail('should not call reject function'); 1.327 + }); 1.328 +}; 1.329 + 1.330 +exports['test `all` aborts upon first reject'] = function (assert, done) { 1.331 + all([ 1.332 + resolve(5), reject('error'), delayedResolve() 1.333 + ]).then(function (val) { 1.334 + assert.fail('Successful resolve function should not be called'); 1.335 + }, function (reason) { 1.336 + assert.equal(reason, 'error', 'should reject the `all` promise'); 1.337 + done(); 1.338 + }); 1.339 + 1.340 + function delayedResolve () { 1.341 + let deferred = defer(); 1.342 + setTimeout(deferred.resolve, 50); 1.343 + return deferred.promise; 1.344 + } 1.345 +}; 1.346 + 1.347 +exports['test `all` with array containing non-promise'] = function (assert, done) { 1.348 + all([ 1.349 + resolve(5), resolve(10), 925 1.350 + ]).then(function (val) { 1.351 + assert.equal(val[2], 925, 'non-promises should pass-through value'); 1.352 + done(); 1.353 + }, function () { 1.354 + assert.fail('should not be rejected'); 1.355 + }); 1.356 +}; 1.357 + 1.358 +exports['test `all` should resolve with an empty array'] = function (assert, done) { 1.359 + all([]).then(function (val) { 1.360 + assert.equal(Array.isArray(val), true, 'should return array in resolved'); 1.361 + assert.equal(val.length, 0, 'array should be empty in resolved'); 1.362 + done(); 1.363 + }, function () { 1.364 + assert.fail('should not be rejected'); 1.365 + }); 1.366 +}; 1.367 + 1.368 +exports['test `all` with multiple rejected'] = function (assert, done) { 1.369 + all([ 1.370 + reject('error1'), reject('error2'), reject('error3') 1.371 + ]).then(function (value) { 1.372 + assert.fail('should not be successful'); 1.373 + }, function (reason) { 1.374 + assert.equal(reason, 'error1', 'should reject on first promise reject'); 1.375 + done(); 1.376 + }); 1.377 +}; 1.378 + 1.379 +exports['test Promise constructor resolve'] = function (assert, done) { 1.380 + var isAsync = true; 1.381 + new Promise(function (resolve, reject) { 1.382 + resolve(5); 1.383 + }).then(x => { 1.384 + isAsync = false; 1.385 + assert.equal(x, 5, 'Promise constructor resolves correctly'); 1.386 + }).catch(assert.fail).then(done); 1.387 + assert.ok(isAsync, 'Promise constructor runs async'); 1.388 +}; 1.389 + 1.390 +exports['test Promise constructor reject'] = function (assert, done) { 1.391 + new Promise(function (resolve, reject) { 1.392 + reject(new Error('deferred4life')); 1.393 + }).then(assert.fail, (err) => { 1.394 + assert.equal(err.message, 'deferred4life', 'Promise constructor rejects correctly'); 1.395 + }).catch(assert.fail).then(done); 1.396 +}; 1.397 + 1.398 +exports['test JSM Load and API'] = function (assert, done) { 1.399 + // Use addon URL when loading from cfx/local: 1.400 + // resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js 1.401 + // Use built URL when testing on try, etc. 1.402 + // resource://gre/modules/commonjs/sdk/core/promise.js 1.403 + try { 1.404 + var { Promise } = Cu.import(addonPromiseURI, {}); 1.405 + } catch (e) { 1.406 + var { Promise } = Cu.import(builtPromiseURI, {}); 1.407 + } 1.408 + testEnvironment(Promise, assert, done, 'JSM'); 1.409 +}; 1.410 + 1.411 +exports['test mozIJSSubScriptLoader exporting'] = function (assert, done) { 1.412 + let { Services } = Cu.import('resource://gre/modules/Services.jsm', {}); 1.413 + let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); 1.414 + let Promise = new Cu.Sandbox(systemPrincipal); 1.415 + let loader = Cc['@mozilla.org/moz/jssubscript-loader;1'] 1.416 + .getService(Ci.mozIJSSubScriptLoader); 1.417 + 1.418 + // Use addon URL when loading from cfx/local: 1.419 + // resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js 1.420 + // Use built URL when testing on try, etc. 1.421 + // resource://gre/modules/commonjs/sdk/core/promise.js 1.422 + try { 1.423 + loader.loadSubScript(addonPromiseURI, Promise); 1.424 + } catch (e) { 1.425 + loader.loadSubScript(builtPromiseURI, Promise); 1.426 + } 1.427 + 1.428 + testEnvironment(Promise, assert, done, 'mozIJSSubScript'); 1.429 +}; 1.430 + 1.431 +function testEnvironment ({all, resolve, defer, reject, promised}, assert, done, type) { 1.432 + all([resolve(5), resolve(10), 925]).then(val => { 1.433 + assert.equal(val[0], 5, 'promise#all works ' + type); 1.434 + assert.equal(val[1], 10, 'promise#all works ' + type); 1.435 + assert.equal(val[2], 925, 'promise#all works ' + type); 1.436 + return resolve(1000); 1.437 + }).then(value => { 1.438 + assert.equal(value, 1000, 'promise#resolve works ' + type); 1.439 + return reject('testing reject'); 1.440 + }).then(null, reason => { 1.441 + assert.equal(reason, 'testing reject', 'promise#reject works ' + type); 1.442 + let deferred = defer(); 1.443 + setTimeout(() => deferred.resolve('\\m/'), 10); 1.444 + return deferred.promise; 1.445 + }).then(value => { 1.446 + assert.equal(value, '\\m/', 'promise#defer works ' + type); 1.447 + return promised(x => x * x)(5); 1.448 + }).then(value => { 1.449 + assert.equal(value, 25, 'promise#promised works ' + type); 1.450 + }).then(done, assert.fail); 1.451 +} 1.452 + 1.453 +require("sdk/test").run(exports);