|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 'use strict'; |
|
6 |
|
7 const { Cc, Cu, Ci } = require('chrome'); |
|
8 const { setTimeout } = require('sdk/timers'); |
|
9 const { prefixURI, name } = require('@loader/options'); |
|
10 const addonPromiseURI = prefixURI + name + '/lib/sdk/core/promise.js'; |
|
11 const builtPromiseURI = 'resource://gre/modules/commonjs/sdk/core/promise.js'; |
|
12 let { Promise, defer, resolve, reject, all, promised } = require('sdk/core/promise'); |
|
13 |
|
14 exports['test all observers are notified'] = function(assert, done) { |
|
15 let expected = 'Taram pam param!'; |
|
16 let deferred = defer(); |
|
17 let pending = 10, i = 0; |
|
18 |
|
19 function resolved(value) { |
|
20 assert.equal(value, expected, 'value resolved as expected: #' + pending); |
|
21 if (!--pending) done(); |
|
22 } |
|
23 |
|
24 while (i++ < pending) deferred.promise.then(resolved); |
|
25 |
|
26 deferred.resolve(expected); |
|
27 }; |
|
28 |
|
29 exports['test exceptions dont stop notifications'] = function(assert, done) { |
|
30 let threw = false, boom = Error('Boom!'); |
|
31 let deferred = defer(); |
|
32 |
|
33 let promise2 = deferred.promise.then(function() { |
|
34 threw = true; |
|
35 throw boom; |
|
36 }); |
|
37 |
|
38 deferred.promise.then(function() { |
|
39 assert.ok(threw, 'observer is called even though previos one threw'); |
|
40 promise2.then(function() { |
|
41 assert.fail('should not resolve'); |
|
42 }, function(reason) { |
|
43 assert.equal(reason, boom, 'rejects to thrown error'); |
|
44 done(); |
|
45 }); |
|
46 }); |
|
47 |
|
48 deferred.resolve('go!'); |
|
49 }; |
|
50 |
|
51 exports['test subsequent resolves are ignored'] = function(assert, done) { |
|
52 let deferred = defer(); |
|
53 deferred.resolve(1); |
|
54 deferred.resolve(2); |
|
55 deferred.reject(3); |
|
56 |
|
57 deferred.promise.then(function(actual) { |
|
58 assert.equal(actual, 1, 'resolves to first value'); |
|
59 }, function() { |
|
60 assert.fail('must not reject'); |
|
61 }); |
|
62 deferred.promise.then(function(actual) { |
|
63 assert.equal(actual, 1, 'subsequent resolutions are ignored'); |
|
64 done(); |
|
65 }, function() { |
|
66 assert.fail('must not reject'); |
|
67 }); |
|
68 }; |
|
69 |
|
70 exports['test subsequent rejections are ignored'] = function(assert, done) { |
|
71 let deferred = defer(); |
|
72 deferred.reject(1); |
|
73 deferred.resolve(2); |
|
74 deferred.reject(3); |
|
75 |
|
76 deferred.promise.then(function(actual) { |
|
77 assert.fail('must not resolve'); |
|
78 }, function(actual) { |
|
79 assert.equal(actual, 1, 'must reject to first'); |
|
80 }); |
|
81 deferred.promise.then(function(actual) { |
|
82 assert.fail('must not resolve'); |
|
83 }, function(actual) { |
|
84 assert.equal(actual, 1, 'must reject to first'); |
|
85 done(); |
|
86 }); |
|
87 }; |
|
88 |
|
89 exports['test error recovery'] = function(assert, done) { |
|
90 let boom = Error('Boom!'); |
|
91 let deferred = defer(); |
|
92 |
|
93 deferred.promise.then(function() { |
|
94 assert.fail('rejected promise should not resolve'); |
|
95 }, function(reason) { |
|
96 assert.equal(reason, boom, 'rejection reason delivered'); |
|
97 return 'recovery'; |
|
98 }).then(function(value) { |
|
99 assert.equal(value, 'recovery', 'error handled by a handler'); |
|
100 done(); |
|
101 }); |
|
102 |
|
103 deferred.reject(boom); |
|
104 }; |
|
105 |
|
106 exports['test error recovery with promise'] = function(assert, done) { |
|
107 let deferred = defer(); |
|
108 |
|
109 deferred.promise.then(function() { |
|
110 assert.fail('must reject'); |
|
111 }, function(actual) { |
|
112 assert.equal(actual, 'reason', 'rejected'); |
|
113 let deferred = defer(); |
|
114 deferred.resolve('recovery'); |
|
115 return deferred.promise; |
|
116 }).then(function(actual) { |
|
117 assert.equal(actual, 'recovery', 'recorvered via promise'); |
|
118 let deferred = defer(); |
|
119 deferred.reject('error'); |
|
120 return deferred.promise; |
|
121 }).then(null, function(actual) { |
|
122 assert.equal(actual, 'error', 'rejected via promise'); |
|
123 let deferred = defer(); |
|
124 deferred.reject('end'); |
|
125 return deferred.promise; |
|
126 }).then(null, function(actual) { |
|
127 assert.equal(actual, 'end', 'rejeced via promise'); |
|
128 done(); |
|
129 }); |
|
130 |
|
131 deferred.reject('reason'); |
|
132 }; |
|
133 |
|
134 exports['test propagation'] = function(assert, done) { |
|
135 let d1 = defer(), d2 = defer(), d3 = defer(); |
|
136 |
|
137 d1.promise.then(function(actual) { |
|
138 assert.equal(actual, 'expected', 'resolves to expected value'); |
|
139 done(); |
|
140 }); |
|
141 |
|
142 d1.resolve(d2.promise); |
|
143 d2.resolve(d3.promise); |
|
144 d3.resolve('expected'); |
|
145 }; |
|
146 |
|
147 exports['test chaining'] = function(assert, done) { |
|
148 let boom = Error('boom'), brax = Error('braxXXx'); |
|
149 let deferred = defer(); |
|
150 |
|
151 deferred.promise.then().then().then(function(actual) { |
|
152 assert.equal(actual, 2, 'value propagates unchanged'); |
|
153 return actual + 2; |
|
154 }).then(null, function(reason) { |
|
155 assert.fail('should not reject'); |
|
156 }).then(function(actual) { |
|
157 assert.equal(actual, 4, 'value propagates through if not handled'); |
|
158 throw boom; |
|
159 }).then(function(actual) { |
|
160 assert.fail('exception must reject promise'); |
|
161 }).then().then(null, function(actual) { |
|
162 assert.equal(actual, boom, 'reason propagates unchanged'); |
|
163 throw brax; |
|
164 }).then().then(null, function(actual) { |
|
165 assert.equal(actual, brax, 'reason changed becase of exception'); |
|
166 return 'recovery'; |
|
167 }).then(function(actual) { |
|
168 assert.equal(actual, 'recovery', 'recovered from error'); |
|
169 done(); |
|
170 }); |
|
171 |
|
172 deferred.resolve(2); |
|
173 }; |
|
174 |
|
175 exports['test reject'] = function(assert, done) { |
|
176 let expected = Error('boom'); |
|
177 |
|
178 reject(expected).then(function() { |
|
179 assert.fail('should reject'); |
|
180 }, function(actual) { |
|
181 assert.equal(actual, expected, 'rejected with expected reason'); |
|
182 }).then(done, assert.fail); |
|
183 }; |
|
184 |
|
185 exports['test resolve to rejected'] = function(assert, done) { |
|
186 let expected = Error('boom'); |
|
187 let deferred = defer(); |
|
188 |
|
189 deferred.promise.then(function() { |
|
190 assert.fail('should reject'); |
|
191 }, function(actual) { |
|
192 assert.equal(actual, expected, 'rejected with expected failure'); |
|
193 }).then(done, assert.fail); |
|
194 |
|
195 deferred.resolve(reject(expected)); |
|
196 }; |
|
197 |
|
198 exports['test resolve'] = function(assert, done) { |
|
199 let expected = 'value'; |
|
200 resolve(expected).then(function(actual) { |
|
201 assert.equal(actual, expected, 'resolved as expected'); |
|
202 }).catch(assert.fail).then(done); |
|
203 }; |
|
204 |
|
205 exports['test promised with normal args'] = function(assert, done) { |
|
206 let sum = promised((x, y) => x + y ); |
|
207 |
|
208 sum(7, 8).then(function(actual) { |
|
209 assert.equal(actual, 7 + 8, 'resolves as expected'); |
|
210 }).catch(assert.fail).then(done); |
|
211 }; |
|
212 |
|
213 exports['test promised with promise args'] = function(assert, done) { |
|
214 let sum = promised((x, y) => x + y ); |
|
215 let deferred = defer(); |
|
216 |
|
217 sum(11, deferred.promise).then(function(actual) { |
|
218 assert.equal(actual, 11 + 24, 'resolved as expected'); |
|
219 }).catch(assert.fail).then(done); |
|
220 |
|
221 deferred.resolve(24); |
|
222 }; |
|
223 |
|
224 exports['test promised error handleing'] = function(assert, done) { |
|
225 let expected = Error('boom'); |
|
226 let f = promised(function() { |
|
227 throw expected; |
|
228 }); |
|
229 |
|
230 f().then(function() { |
|
231 assert.fail('should reject'); |
|
232 }, function(actual) { |
|
233 assert.equal(actual, expected, 'rejected as expected'); |
|
234 }).catch(assert.fail).then(done); |
|
235 }; |
|
236 |
|
237 exports['test errors in promise resolution handlers are propagated'] = function(assert, done) { |
|
238 var expected = Error('Boom'); |
|
239 var { promise, resolve } = defer(); |
|
240 |
|
241 promise.then(function() { |
|
242 throw expected; |
|
243 }).then(function() { |
|
244 return undefined; |
|
245 }).then(null, function(actual) { |
|
246 assert.equal(actual, expected, 'rejected as expected'); |
|
247 }).then(done, assert.fail); |
|
248 |
|
249 resolve({}); |
|
250 }; |
|
251 |
|
252 exports['test return promise form promised'] = function(assert, done) { |
|
253 let f = promised(function() { |
|
254 return resolve(17); |
|
255 }); |
|
256 |
|
257 f().then(function(actual) { |
|
258 assert.equal(actual, 17, 'resolves to a promise resolution'); |
|
259 }).catch(assert.fail).then(done); |
|
260 }; |
|
261 |
|
262 exports['test promised returning failure'] = function(assert, done) { |
|
263 let expected = Error('boom'); |
|
264 let f = promised(function() { |
|
265 return reject(expected); |
|
266 }); |
|
267 |
|
268 f().then(function() { |
|
269 assert.fail('must reject'); |
|
270 }, function(actual) { |
|
271 assert.equal(actual, expected, 'rejects with expected reason'); |
|
272 }).catch(assert.fail).then(done); |
|
273 }; |
|
274 |
|
275 /* |
|
276 * Changed for compliance in Bug 881047, promises are now always async |
|
277 */ |
|
278 exports['test promises are always async'] = function (assert, done) { |
|
279 let runs = 0; |
|
280 resolve(1) |
|
281 .then(val => ++runs) |
|
282 .catch(assert.fail).then(done); |
|
283 assert.equal(runs, 0, 'resolutions are called in following tick'); |
|
284 }; |
|
285 |
|
286 /* |
|
287 * Changed for compliance in Bug 881047, promised's are now non greedy |
|
288 */ |
|
289 exports['test promised are not greedy'] = function(assert, done) { |
|
290 let runs = 0; |
|
291 promised(() => ++runs)() |
|
292 .catch(assert.fail).then(done); |
|
293 assert.equal(runs, 0, 'promised does not run task right away'); |
|
294 }; |
|
295 |
|
296 exports['test arrays should not flatten'] = function(assert, done) { |
|
297 let a = defer(); |
|
298 let b = defer(); |
|
299 |
|
300 let combine = promised(function(str, arr) { |
|
301 assert.equal(str, 'Hello', 'Array was not flattened'); |
|
302 assert.deepEqual(arr, [ 'my', 'friend' ]); |
|
303 }); |
|
304 |
|
305 combine(a.promise, b.promise).catch(assert.fail).then(done); |
|
306 |
|
307 |
|
308 a.resolve('Hello'); |
|
309 b.resolve([ 'my', 'friend' ]); |
|
310 }; |
|
311 |
|
312 exports['test `all` for all promises'] = function (assert, done) { |
|
313 all([ |
|
314 resolve(5), resolve(7), resolve(10) |
|
315 ]).then(function (val) { |
|
316 assert.equal( |
|
317 val[0] === 5 && |
|
318 val[1] === 7 && |
|
319 val[2] === 10 |
|
320 , true, 'return value contains resolved promises values'); |
|
321 done(); |
|
322 }, function () { |
|
323 assert.fail('should not call reject function'); |
|
324 }); |
|
325 }; |
|
326 |
|
327 exports['test `all` aborts upon first reject'] = function (assert, done) { |
|
328 all([ |
|
329 resolve(5), reject('error'), delayedResolve() |
|
330 ]).then(function (val) { |
|
331 assert.fail('Successful resolve function should not be called'); |
|
332 }, function (reason) { |
|
333 assert.equal(reason, 'error', 'should reject the `all` promise'); |
|
334 done(); |
|
335 }); |
|
336 |
|
337 function delayedResolve () { |
|
338 let deferred = defer(); |
|
339 setTimeout(deferred.resolve, 50); |
|
340 return deferred.promise; |
|
341 } |
|
342 }; |
|
343 |
|
344 exports['test `all` with array containing non-promise'] = function (assert, done) { |
|
345 all([ |
|
346 resolve(5), resolve(10), 925 |
|
347 ]).then(function (val) { |
|
348 assert.equal(val[2], 925, 'non-promises should pass-through value'); |
|
349 done(); |
|
350 }, function () { |
|
351 assert.fail('should not be rejected'); |
|
352 }); |
|
353 }; |
|
354 |
|
355 exports['test `all` should resolve with an empty array'] = function (assert, done) { |
|
356 all([]).then(function (val) { |
|
357 assert.equal(Array.isArray(val), true, 'should return array in resolved'); |
|
358 assert.equal(val.length, 0, 'array should be empty in resolved'); |
|
359 done(); |
|
360 }, function () { |
|
361 assert.fail('should not be rejected'); |
|
362 }); |
|
363 }; |
|
364 |
|
365 exports['test `all` with multiple rejected'] = function (assert, done) { |
|
366 all([ |
|
367 reject('error1'), reject('error2'), reject('error3') |
|
368 ]).then(function (value) { |
|
369 assert.fail('should not be successful'); |
|
370 }, function (reason) { |
|
371 assert.equal(reason, 'error1', 'should reject on first promise reject'); |
|
372 done(); |
|
373 }); |
|
374 }; |
|
375 |
|
376 exports['test Promise constructor resolve'] = function (assert, done) { |
|
377 var isAsync = true; |
|
378 new Promise(function (resolve, reject) { |
|
379 resolve(5); |
|
380 }).then(x => { |
|
381 isAsync = false; |
|
382 assert.equal(x, 5, 'Promise constructor resolves correctly'); |
|
383 }).catch(assert.fail).then(done); |
|
384 assert.ok(isAsync, 'Promise constructor runs async'); |
|
385 }; |
|
386 |
|
387 exports['test Promise constructor reject'] = function (assert, done) { |
|
388 new Promise(function (resolve, reject) { |
|
389 reject(new Error('deferred4life')); |
|
390 }).then(assert.fail, (err) => { |
|
391 assert.equal(err.message, 'deferred4life', 'Promise constructor rejects correctly'); |
|
392 }).catch(assert.fail).then(done); |
|
393 }; |
|
394 |
|
395 exports['test JSM Load and API'] = function (assert, done) { |
|
396 // Use addon URL when loading from cfx/local: |
|
397 // resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js |
|
398 // Use built URL when testing on try, etc. |
|
399 // resource://gre/modules/commonjs/sdk/core/promise.js |
|
400 try { |
|
401 var { Promise } = Cu.import(addonPromiseURI, {}); |
|
402 } catch (e) { |
|
403 var { Promise } = Cu.import(builtPromiseURI, {}); |
|
404 } |
|
405 testEnvironment(Promise, assert, done, 'JSM'); |
|
406 }; |
|
407 |
|
408 exports['test mozIJSSubScriptLoader exporting'] = function (assert, done) { |
|
409 let { Services } = Cu.import('resource://gre/modules/Services.jsm', {}); |
|
410 let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); |
|
411 let Promise = new Cu.Sandbox(systemPrincipal); |
|
412 let loader = Cc['@mozilla.org/moz/jssubscript-loader;1'] |
|
413 .getService(Ci.mozIJSSubScriptLoader); |
|
414 |
|
415 // Use addon URL when loading from cfx/local: |
|
416 // resource://90111c90-c31e-4dc7-ac35-b65947434435-at-jetpack/addon-sdk/lib/sdk/core/promise.js |
|
417 // Use built URL when testing on try, etc. |
|
418 // resource://gre/modules/commonjs/sdk/core/promise.js |
|
419 try { |
|
420 loader.loadSubScript(addonPromiseURI, Promise); |
|
421 } catch (e) { |
|
422 loader.loadSubScript(builtPromiseURI, Promise); |
|
423 } |
|
424 |
|
425 testEnvironment(Promise, assert, done, 'mozIJSSubScript'); |
|
426 }; |
|
427 |
|
428 function testEnvironment ({all, resolve, defer, reject, promised}, assert, done, type) { |
|
429 all([resolve(5), resolve(10), 925]).then(val => { |
|
430 assert.equal(val[0], 5, 'promise#all works ' + type); |
|
431 assert.equal(val[1], 10, 'promise#all works ' + type); |
|
432 assert.equal(val[2], 925, 'promise#all works ' + type); |
|
433 return resolve(1000); |
|
434 }).then(value => { |
|
435 assert.equal(value, 1000, 'promise#resolve works ' + type); |
|
436 return reject('testing reject'); |
|
437 }).then(null, reason => { |
|
438 assert.equal(reason, 'testing reject', 'promise#reject works ' + type); |
|
439 let deferred = defer(); |
|
440 setTimeout(() => deferred.resolve('\\m/'), 10); |
|
441 return deferred.promise; |
|
442 }).then(value => { |
|
443 assert.equal(value, '\\m/', 'promise#defer works ' + type); |
|
444 return promised(x => x * x)(5); |
|
445 }).then(value => { |
|
446 assert.equal(value, 25, 'promise#promised works ' + type); |
|
447 }).then(done, assert.fail); |
|
448 } |
|
449 |
|
450 require("sdk/test").run(exports); |