"use strict" var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") var Promise = require("../../promise/promise") o.spec("promise", function() { o.spec("constructor", function() { o("constructor throws if called without new", function(done) { try {Promise(function() {})} catch(e) {done()} }) o("constructor throws if called without executor", function(done) { try {new Promise()} catch(e) {done()} }) o("constructor has correct methods", function() { o(typeof Promise.prototype.then).equals("function") o(typeof Promise.prototype.catch).equals("function") o(typeof Promise.resolve).equals("function") o(typeof Promise.reject).equals("function") o(typeof Promise.race).equals("function") o(typeof Promise.all).equals("function") }) }) o.spec("return value", function() { o("static resolve returns promise", function() { var promise = Promise.resolve() o(promise instanceof Promise).equals(true) }) o("static reject returns promise", function() { var promise = Promise.reject() promise.catch(function() {}) o(promise instanceof Promise).equals(true) }) o("static resolve with promise input returns same promise", function() { var resolved = Promise.resolve(1) var promise = Promise.resolve(resolved) o(promise).equals(resolved) }) o("then returns promise", function(done) { var promise = Promise.resolve(1) promise.then(function(value) { o(value).equals(1) }).then(done) }) o("catch returns promise", function(done) { var promise = Promise.reject(1) promise.catch(function(value) { o(value).equals(1) }).then(done) }) }) o.spec("resolve", function() { o("resolves once", function(done) { var callCount = 0 var promise = new Promise(function(resolve, reject) { resolve(1) resolve(2) callAsync(function() {resolve(3)}) }) promise.then(function(value) { callCount++ o(value).equals(1) o(callCount).equals(1) done() }) }) o("does not reject after resolve", function(done) { var promise = new Promise(function(resolve, reject) { resolve(1) reject(2) callAsync(function() {reject(3)}) }) promise.then(function(value) { o(value).equals(1) done() }) }) o("resolves asynchronously", function(done) { var state = 0 var promise = Promise.resolve() state = 1 promise.then(function(value) { o(state).equals(2) done() }) state = 2 }) o("resolves via static method", function(done) { var promise = Promise.resolve(1) promise.then(function(value) { o(value).equals(1) done() }) }) o("resolves asynchronously via executor", function(done) { var promise = new Promise(function(resolve, reject) { callAsync(function() {resolve(1)}) }) promise.then(function(value) { o(value).equals(1) done() }) }) o("downstreams correctly", function(done) { var promise = Promise.resolve(1) var a = promise.then(function(value) {return value + 1}) var b = promise.then(function(value) {return value + 2}) a.then(function(aValue) { b.then(function(bValue) { o(aValue).equals(2) o(bValue).equals(3) done() }) }) }) o("cannot resolve to itself", function(done) { var promise = new Promise(function(resolve) { callAsync(function() {resolve(promise)}) }) promise.then(null, done) }) o("non-function onFulfilled is ignored", function(done) { var promise = Promise.resolve(1) promise.then(null, null).then(function(value) { o(value).equals(1) done() }) }) o("non-function onFulfilled with no second param is ignored", function(done) { var promise = Promise.resolve(1) promise.then(null).then(function(value) { o(value).equals(1) done() }) }) }) o.spec("reject", function() { o("rejects once", function(done) { var callCount = 0 var promise = new Promise(function(resolve, reject) { reject(1) reject(2) callAsync(function() {reject(3)}) }) promise.then(null, function(value) { callCount++ o(value).equals(1) o(callCount).equals(1) done() }) }) o("does not resolve after reject", function(done) { var promise = new Promise(function(resolve, reject) { reject(1) resolve(2) callAsync(function() {resolve(3)}) }) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("rejects asynchronously", function(done) { var state = 0 var promise = Promise.reject() state = 1 promise.then(null, function(value) { o(state).equals(2) done() }) state = 2 }) o("does not catch itself", function(done) { var callCount = 0 var promise = Promise.resolve().then(function() {throw 1}, function() {callCount++}) promise.then(null, function() { o(callCount).equals(0) done() }) }) o("rejects via static method", function(done) { var promise = Promise.reject(1) promise.then(null, function(value) { o(value).equals(1) return value }).then(function(value) { o(value).equals(1) done() }) }) o("rejects synchronously via executor", function(done) { var promise = new Promise(function(resolve, reject) { reject(1) }) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("rejects asynchronously via executor", function(done) { var promise = new Promise(function(resolve, reject) { callAsync(function() {reject(1)}) }) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("rejects via executor on error", function(done) { var promise = new Promise(function(resolve, reject) { throw 1 }) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("rejects on fulfillment error", function(done) { var promise = Promise.resolve() promise.then(function() { throw 1 }).then(null, function(value) { o(value).equals(1) done() }) }) o("rejects on rejection error", function(done) { var promise = Promise.resolve() promise.then(function() { throw 1 }).then(null, function() { throw 2 }).then(null, function(value) { o(value).equals(2) done() }) }) o("non-function onRejected is ignored", function(done) { var promise = Promise.reject(1) promise.then(function() {}, null).then(null, function(value) { o(value).equals(1) done() }) }) }) o.spec("promise absorption", function() { o("absorbs resolved promise via static resolver", function(done) { var promise = Promise.resolve(Promise.resolve(1)) promise.then(function(value) { o(value).equals(1) }).then(done) }) o("absorbs resolved promise in executor resolve", function(done) { var promise = new Promise(function(resolve, reject) { var p = Promise.resolve(1) resolve(p) }) promise.then(function(value) { o(value).equals(1) done() }) }) o("absorbs resolved promise on fulfillment", function(done) { var promise = Promise.resolve() promise.then(function() { return Promise.resolve(1) }).then(function(value) { o(value).equals(1) done() }) }) o("absorbs rejected promise via static resolver", function(done) { var promise = Promise.resolve(Promise.reject(1)) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("absorbs rejected promise in executor resolve", function(done) { var promise = new Promise(function(resolve, reject) { resolve(Promise.reject(1)) }) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("absorbs rejected promise on fulfillment", function(done) { var promise = Promise.resolve() promise.then(function() { return Promise.reject(1) }).then(null, function(value) { o(value).equals(1) done() }) }) o("absorbs pending promise that resolves via static resolver", function(done) { var pending = new Promise(function(resolve, reject) { setTimeout(function() {resolve(1)}, 10) }) var promise = Promise.resolve(pending) promise.then(function(value) { o(value).equals(1) done() }) }) o("absorbs pending promise that resolves in executor resolve", function(done) { var pending = new Promise(function(resolve, reject) { setTimeout(function() {resolve(1)}, 10) }) var promise = new Promise(function(resolve, reject) { resolve(pending) }) promise.then(function(value) { o(value).equals(1) done() }) }) o("absorbs pending promise that resolves on fulfillment", function(done) { var pending = new Promise(function(resolve, reject) { setTimeout(function() {resolve(1)}, 10) }) var promise = Promise.resolve() promise.then(function() { return pending }).then(function(value) { o(value).equals(1) done() }) }) o("absorbs pending promise that rejects via static resolver", function(done) { var pending = new Promise(function(resolve, reject) { setTimeout(function() {reject(1)}, 10) }) var promise = Promise.resolve(pending) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("absorbs pending promise that rejects in executor resolve", function(done) { var pending = new Promise(function(resolve, reject) { setTimeout(function() {reject(1)}, 10) }) var promise = new Promise(function(resolve, reject) { resolve(pending) }) promise.then(null, function(value) { o(value).equals(1) done() }) }) o("absorbs pending promise that rejects on fulfillment", function(done) { var pending = new Promise(function(resolve, reject) { setTimeout(function() {reject(1)}, 10) }) var promise = Promise.resolve() promise.then(function() { return pending }).then(null, function(value) { o(value).equals(1) done() }) }) o("triggers all branched rejection handlers upon rejection", function(done) { var promise = Promise.reject() var then = o.spy() var catch1 = o.spy() var catch2 = o.spy() var catch3 = o.spy() promise.catch(catch1) promise.then(then, catch2) promise.then(then).catch(catch3) callAsync(function() { callAsync(function() { o(catch1.callCount).equals(1) o(then.callCount).equals(0) o(catch2.callCount).equals(1) o(catch3.callCount).equals(1) done() }) }) }) o("does not absorb resolved promise via static rejector", function(done) { var promise = Promise.reject(Promise.resolve(1)) promise.then(null, function(value) { o(value instanceof Promise).equals(true) done() }) }) o("does not absorb rejected promise via static rejector", function(done) { var rejected = Promise.reject(1) rejected.catch(function() {}) var promise = Promise.reject(rejected) promise.then(null, function(value) { o(value instanceof Promise).equals(true) done() }) }) o("does not absorb resolved promise in executor reject", function(done) { var promise = new Promise(function(resolve, reject) { reject(Promise.resolve(1)) }) promise.then(null, function(value) { o(value instanceof Promise).equals(true) done() }) }) o("does not absorb rejected promise in executor reject", function(done) { var promise = new Promise(function(resolve, reject) { var rejected = Promise.reject(1) rejected.catch(function() {}) reject(rejected) }) promise.then(null, function(value) { o(value instanceof Promise).equals(true) done() }) }) o("does not absorb resolved promise on fulfillment error", function(done) { var promise = Promise.resolve() promise.then(function() { throw Promise.resolve(1) }).then(null, function(value) { o(value instanceof Promise).equals(true) done() }) }) o("does not absorb rejected promise on fulfillment error", function(done) { var promise = Promise.resolve() promise.then(function() { var rejected = Promise.reject(1) rejected.catch(function() {}) throw rejected }).then(null, function(value) { o(value instanceof Promise).equals(true) done() }) }) o("promise stays pending if absorbed promise is pending", function(done) { var promise = new Promise(function(resolve) {resolve()}) var fulfilled = false, rejected = false promise.then(function() { return new Promise(function() {}) }).then(function() { fulfilled = true }, function() { rejected = false }) setTimeout(function() { o(fulfilled).equals(false) o(rejected).equals(false) done() }, 10) }) o("absorbs early resolved promise", function(done) { var resolved = Promise.resolve(1) var promise = new Promise(function(resolve) { setTimeout(function() { resolve(resolved) }, 10) }) promise.then(function(value) { o(value).equals(1) done() }) }) }) o.spec("race", function() { o("resolves to first resolved", function(done) { var a = Promise.resolve(1) var b = new Promise(function(resolve, reject) { callAsync(function() {resolve(2)}) }) Promise.race([a, b]).then(function(value) { o(value).equals(1) done() }) }) o("rejects to first rejected", function(done) { var a = Promise.reject(1) var b = new Promise(function(resolve, reject) { callAsync(function() {reject(2)}) }) Promise.race([a, b]).then(null, function(value) { o(value).equals(1) done() }) }) }) o.spec("all", function() { o("resolves to array", function(done) { var a = new Promise(function(resolve, reject) { callAsync(function() {resolve(1)}) }) var b = Promise.resolve(2) Promise.all([a, b]).then(function(value) { o(value).deepEquals([1, 2]) done() }) }) o("resolves empty array", function(done) { Promise.all([]).then(function(value) { o(value).deepEquals([]) done() }) }) o("resolves non-promise to itself", function(done) { var a = new Promise(function(resolve, reject) { callAsync(function() {resolve(1)}) }) var b = Promise.resolve(2) var c = 3 Promise.all([a, b, c]).then(function(value) { o(value).deepEquals([1, 2, 3]) done() }) }) o("rejects to first rejected", function(done) { var a = Promise.reject(1) var b = new Promise(function(resolve, reject) { callAsync(function() {reject(2)}) }) Promise.all([a, b]).then(null, function(value) { o(value).equals(1) done() }) }) }) o.spec("A+ compliance", function() { o("accesses then only once", function(done) { var readCount = 0 var promise = Promise.resolve(1).then(function() { return Object.create(null, { then: { get: function () { ++readCount return function(onFulfilled) { onFulfilled() } } } }) }) promise.then(function(value) { o(readCount).equals(1) done() }) }) o("works if thennable resolves twice", function(done) { var promise = Promise.resolve({ then: function(res) { res({ then: function(resolve) { setTimeout(function() {resolve(2)}) } }) res(1) } }) promise.then(function(value) { o(value).equals(2) done() }) }) o("works if thennable resolves async rejection then throws", function(done) { var promise = new Promise(function(res) { res({ then: function(resolve, reject) { setTimeout(function() {reject(2)}) } }) throw 3 }) promise.then(null, function(value) { o(value).equals(2) done() }) }) }) })