├── .gitignore ├── .jshintrc ├── LICENSE.txt ├── README.md ├── lib ├── cli.js ├── getMochaOpts.js ├── programmaticRunner.js └── tests │ ├── 2.1.2.js │ ├── 2.1.3.js │ ├── 2.2.1.js │ ├── 2.2.2.js │ ├── 2.2.3.js │ ├── 2.2.4.js │ ├── 2.2.5.js │ ├── 2.2.6.js │ ├── 2.2.7.js │ ├── 2.3.1.js │ ├── 2.3.2.js │ ├── 2.3.3.js │ ├── 2.3.4.js │ └── helpers │ ├── reasons.js │ ├── testThreeCases.js │ └── thenables.js ├── package.json ├── scripts └── generateTestFiles.js └── test └── getMochaOptsTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /npm-debug.log 3 | lib/testFiles.js 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "camelcase": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "globalstrict": true, 6 | "immed": true, 7 | "indent": 4, 8 | "maxlen": 120, 9 | "newcap": true, 10 | "noarg": true, 11 | "node": true, 12 | "nonew": true, 13 | "nomen": true, 14 | "quotmark": "double", 15 | "trailing": true, 16 | "undef": true, 17 | "unused": true, 18 | "white": true, 19 | "globals": { 20 | "afterEach": false, 21 | "beforeEach": false, 22 | "describe": false, 23 | "it": false, 24 | "specify": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2012–2015 Domenic Denicola 2 | 3 | This work is free. You can redistribute it and/or modify it under the 4 | terms of the Do What The Fuck You Want To Public License, Version 2, 5 | as published by Sam Hocevar. See below for more details. 6 | 7 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 8 | Version 2, December 2004 9 | 10 | Copyright (C) 2004 Sam Hocevar 11 | 12 | Everyone is permitted to copy and distribute verbatim or modified 13 | copies of this license document, and changing it is allowed as long 14 | as the name is changed. 15 | 16 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 17 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 18 | 19 | 0. You just DO WHAT THE FUCK YOU WANT TO. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Promises/A+ logo 4 | 5 | 6 | # Promises/A+ Compliance Test Suite 7 | 8 | This suite tests compliance of a promise implementation with the [Promises/A+ specification][]. 9 | 10 | [Promises/A+ specification]: https://github.com/promises-aplus/promises-spec 11 | 12 | Passing the tests in this repo means that you have a Promises/A+ compliant implementation of the `then()` method, and you can display the Promises/A+ logo in your README. You can also [send a pull request](https://github.com/promises-aplus/promises-spec) to have your implementation listed on the [implementations page](https://promisesaplus.com/implementations). 13 | 14 | ## How To Run 15 | 16 | The tests can run in either a Node.js environment or, if you set things up correctly, in the browser. 17 | 18 | ### Adapters 19 | 20 | In order to test your promise library, you must expose a very minimal adapter interface. These are written as Node.js 21 | modules with a few well-known exports: 22 | 23 | - `resolved(value)`: creates a promise that is resolved with `value`. 24 | - `rejected(reason)`: creates a promise that is already rejected with `reason`. 25 | - `deferred()`: creates an object consisting of `{ promise, resolve, reject }`: 26 | - `promise` is a promise that is currently in the pending state. 27 | - `resolve(value)` resolves the promise with `value`. 28 | - `reject(reason)` moves the promise from the pending state to the rejected state, with rejection reason `reason`. 29 | 30 | The `resolved` and `rejected` exports are actually optional, and will be automatically created by the test runner using 31 | `deferred` if they are not present. But, if your promise library has the capability to create already-resolved or 32 | already-rejected promises, then you should include these exports, so that the test runner can provide you with better 33 | code coverage and uncover any bugs in those methods. 34 | 35 | Note that the tests will never pass a promise or a thenable as a resolution. That means that we never use the promise- 36 | or thenable-accepting forms of the resolve operation directly, and instead only use the direct fulfillment operation, 37 | since fulfill and resolve are equivalent when not given a thenable. 38 | 39 | Finally, note that none of these functions, including `deferred().resolve` and `deferred().reject`, should throw 40 | exceptions. The tests are not structured to deal with that, and if your implementation has the potential to throw 41 | exceptions—e.g., perhaps it throws when trying to resolve an already-resolved promise—you should wrap direct calls to 42 | your implementation in `try`/`catch` when writing the adapter. 43 | 44 | ### From the CLI 45 | 46 | This package comes with a command-line interface that can be used either by installing it globally with 47 | `npm install promises-aplus-tests -g` or by including it in your `package.json`'s `devDependencies` and using npm's 48 | `scripts` feature. In the latter case, your setup might look something like 49 | 50 | ```json 51 | { 52 | "devDependencies": { 53 | "promises-aplus-tests": "*" 54 | }, 55 | "scripts": { 56 | "test": "run-my-own-tests && promises-aplus-tests test/my-adapter" 57 | } 58 | } 59 | ``` 60 | 61 | The CLI takes as its first argument the filename of your adapter file, relative to the current working directory. It 62 | tries to pass through any subsequent options to Mocha, so you can use e.g. `--reporter spec` or `--grep 2.2.4`. 63 | 64 | ### Programmatically 65 | 66 | The main export of this package is a function that allows you to run the tests against an adapter: 67 | 68 | ```js 69 | var promisesAplusTests = require("promises-aplus-tests"); 70 | 71 | promisesAplusTests(adapter, function (err) { 72 | // All done; output is in the console. Or check `err` for number of failures. 73 | }); 74 | ``` 75 | 76 | You can also pass any Mocha options as the second parameter, e.g. 77 | 78 | ```js 79 | promisesAplusTests(adapter, { reporter: "dot" }, function (err) { 80 | // As before. 81 | }); 82 | ``` 83 | 84 | ### Within an Existing Mocha Test Suite 85 | 86 | If you already have a Mocha test suite and want to include these tests in it, you can do: 87 | 88 | ```js 89 | describe("Promises/A+ Tests", function () { 90 | require("promises-aplus-tests").mocha(adapter); 91 | }); 92 | ``` 93 | 94 | This also works in the browser, if you have your Mocha tests running there, as long as you use [browserify](http://browserify.org/). 95 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | var path = require("path"); 5 | var getMochaOpts = require("./getMochaOpts"); 6 | var programmaticRunner = require("./programmaticRunner"); 7 | 8 | var filePath = getAdapterFilePath(); 9 | var adapter = adapterObjectFromFilePath(filePath); 10 | var mochaOpts = getMochaOpts(process.argv.slice(3)); 11 | programmaticRunner(adapter, mochaOpts, function (err) { 12 | if (err) { 13 | process.exit(err.failures || -1); 14 | } 15 | }); 16 | 17 | function getAdapterFilePath() { 18 | if (process.argv[2]) { 19 | return path.join(process.cwd(), process.argv[2]); 20 | } else { 21 | throw new Error("Specify your adapter file as an argument."); 22 | } 23 | } 24 | 25 | function adapterObjectFromFilePath(filePath) { 26 | try { 27 | return require(filePath); 28 | } catch (e) { 29 | var error = new Error("Error `require`ing adapter file " + filePath + "\n\n" + e); 30 | error.cause = e; 31 | 32 | throw error; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/getMochaOpts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function getMochaOpts(args) { 4 | var rawOpts = args; 5 | var opts = {}; 6 | 7 | rawOpts.join(" ").split("--").forEach(function (opt) { 8 | var optSplit = opt.split(" "); 9 | 10 | var key = optSplit[0]; 11 | var value = optSplit[1] || true; 12 | 13 | if (key) { 14 | opts[key] = value; 15 | } 16 | }); 17 | 18 | return opts; 19 | }; 20 | -------------------------------------------------------------------------------- /lib/programmaticRunner.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Mocha = require("mocha"); 4 | var path = require("path"); 5 | var fs = require("fs"); 6 | var _ = require("underscore"); 7 | 8 | var testsDir = path.resolve(__dirname, "tests"); 9 | 10 | function normalizeAdapter(adapter) { 11 | if (!adapter.resolved) { 12 | adapter.resolved = function (value) { 13 | var d = adapter.deferred(); 14 | d.resolve(value); 15 | return d.promise; 16 | }; 17 | } 18 | 19 | if (!adapter.rejected) { 20 | adapter.rejected = function (reason) { 21 | var d = adapter.deferred(); 22 | d.reject(reason); 23 | return d.promise; 24 | }; 25 | } 26 | } 27 | 28 | module.exports = function (adapter, mochaOpts, cb) { 29 | if (typeof mochaOpts === "function") { 30 | cb = mochaOpts; 31 | mochaOpts = {}; 32 | } 33 | if (typeof cb !== "function") { 34 | cb = function () { }; 35 | } 36 | 37 | normalizeAdapter(adapter); 38 | mochaOpts = _.defaults(mochaOpts, { timeout: 200, slow: Infinity }); 39 | 40 | fs.readdir(testsDir, function (err, testFileNames) { 41 | if (err) { 42 | cb(err); 43 | return; 44 | } 45 | 46 | var mocha = new Mocha(mochaOpts); 47 | testFileNames.forEach(function (testFileName) { 48 | if (path.extname(testFileName) === ".js") { 49 | var testFilePath = path.resolve(testsDir, testFileName); 50 | mocha.addFile(testFilePath); 51 | } 52 | }); 53 | 54 | global.adapter = adapter; 55 | mocha.run(function (failures) { 56 | delete global.adapter; 57 | if (failures > 0) { 58 | var err = new Error("Test suite failed with " + failures + " failures."); 59 | err.failures = failures; 60 | cb(err); 61 | } else { 62 | cb(null); 63 | } 64 | }); 65 | }); 66 | }; 67 | 68 | module.exports.mocha = function (adapter) { 69 | normalizeAdapter(adapter); 70 | 71 | global.adapter = adapter; 72 | 73 | require("./testFiles"); 74 | 75 | delete global.adapter; 76 | }; 77 | -------------------------------------------------------------------------------- /lib/tests/2.1.2.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var testFulfilled = require("./helpers/testThreeCases").testFulfilled; 5 | 6 | var adapter = global.adapter; 7 | var deferred = adapter.deferred; 8 | 9 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 10 | 11 | describe("2.1.2.1: When fulfilled, a promise: must not transition to any other state.", function () { 12 | testFulfilled(dummy, function (promise, done) { 13 | var onFulfilledCalled = false; 14 | 15 | promise.then(function onFulfilled() { 16 | onFulfilledCalled = true; 17 | }, function onRejected() { 18 | assert.strictEqual(onFulfilledCalled, false); 19 | done(); 20 | }); 21 | 22 | setTimeout(done, 100); 23 | }); 24 | 25 | specify("trying to fulfill then immediately reject", function (done) { 26 | var d = deferred(); 27 | var onFulfilledCalled = false; 28 | 29 | d.promise.then(function onFulfilled() { 30 | onFulfilledCalled = true; 31 | }, function onRejected() { 32 | assert.strictEqual(onFulfilledCalled, false); 33 | done(); 34 | }); 35 | 36 | d.resolve(dummy); 37 | d.reject(dummy); 38 | setTimeout(done, 100); 39 | }); 40 | 41 | specify("trying to fulfill then reject, delayed", function (done) { 42 | var d = deferred(); 43 | var onFulfilledCalled = false; 44 | 45 | d.promise.then(function onFulfilled() { 46 | onFulfilledCalled = true; 47 | }, function onRejected() { 48 | assert.strictEqual(onFulfilledCalled, false); 49 | done(); 50 | }); 51 | 52 | setTimeout(function () { 53 | d.resolve(dummy); 54 | d.reject(dummy); 55 | }, 50); 56 | setTimeout(done, 100); 57 | }); 58 | 59 | specify("trying to fulfill immediately then reject delayed", function (done) { 60 | var d = deferred(); 61 | var onFulfilledCalled = false; 62 | 63 | d.promise.then(function onFulfilled() { 64 | onFulfilledCalled = true; 65 | }, function onRejected() { 66 | assert.strictEqual(onFulfilledCalled, false); 67 | done(); 68 | }); 69 | 70 | d.resolve(dummy); 71 | setTimeout(function () { 72 | d.reject(dummy); 73 | }, 50); 74 | setTimeout(done, 100); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /lib/tests/2.1.3.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var testRejected = require("./helpers/testThreeCases").testRejected; 5 | 6 | var adapter = global.adapter; 7 | var deferred = adapter.deferred; 8 | 9 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 10 | 11 | describe("2.1.3.1: When rejected, a promise: must not transition to any other state.", function () { 12 | testRejected(dummy, function (promise, done) { 13 | var onRejectedCalled = false; 14 | 15 | promise.then(function onFulfilled() { 16 | assert.strictEqual(onRejectedCalled, false); 17 | done(); 18 | }, function onRejected() { 19 | onRejectedCalled = true; 20 | }); 21 | 22 | setTimeout(done, 100); 23 | }); 24 | 25 | specify("trying to reject then immediately fulfill", function (done) { 26 | var d = deferred(); 27 | var onRejectedCalled = false; 28 | 29 | d.promise.then(function onFulfilled() { 30 | assert.strictEqual(onRejectedCalled, false); 31 | done(); 32 | }, function onRejected() { 33 | onRejectedCalled = true; 34 | }); 35 | 36 | d.reject(dummy); 37 | d.resolve(dummy); 38 | setTimeout(done, 100); 39 | }); 40 | 41 | specify("trying to reject then fulfill, delayed", function (done) { 42 | var d = deferred(); 43 | var onRejectedCalled = false; 44 | 45 | d.promise.then(function onFulfilled() { 46 | assert.strictEqual(onRejectedCalled, false); 47 | done(); 48 | }, function onRejected() { 49 | onRejectedCalled = true; 50 | }); 51 | 52 | setTimeout(function () { 53 | d.reject(dummy); 54 | d.resolve(dummy); 55 | }, 50); 56 | setTimeout(done, 100); 57 | }); 58 | 59 | specify("trying to reject immediately then fulfill delayed", function (done) { 60 | var d = deferred(); 61 | var onRejectedCalled = false; 62 | 63 | d.promise.then(function onFulfilled() { 64 | assert.strictEqual(onRejectedCalled, false); 65 | done(); 66 | }, function onRejected() { 67 | onRejectedCalled = true; 68 | }); 69 | 70 | d.reject(dummy); 71 | setTimeout(function () { 72 | d.resolve(dummy); 73 | }, 50); 74 | setTimeout(done, 100); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /lib/tests/2.2.1.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var adapter = global.adapter; 4 | var resolved = adapter.resolved; 5 | var rejected = adapter.rejected; 6 | 7 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 8 | 9 | describe("2.2.1: Both `onFulfilled` and `onRejected` are optional arguments.", function () { 10 | describe("2.2.1.1: If `onFulfilled` is not a function, it must be ignored.", function () { 11 | describe("applied to a directly-rejected promise", function () { 12 | function testNonFunction(nonFunction, stringRepresentation) { 13 | specify("`onFulfilled` is " + stringRepresentation, function (done) { 14 | rejected(dummy).then(nonFunction, function () { 15 | done(); 16 | }); 17 | }); 18 | } 19 | 20 | testNonFunction(undefined, "`undefined`"); 21 | testNonFunction(null, "`null`"); 22 | testNonFunction(false, "`false`"); 23 | testNonFunction(5, "`5`"); 24 | testNonFunction({}, "an object"); 25 | }); 26 | 27 | describe("applied to a promise rejected and then chained off of", function () { 28 | function testNonFunction(nonFunction, stringRepresentation) { 29 | specify("`onFulfilled` is " + stringRepresentation, function (done) { 30 | rejected(dummy).then(function () { }, undefined).then(nonFunction, function () { 31 | done(); 32 | }); 33 | }); 34 | } 35 | 36 | testNonFunction(undefined, "`undefined`"); 37 | testNonFunction(null, "`null`"); 38 | testNonFunction(false, "`false`"); 39 | testNonFunction(5, "`5`"); 40 | testNonFunction({}, "an object"); 41 | }); 42 | }); 43 | 44 | describe("2.2.1.2: If `onRejected` is not a function, it must be ignored.", function () { 45 | describe("applied to a directly-fulfilled promise", function () { 46 | function testNonFunction(nonFunction, stringRepresentation) { 47 | specify("`onRejected` is " + stringRepresentation, function (done) { 48 | resolved(dummy).then(function () { 49 | done(); 50 | }, nonFunction); 51 | }); 52 | } 53 | 54 | testNonFunction(undefined, "`undefined`"); 55 | testNonFunction(null, "`null`"); 56 | testNonFunction(false, "`false`"); 57 | testNonFunction(5, "`5`"); 58 | testNonFunction({}, "an object"); 59 | }); 60 | 61 | describe("applied to a promise fulfilled and then chained off of", function () { 62 | function testNonFunction(nonFunction, stringRepresentation) { 63 | specify("`onRejected` is " + stringRepresentation, function (done) { 64 | resolved(dummy).then(undefined, function () { }).then(function () { 65 | done(); 66 | }, nonFunction); 67 | }); 68 | } 69 | 70 | testNonFunction(undefined, "`undefined`"); 71 | testNonFunction(null, "`null`"); 72 | testNonFunction(false, "`false`"); 73 | testNonFunction(5, "`5`"); 74 | testNonFunction({}, "an object"); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /lib/tests/2.2.2.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var testFulfilled = require("./helpers/testThreeCases").testFulfilled; 5 | 6 | var adapter = global.adapter; 7 | var resolved = adapter.resolved; 8 | var deferred = adapter.deferred; 9 | 10 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 11 | var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality 12 | 13 | describe("2.2.2: If `onFulfilled` is a function,", function () { 14 | describe("2.2.2.1: it must be called after `promise` is fulfilled, with `promise`’s fulfillment value as its " + 15 | "first argument.", function () { 16 | testFulfilled(sentinel, function (promise, done) { 17 | promise.then(function onFulfilled(value) { 18 | assert.strictEqual(value, sentinel); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | 24 | describe("2.2.2.2: it must not be called before `promise` is fulfilled", function () { 25 | specify("fulfilled after a delay", function (done) { 26 | var d = deferred(); 27 | var isFulfilled = false; 28 | 29 | d.promise.then(function onFulfilled() { 30 | assert.strictEqual(isFulfilled, true); 31 | done(); 32 | }); 33 | 34 | setTimeout(function () { 35 | d.resolve(dummy); 36 | isFulfilled = true; 37 | }, 50); 38 | }); 39 | 40 | specify("never fulfilled", function (done) { 41 | var d = deferred(); 42 | var onFulfilledCalled = false; 43 | 44 | d.promise.then(function onFulfilled() { 45 | onFulfilledCalled = true; 46 | done(); 47 | }); 48 | 49 | setTimeout(function () { 50 | assert.strictEqual(onFulfilledCalled, false); 51 | done(); 52 | }, 150); 53 | }); 54 | }); 55 | 56 | describe("2.2.2.3: it must not be called more than once.", function () { 57 | specify("already-fulfilled", function (done) { 58 | var timesCalled = 0; 59 | 60 | resolved(dummy).then(function onFulfilled() { 61 | assert.strictEqual(++timesCalled, 1); 62 | done(); 63 | }); 64 | }); 65 | 66 | specify("trying to fulfill a pending promise more than once, immediately", function (done) { 67 | var d = deferred(); 68 | var timesCalled = 0; 69 | 70 | d.promise.then(function onFulfilled() { 71 | assert.strictEqual(++timesCalled, 1); 72 | done(); 73 | }); 74 | 75 | d.resolve(dummy); 76 | d.resolve(dummy); 77 | }); 78 | 79 | specify("trying to fulfill a pending promise more than once, delayed", function (done) { 80 | var d = deferred(); 81 | var timesCalled = 0; 82 | 83 | d.promise.then(function onFulfilled() { 84 | assert.strictEqual(++timesCalled, 1); 85 | done(); 86 | }); 87 | 88 | setTimeout(function () { 89 | d.resolve(dummy); 90 | d.resolve(dummy); 91 | }, 50); 92 | }); 93 | 94 | specify("trying to fulfill a pending promise more than once, immediately then delayed", function (done) { 95 | var d = deferred(); 96 | var timesCalled = 0; 97 | 98 | d.promise.then(function onFulfilled() { 99 | assert.strictEqual(++timesCalled, 1); 100 | done(); 101 | }); 102 | 103 | d.resolve(dummy); 104 | setTimeout(function () { 105 | d.resolve(dummy); 106 | }, 50); 107 | }); 108 | 109 | specify("when multiple `then` calls are made, spaced apart in time", function (done) { 110 | var d = deferred(); 111 | var timesCalled = [0, 0, 0]; 112 | 113 | d.promise.then(function onFulfilled() { 114 | assert.strictEqual(++timesCalled[0], 1); 115 | }); 116 | 117 | setTimeout(function () { 118 | d.promise.then(function onFulfilled() { 119 | assert.strictEqual(++timesCalled[1], 1); 120 | }); 121 | }, 50); 122 | 123 | setTimeout(function () { 124 | d.promise.then(function onFulfilled() { 125 | assert.strictEqual(++timesCalled[2], 1); 126 | done(); 127 | }); 128 | }, 100); 129 | 130 | setTimeout(function () { 131 | d.resolve(dummy); 132 | }, 150); 133 | }); 134 | 135 | specify("when `then` is interleaved with fulfillment", function (done) { 136 | var d = deferred(); 137 | var timesCalled = [0, 0]; 138 | 139 | d.promise.then(function onFulfilled() { 140 | assert.strictEqual(++timesCalled[0], 1); 141 | }); 142 | 143 | d.resolve(dummy); 144 | 145 | d.promise.then(function onFulfilled() { 146 | assert.strictEqual(++timesCalled[1], 1); 147 | done(); 148 | }); 149 | }); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /lib/tests/2.2.3.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var testRejected = require("./helpers/testThreeCases").testRejected; 5 | 6 | var adapter = global.adapter; 7 | var rejected = adapter.rejected; 8 | var deferred = adapter.deferred; 9 | 10 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 11 | var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality 12 | 13 | describe("2.2.3: If `onRejected` is a function,", function () { 14 | describe("2.2.3.1: it must be called after `promise` is rejected, with `promise`’s rejection reason as its " + 15 | "first argument.", function () { 16 | testRejected(sentinel, function (promise, done) { 17 | promise.then(null, function onRejected(reason) { 18 | assert.strictEqual(reason, sentinel); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | 24 | describe("2.2.3.2: it must not be called before `promise` is rejected", function () { 25 | specify("rejected after a delay", function (done) { 26 | var d = deferred(); 27 | var isRejected = false; 28 | 29 | d.promise.then(null, function onRejected() { 30 | assert.strictEqual(isRejected, true); 31 | done(); 32 | }); 33 | 34 | setTimeout(function () { 35 | d.reject(dummy); 36 | isRejected = true; 37 | }, 50); 38 | }); 39 | 40 | specify("never rejected", function (done) { 41 | var d = deferred(); 42 | var onRejectedCalled = false; 43 | 44 | d.promise.then(null, function onRejected() { 45 | onRejectedCalled = true; 46 | done(); 47 | }); 48 | 49 | setTimeout(function () { 50 | assert.strictEqual(onRejectedCalled, false); 51 | done(); 52 | }, 150); 53 | }); 54 | }); 55 | 56 | describe("2.2.3.3: it must not be called more than once.", function () { 57 | specify("already-rejected", function (done) { 58 | var timesCalled = 0; 59 | 60 | rejected(dummy).then(null, function onRejected() { 61 | assert.strictEqual(++timesCalled, 1); 62 | done(); 63 | }); 64 | }); 65 | 66 | specify("trying to reject a pending promise more than once, immediately", function (done) { 67 | var d = deferred(); 68 | var timesCalled = 0; 69 | 70 | d.promise.then(null, function onRejected() { 71 | assert.strictEqual(++timesCalled, 1); 72 | done(); 73 | }); 74 | 75 | d.reject(dummy); 76 | d.reject(dummy); 77 | }); 78 | 79 | specify("trying to reject a pending promise more than once, delayed", function (done) { 80 | var d = deferred(); 81 | var timesCalled = 0; 82 | 83 | d.promise.then(null, function onRejected() { 84 | assert.strictEqual(++timesCalled, 1); 85 | done(); 86 | }); 87 | 88 | setTimeout(function () { 89 | d.reject(dummy); 90 | d.reject(dummy); 91 | }, 50); 92 | }); 93 | 94 | specify("trying to reject a pending promise more than once, immediately then delayed", function (done) { 95 | var d = deferred(); 96 | var timesCalled = 0; 97 | 98 | d.promise.then(null, function onRejected() { 99 | assert.strictEqual(++timesCalled, 1); 100 | done(); 101 | }); 102 | 103 | d.reject(dummy); 104 | setTimeout(function () { 105 | d.reject(dummy); 106 | }, 50); 107 | }); 108 | 109 | specify("when multiple `then` calls are made, spaced apart in time", function (done) { 110 | var d = deferred(); 111 | var timesCalled = [0, 0, 0]; 112 | 113 | d.promise.then(null, function onRejected() { 114 | assert.strictEqual(++timesCalled[0], 1); 115 | }); 116 | 117 | setTimeout(function () { 118 | d.promise.then(null, function onRejected() { 119 | assert.strictEqual(++timesCalled[1], 1); 120 | }); 121 | }, 50); 122 | 123 | setTimeout(function () { 124 | d.promise.then(null, function onRejected() { 125 | assert.strictEqual(++timesCalled[2], 1); 126 | done(); 127 | }); 128 | }, 100); 129 | 130 | setTimeout(function () { 131 | d.reject(dummy); 132 | }, 150); 133 | }); 134 | 135 | specify("when `then` is interleaved with rejection", function (done) { 136 | var d = deferred(); 137 | var timesCalled = [0, 0]; 138 | 139 | d.promise.then(null, function onRejected() { 140 | assert.strictEqual(++timesCalled[0], 1); 141 | }); 142 | 143 | d.reject(dummy); 144 | 145 | d.promise.then(null, function onRejected() { 146 | assert.strictEqual(++timesCalled[1], 1); 147 | done(); 148 | }); 149 | }); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /lib/tests/2.2.4.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var testFulfilled = require("./helpers/testThreeCases").testFulfilled; 5 | var testRejected = require("./helpers/testThreeCases").testRejected; 6 | 7 | var adapter = global.adapter; 8 | var resolved = adapter.resolved; 9 | var rejected = adapter.rejected; 10 | var deferred = adapter.deferred; 11 | 12 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 13 | 14 | describe("2.2.4: `onFulfilled` or `onRejected` must not be called until the execution context stack contains only " + 15 | "platform code.", function () { 16 | describe("`then` returns before the promise becomes fulfilled or rejected", function () { 17 | testFulfilled(dummy, function (promise, done) { 18 | var thenHasReturned = false; 19 | 20 | promise.then(function onFulfilled() { 21 | assert.strictEqual(thenHasReturned, true); 22 | done(); 23 | }); 24 | 25 | thenHasReturned = true; 26 | }); 27 | testRejected(dummy, function (promise, done) { 28 | var thenHasReturned = false; 29 | 30 | promise.then(null, function onRejected() { 31 | assert.strictEqual(thenHasReturned, true); 32 | done(); 33 | }); 34 | 35 | thenHasReturned = true; 36 | }); 37 | }); 38 | 39 | describe("Clean-stack execution ordering tests (fulfillment case)", function () { 40 | specify("when `onFulfilled` is added immediately before the promise is fulfilled", 41 | function () { 42 | var d = deferred(); 43 | var onFulfilledCalled = false; 44 | 45 | d.promise.then(function onFulfilled() { 46 | onFulfilledCalled = true; 47 | }); 48 | 49 | d.resolve(dummy); 50 | 51 | assert.strictEqual(onFulfilledCalled, false); 52 | }); 53 | 54 | specify("when `onFulfilled` is added immediately after the promise is fulfilled", 55 | function () { 56 | var d = deferred(); 57 | var onFulfilledCalled = false; 58 | 59 | d.resolve(dummy); 60 | 61 | d.promise.then(function onFulfilled() { 62 | onFulfilledCalled = true; 63 | }); 64 | 65 | assert.strictEqual(onFulfilledCalled, false); 66 | }); 67 | 68 | specify("when one `onFulfilled` is added inside another `onFulfilled`", function (done) { 69 | var promise = resolved(); 70 | var firstOnFulfilledFinished = false; 71 | 72 | promise.then(function () { 73 | promise.then(function () { 74 | assert.strictEqual(firstOnFulfilledFinished, true); 75 | done(); 76 | }); 77 | firstOnFulfilledFinished = true; 78 | }); 79 | }); 80 | 81 | specify("when `onFulfilled` is added inside an `onRejected`", function (done) { 82 | var promise = rejected(); 83 | var promise2 = resolved(); 84 | var firstOnRejectedFinished = false; 85 | 86 | promise.then(null, function () { 87 | promise2.then(function () { 88 | assert.strictEqual(firstOnRejectedFinished, true); 89 | done(); 90 | }); 91 | firstOnRejectedFinished = true; 92 | }); 93 | }); 94 | 95 | specify("when the promise is fulfilled asynchronously", function (done) { 96 | var d = deferred(); 97 | var firstStackFinished = false; 98 | 99 | setTimeout(function () { 100 | d.resolve(dummy); 101 | firstStackFinished = true; 102 | }, 0); 103 | 104 | d.promise.then(function () { 105 | assert.strictEqual(firstStackFinished, true); 106 | done(); 107 | }); 108 | }); 109 | }); 110 | 111 | describe("Clean-stack execution ordering tests (rejection case)", function () { 112 | specify("when `onRejected` is added immediately before the promise is rejected", 113 | function () { 114 | var d = deferred(); 115 | var onRejectedCalled = false; 116 | 117 | d.promise.then(null, function onRejected() { 118 | onRejectedCalled = true; 119 | }); 120 | 121 | d.reject(dummy); 122 | 123 | assert.strictEqual(onRejectedCalled, false); 124 | }); 125 | 126 | specify("when `onRejected` is added immediately after the promise is rejected", 127 | function () { 128 | var d = deferred(); 129 | var onRejectedCalled = false; 130 | 131 | d.reject(dummy); 132 | 133 | d.promise.then(null, function onRejected() { 134 | onRejectedCalled = true; 135 | }); 136 | 137 | assert.strictEqual(onRejectedCalled, false); 138 | }); 139 | 140 | specify("when `onRejected` is added inside an `onFulfilled`", function (done) { 141 | var promise = resolved(); 142 | var promise2 = rejected(); 143 | var firstOnFulfilledFinished = false; 144 | 145 | promise.then(function () { 146 | promise2.then(null, function () { 147 | assert.strictEqual(firstOnFulfilledFinished, true); 148 | done(); 149 | }); 150 | firstOnFulfilledFinished = true; 151 | }); 152 | }); 153 | 154 | specify("when one `onRejected` is added inside another `onRejected`", function (done) { 155 | var promise = rejected(); 156 | var firstOnRejectedFinished = false; 157 | 158 | promise.then(null, function () { 159 | promise.then(null, function () { 160 | assert.strictEqual(firstOnRejectedFinished, true); 161 | done(); 162 | }); 163 | firstOnRejectedFinished = true; 164 | }); 165 | }); 166 | 167 | specify("when the promise is rejected asynchronously", function (done) { 168 | var d = deferred(); 169 | var firstStackFinished = false; 170 | 171 | setTimeout(function () { 172 | d.reject(dummy); 173 | firstStackFinished = true; 174 | }, 0); 175 | 176 | d.promise.then(null, function () { 177 | assert.strictEqual(firstStackFinished, true); 178 | done(); 179 | }); 180 | }); 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /lib/tests/2.2.5.js: -------------------------------------------------------------------------------- 1 | /*jshint strict: false */ 2 | 3 | var assert = require("assert"); 4 | 5 | var adapter = global.adapter; 6 | var resolved = adapter.resolved; 7 | var rejected = adapter.rejected; 8 | 9 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 10 | 11 | describe("2.2.5 `onFulfilled` and `onRejected` must be called as functions (i.e. with no `this` value).", function () { 12 | describe("strict mode", function () { 13 | specify("fulfilled", function (done) { 14 | resolved(dummy).then(function onFulfilled() { 15 | "use strict"; 16 | 17 | assert.strictEqual(this, undefined); 18 | done(); 19 | }); 20 | }); 21 | 22 | specify("rejected", function (done) { 23 | rejected(dummy).then(null, function onRejected() { 24 | "use strict"; 25 | 26 | assert.strictEqual(this, undefined); 27 | done(); 28 | }); 29 | }); 30 | }); 31 | 32 | describe("sloppy mode", function () { 33 | specify("fulfilled", function (done) { 34 | resolved(dummy).then(function onFulfilled() { 35 | assert.strictEqual(this, global); 36 | done(); 37 | }); 38 | }); 39 | 40 | specify("rejected", function (done) { 41 | rejected(dummy).then(null, function onRejected() { 42 | assert.strictEqual(this, global); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /lib/tests/2.2.6.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var sinon = require("sinon"); 5 | var testFulfilled = require("./helpers/testThreeCases").testFulfilled; 6 | var testRejected = require("./helpers/testThreeCases").testRejected; 7 | 8 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 9 | var other = { other: "other" }; // a value we don't want to be strict equal to 10 | var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality 11 | var sentinel2 = { sentinel2: "sentinel2" }; 12 | var sentinel3 = { sentinel3: "sentinel3" }; 13 | 14 | function callbackAggregator(times, ultimateCallback) { 15 | var soFar = 0; 16 | return function () { 17 | if (++soFar === times) { 18 | ultimateCallback(); 19 | } 20 | }; 21 | } 22 | 23 | describe("2.2.6: `then` may be called multiple times on the same promise.", function () { 24 | describe("2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` callbacks must execute in the " + 25 | "order of their originating calls to `then`.", function () { 26 | describe("multiple boring fulfillment handlers", function () { 27 | testFulfilled(sentinel, function (promise, done) { 28 | var handler1 = sinon.stub().returns(other); 29 | var handler2 = sinon.stub().returns(other); 30 | var handler3 = sinon.stub().returns(other); 31 | 32 | var spy = sinon.spy(); 33 | promise.then(handler1, spy); 34 | promise.then(handler2, spy); 35 | promise.then(handler3, spy); 36 | 37 | promise.then(function (value) { 38 | assert.strictEqual(value, sentinel); 39 | 40 | sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); 41 | sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); 42 | sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); 43 | sinon.assert.notCalled(spy); 44 | 45 | done(); 46 | }); 47 | }); 48 | }); 49 | 50 | describe("multiple fulfillment handlers, one of which throws", function () { 51 | testFulfilled(sentinel, function (promise, done) { 52 | var handler1 = sinon.stub().returns(other); 53 | var handler2 = sinon.stub().throws(other); 54 | var handler3 = sinon.stub().returns(other); 55 | 56 | var spy = sinon.spy(); 57 | promise.then(handler1, spy); 58 | promise.then(handler2, spy); 59 | promise.then(handler3, spy); 60 | 61 | promise.then(function (value) { 62 | assert.strictEqual(value, sentinel); 63 | 64 | sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); 65 | sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); 66 | sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); 67 | sinon.assert.notCalled(spy); 68 | 69 | done(); 70 | }); 71 | }); 72 | }); 73 | 74 | describe("results in multiple branching chains with their own fulfillment values", function () { 75 | testFulfilled(dummy, function (promise, done) { 76 | var semiDone = callbackAggregator(3, done); 77 | 78 | promise.then(function () { 79 | return sentinel; 80 | }).then(function (value) { 81 | assert.strictEqual(value, sentinel); 82 | semiDone(); 83 | }); 84 | 85 | promise.then(function () { 86 | throw sentinel2; 87 | }).then(null, function (reason) { 88 | assert.strictEqual(reason, sentinel2); 89 | semiDone(); 90 | }); 91 | 92 | promise.then(function () { 93 | return sentinel3; 94 | }).then(function (value) { 95 | assert.strictEqual(value, sentinel3); 96 | semiDone(); 97 | }); 98 | }); 99 | }); 100 | 101 | describe("`onFulfilled` handlers are called in the original order", function () { 102 | testFulfilled(dummy, function (promise, done) { 103 | var handler1 = sinon.spy(function handler1() {}); 104 | var handler2 = sinon.spy(function handler2() {}); 105 | var handler3 = sinon.spy(function handler3() {}); 106 | 107 | promise.then(handler1); 108 | promise.then(handler2); 109 | promise.then(handler3); 110 | 111 | promise.then(function () { 112 | sinon.assert.callOrder(handler1, handler2, handler3); 113 | done(); 114 | }); 115 | }); 116 | 117 | describe("even when one handler is added inside another handler", function () { 118 | testFulfilled(dummy, function (promise, done) { 119 | var handler1 = sinon.spy(function handler1() {}); 120 | var handler2 = sinon.spy(function handler2() {}); 121 | var handler3 = sinon.spy(function handler3() {}); 122 | 123 | promise.then(function () { 124 | handler1(); 125 | promise.then(handler3); 126 | }); 127 | promise.then(handler2); 128 | 129 | promise.then(function () { 130 | // Give implementations a bit of extra time to flush their internal queue, if necessary. 131 | setTimeout(function () { 132 | sinon.assert.callOrder(handler1, handler2, handler3); 133 | done(); 134 | }, 15); 135 | }); 136 | }); 137 | }); 138 | }); 139 | }); 140 | 141 | describe("2.2.6.2: If/when `promise` is rejected, all respective `onRejected` callbacks must execute in the " + 142 | "order of their originating calls to `then`.", function () { 143 | describe("multiple boring rejection handlers", function () { 144 | testRejected(sentinel, function (promise, done) { 145 | var handler1 = sinon.stub().returns(other); 146 | var handler2 = sinon.stub().returns(other); 147 | var handler3 = sinon.stub().returns(other); 148 | 149 | var spy = sinon.spy(); 150 | promise.then(spy, handler1); 151 | promise.then(spy, handler2); 152 | promise.then(spy, handler3); 153 | 154 | promise.then(null, function (reason) { 155 | assert.strictEqual(reason, sentinel); 156 | 157 | sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); 158 | sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); 159 | sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); 160 | sinon.assert.notCalled(spy); 161 | 162 | done(); 163 | }); 164 | }); 165 | }); 166 | 167 | describe("multiple rejection handlers, one of which throws", function () { 168 | testRejected(sentinel, function (promise, done) { 169 | var handler1 = sinon.stub().returns(other); 170 | var handler2 = sinon.stub().throws(other); 171 | var handler3 = sinon.stub().returns(other); 172 | 173 | var spy = sinon.spy(); 174 | promise.then(spy, handler1); 175 | promise.then(spy, handler2); 176 | promise.then(spy, handler3); 177 | 178 | promise.then(null, function (reason) { 179 | assert.strictEqual(reason, sentinel); 180 | 181 | sinon.assert.calledWith(handler1, sinon.match.same(sentinel)); 182 | sinon.assert.calledWith(handler2, sinon.match.same(sentinel)); 183 | sinon.assert.calledWith(handler3, sinon.match.same(sentinel)); 184 | sinon.assert.notCalled(spy); 185 | 186 | done(); 187 | }); 188 | }); 189 | }); 190 | 191 | describe("results in multiple branching chains with their own fulfillment values", function () { 192 | testRejected(sentinel, function (promise, done) { 193 | var semiDone = callbackAggregator(3, done); 194 | 195 | promise.then(null, function () { 196 | return sentinel; 197 | }).then(function (value) { 198 | assert.strictEqual(value, sentinel); 199 | semiDone(); 200 | }); 201 | 202 | promise.then(null, function () { 203 | throw sentinel2; 204 | }).then(null, function (reason) { 205 | assert.strictEqual(reason, sentinel2); 206 | semiDone(); 207 | }); 208 | 209 | promise.then(null, function () { 210 | return sentinel3; 211 | }).then(function (value) { 212 | assert.strictEqual(value, sentinel3); 213 | semiDone(); 214 | }); 215 | }); 216 | }); 217 | 218 | describe("`onRejected` handlers are called in the original order", function () { 219 | testRejected(dummy, function (promise, done) { 220 | var handler1 = sinon.spy(function handler1() {}); 221 | var handler2 = sinon.spy(function handler2() {}); 222 | var handler3 = sinon.spy(function handler3() {}); 223 | 224 | promise.then(null, handler1); 225 | promise.then(null, handler2); 226 | promise.then(null, handler3); 227 | 228 | promise.then(null, function () { 229 | sinon.assert.callOrder(handler1, handler2, handler3); 230 | done(); 231 | }); 232 | }); 233 | 234 | describe("even when one handler is added inside another handler", function () { 235 | testRejected(dummy, function (promise, done) { 236 | var handler1 = sinon.spy(function handler1() {}); 237 | var handler2 = sinon.spy(function handler2() {}); 238 | var handler3 = sinon.spy(function handler3() {}); 239 | 240 | promise.then(null, function () { 241 | handler1(); 242 | promise.then(null, handler3); 243 | }); 244 | promise.then(null, handler2); 245 | 246 | promise.then(null, function () { 247 | // Give implementations a bit of extra time to flush their internal queue, if necessary. 248 | setTimeout(function () { 249 | sinon.assert.callOrder(handler1, handler2, handler3); 250 | done(); 251 | }, 15); 252 | }); 253 | }); 254 | }); 255 | }); 256 | }); 257 | }); 258 | -------------------------------------------------------------------------------- /lib/tests/2.2.7.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var testFulfilled = require("./helpers/testThreeCases").testFulfilled; 5 | var testRejected = require("./helpers/testThreeCases").testRejected; 6 | var reasons = require("./helpers/reasons"); 7 | 8 | var adapter = global.adapter; 9 | var deferred = adapter.deferred; 10 | 11 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 12 | var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality 13 | var other = { other: "other" }; // a value we don't want to be strict equal to 14 | 15 | describe("2.2.7: `then` must return a promise: `promise2 = promise1.then(onFulfilled, onRejected)`", function () { 16 | specify("is a promise", function () { 17 | var promise1 = deferred().promise; 18 | var promise2 = promise1.then(); 19 | 20 | assert(typeof promise2 === "object" || typeof promise2 === "function"); 21 | assert.notStrictEqual(promise2, null); 22 | assert.strictEqual(typeof promise2.then, "function"); 23 | }); 24 | 25 | describe("2.2.7.1: If either `onFulfilled` or `onRejected` returns a value `x`, run the Promise Resolution " + 26 | "Procedure `[[Resolve]](promise2, x)`", function () { 27 | specify("see separate 3.3 tests", function () { }); 28 | }); 29 | 30 | describe("2.2.7.2: If either `onFulfilled` or `onRejected` throws an exception `e`, `promise2` must be rejected " + 31 | "with `e` as the reason.", function () { 32 | function testReason(expectedReason, stringRepresentation) { 33 | describe("The reason is " + stringRepresentation, function () { 34 | testFulfilled(dummy, function (promise1, done) { 35 | var promise2 = promise1.then(function onFulfilled() { 36 | throw expectedReason; 37 | }); 38 | 39 | promise2.then(null, function onPromise2Rejected(actualReason) { 40 | assert.strictEqual(actualReason, expectedReason); 41 | done(); 42 | }); 43 | }); 44 | testRejected(dummy, function (promise1, done) { 45 | var promise2 = promise1.then(null, function onRejected() { 46 | throw expectedReason; 47 | }); 48 | 49 | promise2.then(null, function onPromise2Rejected(actualReason) { 50 | assert.strictEqual(actualReason, expectedReason); 51 | done(); 52 | }); 53 | }); 54 | }); 55 | } 56 | 57 | Object.keys(reasons).forEach(function (stringRepresentation) { 58 | testReason(reasons[stringRepresentation](), stringRepresentation); 59 | }); 60 | }); 61 | 62 | describe("2.2.7.3: If `onFulfilled` is not a function and `promise1` is fulfilled, `promise2` must be fulfilled " + 63 | "with the same value.", function () { 64 | 65 | function testNonFunction(nonFunction, stringRepresentation) { 66 | describe("`onFulfilled` is " + stringRepresentation, function () { 67 | testFulfilled(sentinel, function (promise1, done) { 68 | var promise2 = promise1.then(nonFunction); 69 | 70 | promise2.then(function onPromise2Fulfilled(value) { 71 | assert.strictEqual(value, sentinel); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | } 77 | 78 | testNonFunction(undefined, "`undefined`"); 79 | testNonFunction(null, "`null`"); 80 | testNonFunction(false, "`false`"); 81 | testNonFunction(5, "`5`"); 82 | testNonFunction({}, "an object"); 83 | testNonFunction([function () { return other; }], "an array containing a function"); 84 | }); 85 | 86 | describe("2.2.7.4: If `onRejected` is not a function and `promise1` is rejected, `promise2` must be rejected " + 87 | "with the same reason.", function () { 88 | 89 | function testNonFunction(nonFunction, stringRepresentation) { 90 | describe("`onRejected` is " + stringRepresentation, function () { 91 | testRejected(sentinel, function (promise1, done) { 92 | var promise2 = promise1.then(null, nonFunction); 93 | 94 | promise2.then(null, function onPromise2Rejected(reason) { 95 | assert.strictEqual(reason, sentinel); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | } 101 | 102 | testNonFunction(undefined, "`undefined`"); 103 | testNonFunction(null, "`null`"); 104 | testNonFunction(false, "`false`"); 105 | testNonFunction(5, "`5`"); 106 | testNonFunction({}, "an object"); 107 | testNonFunction([function () { return other; }], "an array containing a function"); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /lib/tests/2.3.1.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | 5 | var adapter = global.adapter; 6 | var resolved = adapter.resolved; 7 | var rejected = adapter.rejected; 8 | 9 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 10 | 11 | describe("2.3.1: If `promise` and `x` refer to the same object, reject `promise` with a `TypeError' as the reason.", 12 | function () { 13 | specify("via return from a fulfilled promise", function (done) { 14 | var promise = resolved(dummy).then(function () { 15 | return promise; 16 | }); 17 | 18 | promise.then(null, function (reason) { 19 | assert(reason instanceof TypeError); 20 | done(); 21 | }); 22 | }); 23 | 24 | specify("via return from a rejected promise", function (done) { 25 | var promise = rejected(dummy).then(null, function () { 26 | return promise; 27 | }); 28 | 29 | promise.then(null, function (reason) { 30 | assert(reason instanceof TypeError); 31 | done(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /lib/tests/2.3.2.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | 5 | var adapter = global.adapter; 6 | var resolved = adapter.resolved; 7 | var rejected = adapter.rejected; 8 | var deferred = adapter.deferred; 9 | 10 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 11 | var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality 12 | 13 | function testPromiseResolution(xFactory, test) { 14 | specify("via return from a fulfilled promise", function (done) { 15 | var promise = resolved(dummy).then(function onBasePromiseFulfilled() { 16 | return xFactory(); 17 | }); 18 | 19 | test(promise, done); 20 | }); 21 | 22 | specify("via return from a rejected promise", function (done) { 23 | var promise = rejected(dummy).then(null, function onBasePromiseRejected() { 24 | return xFactory(); 25 | }); 26 | 27 | test(promise, done); 28 | }); 29 | } 30 | 31 | describe("2.3.2: If `x` is a promise, adopt its state", function () { 32 | describe("2.3.2.1: If `x` is pending, `promise` must remain pending until `x` is fulfilled or rejected.", 33 | function () { 34 | function xFactory() { 35 | return deferred().promise; 36 | } 37 | 38 | testPromiseResolution(xFactory, function (promise, done) { 39 | var wasFulfilled = false; 40 | var wasRejected = false; 41 | 42 | promise.then( 43 | function onPromiseFulfilled() { 44 | wasFulfilled = true; 45 | }, 46 | function onPromiseRejected() { 47 | wasRejected = true; 48 | } 49 | ); 50 | 51 | setTimeout(function () { 52 | assert.strictEqual(wasFulfilled, false); 53 | assert.strictEqual(wasRejected, false); 54 | done(); 55 | }, 100); 56 | }); 57 | }); 58 | 59 | describe("2.3.2.2: If/when `x` is fulfilled, fulfill `promise` with the same value.", function () { 60 | describe("`x` is already-fulfilled", function () { 61 | function xFactory() { 62 | return resolved(sentinel); 63 | } 64 | 65 | testPromiseResolution(xFactory, function (promise, done) { 66 | promise.then(function onPromiseFulfilled(value) { 67 | assert.strictEqual(value, sentinel); 68 | done(); 69 | }); 70 | }); 71 | }); 72 | 73 | describe("`x` is eventually-fulfilled", function () { 74 | var d = null; 75 | 76 | function xFactory() { 77 | d = deferred(); 78 | setTimeout(function () { 79 | d.resolve(sentinel); 80 | }, 50); 81 | return d.promise; 82 | } 83 | 84 | testPromiseResolution(xFactory, function (promise, done) { 85 | promise.then(function onPromiseFulfilled(value) { 86 | assert.strictEqual(value, sentinel); 87 | done(); 88 | }); 89 | }); 90 | }); 91 | }); 92 | 93 | describe("2.3.2.3: If/when `x` is rejected, reject `promise` with the same reason.", function () { 94 | describe("`x` is already-rejected", function () { 95 | function xFactory() { 96 | return rejected(sentinel); 97 | } 98 | 99 | testPromiseResolution(xFactory, function (promise, done) { 100 | promise.then(null, function onPromiseRejected(reason) { 101 | assert.strictEqual(reason, sentinel); 102 | done(); 103 | }); 104 | }); 105 | }); 106 | 107 | describe("`x` is eventually-rejected", function () { 108 | var d = null; 109 | 110 | function xFactory() { 111 | d = deferred(); 112 | setTimeout(function () { 113 | d.reject(sentinel); 114 | }, 50); 115 | return d.promise; 116 | } 117 | 118 | testPromiseResolution(xFactory, function (promise, done) { 119 | promise.then(null, function onPromiseRejected(reason) { 120 | assert.strictEqual(reason, sentinel); 121 | done(); 122 | }); 123 | }); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /lib/tests/2.3.3.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var thenables = require("./helpers/thenables"); 5 | var reasons = require("./helpers/reasons"); 6 | 7 | var adapter = global.adapter; 8 | var resolved = adapter.resolved; 9 | var rejected = adapter.rejected; 10 | var deferred = adapter.deferred; 11 | 12 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 13 | var sentinel = { sentinel: "sentinel" }; // a sentinel fulfillment value to test for with strict equality 14 | var other = { other: "other" }; // a value we don't want to be strict equal to 15 | var sentinelArray = [sentinel]; // a sentinel fulfillment value to test when we need an array 16 | 17 | function testPromiseResolution(xFactory, test) { 18 | specify("via return from a fulfilled promise", function (done) { 19 | var promise = resolved(dummy).then(function onBasePromiseFulfilled() { 20 | return xFactory(); 21 | }); 22 | 23 | test(promise, done); 24 | }); 25 | 26 | specify("via return from a rejected promise", function (done) { 27 | var promise = rejected(dummy).then(null, function onBasePromiseRejected() { 28 | return xFactory(); 29 | }); 30 | 31 | test(promise, done); 32 | }); 33 | } 34 | 35 | function testCallingResolvePromise(yFactory, stringRepresentation, test) { 36 | describe("`y` is " + stringRepresentation, function () { 37 | describe("`then` calls `resolvePromise` synchronously", function () { 38 | function xFactory() { 39 | return { 40 | then: function (resolvePromise) { 41 | resolvePromise(yFactory()); 42 | } 43 | }; 44 | } 45 | 46 | testPromiseResolution(xFactory, test); 47 | }); 48 | 49 | describe("`then` calls `resolvePromise` asynchronously", function () { 50 | function xFactory() { 51 | return { 52 | then: function (resolvePromise) { 53 | setTimeout(function () { 54 | resolvePromise(yFactory()); 55 | }, 0); 56 | } 57 | }; 58 | } 59 | 60 | testPromiseResolution(xFactory, test); 61 | }); 62 | }); 63 | } 64 | 65 | function testCallingRejectPromise(r, stringRepresentation, test) { 66 | describe("`r` is " + stringRepresentation, function () { 67 | describe("`then` calls `rejectPromise` synchronously", function () { 68 | function xFactory() { 69 | return { 70 | then: function (resolvePromise, rejectPromise) { 71 | rejectPromise(r); 72 | } 73 | }; 74 | } 75 | 76 | testPromiseResolution(xFactory, test); 77 | }); 78 | 79 | describe("`then` calls `rejectPromise` asynchronously", function () { 80 | function xFactory() { 81 | return { 82 | then: function (resolvePromise, rejectPromise) { 83 | setTimeout(function () { 84 | rejectPromise(r); 85 | }, 0); 86 | } 87 | }; 88 | } 89 | 90 | testPromiseResolution(xFactory, test); 91 | }); 92 | }); 93 | } 94 | 95 | function testCallingResolvePromiseFulfillsWith(yFactory, stringRepresentation, fulfillmentValue) { 96 | testCallingResolvePromise(yFactory, stringRepresentation, function (promise, done) { 97 | promise.then(function onPromiseFulfilled(value) { 98 | assert.strictEqual(value, fulfillmentValue); 99 | done(); 100 | }); 101 | }); 102 | } 103 | 104 | function testCallingResolvePromiseRejectsWith(yFactory, stringRepresentation, rejectionReason) { 105 | testCallingResolvePromise(yFactory, stringRepresentation, function (promise, done) { 106 | promise.then(null, function onPromiseRejected(reason) { 107 | assert.strictEqual(reason, rejectionReason); 108 | done(); 109 | }); 110 | }); 111 | } 112 | 113 | function testCallingRejectPromiseRejectsWith(reason, stringRepresentation) { 114 | testCallingRejectPromise(reason, stringRepresentation, function (promise, done) { 115 | promise.then(null, function onPromiseRejected(rejectionReason) { 116 | assert.strictEqual(rejectionReason, reason); 117 | done(); 118 | }); 119 | }); 120 | } 121 | 122 | describe("2.3.3: Otherwise, if `x` is an object or function,", function () { 123 | describe("2.3.3.1: Let `then` be `x.then`", function () { 124 | describe("`x` is an object with null prototype", function () { 125 | var numberOfTimesThenWasRetrieved = null; 126 | 127 | beforeEach(function () { 128 | numberOfTimesThenWasRetrieved = 0; 129 | }); 130 | 131 | function xFactory() { 132 | return Object.create(null, { 133 | then: { 134 | get: function () { 135 | ++numberOfTimesThenWasRetrieved; 136 | return function thenMethodForX(onFulfilled) { 137 | onFulfilled(); 138 | }; 139 | } 140 | } 141 | }); 142 | } 143 | 144 | testPromiseResolution(xFactory, function (promise, done) { 145 | promise.then(function () { 146 | assert.strictEqual(numberOfTimesThenWasRetrieved, 1); 147 | done(); 148 | }); 149 | }); 150 | }); 151 | 152 | describe("`x` is an object with normal Object.prototype", function () { 153 | var numberOfTimesThenWasRetrieved = null; 154 | 155 | beforeEach(function () { 156 | numberOfTimesThenWasRetrieved = 0; 157 | }); 158 | 159 | function xFactory() { 160 | return Object.create(Object.prototype, { 161 | then: { 162 | get: function () { 163 | ++numberOfTimesThenWasRetrieved; 164 | return function thenMethodForX(onFulfilled) { 165 | onFulfilled(); 166 | }; 167 | } 168 | } 169 | }); 170 | } 171 | 172 | testPromiseResolution(xFactory, function (promise, done) { 173 | promise.then(function () { 174 | assert.strictEqual(numberOfTimesThenWasRetrieved, 1); 175 | done(); 176 | }); 177 | }); 178 | }); 179 | 180 | describe("`x` is a function", function () { 181 | var numberOfTimesThenWasRetrieved = null; 182 | 183 | beforeEach(function () { 184 | numberOfTimesThenWasRetrieved = 0; 185 | }); 186 | 187 | function xFactory() { 188 | function x() { } 189 | 190 | Object.defineProperty(x, "then", { 191 | get: function () { 192 | ++numberOfTimesThenWasRetrieved; 193 | return function thenMethodForX(onFulfilled) { 194 | onFulfilled(); 195 | }; 196 | } 197 | }); 198 | 199 | return x; 200 | } 201 | 202 | testPromiseResolution(xFactory, function (promise, done) { 203 | promise.then(function () { 204 | assert.strictEqual(numberOfTimesThenWasRetrieved, 1); 205 | done(); 206 | }); 207 | }); 208 | }); 209 | }); 210 | 211 | describe("2.3.3.2: If retrieving the property `x.then` results in a thrown exception `e`, reject `promise` with " + 212 | "`e` as the reason.", function () { 213 | function testRejectionViaThrowingGetter(e, stringRepresentation) { 214 | function xFactory() { 215 | return Object.create(Object.prototype, { 216 | then: { 217 | get: function () { 218 | throw e; 219 | } 220 | } 221 | }); 222 | } 223 | 224 | describe("`e` is " + stringRepresentation, function () { 225 | testPromiseResolution(xFactory, function (promise, done) { 226 | promise.then(null, function (reason) { 227 | assert.strictEqual(reason, e); 228 | done(); 229 | }); 230 | }); 231 | }); 232 | } 233 | 234 | Object.keys(reasons).forEach(function (stringRepresentation) { 235 | testRejectionViaThrowingGetter(reasons[stringRepresentation], stringRepresentation); 236 | }); 237 | }); 238 | 239 | describe("2.3.3.3: If `then` is a function, call it with `x` as `this`, first argument `resolvePromise`, and " + 240 | "second argument `rejectPromise`", function () { 241 | describe("Calls with `x` as `this` and two function arguments", function () { 242 | function xFactory() { 243 | var x = { 244 | then: function (onFulfilled, onRejected) { 245 | assert.strictEqual(this, x); 246 | assert.strictEqual(typeof onFulfilled, "function"); 247 | assert.strictEqual(typeof onRejected, "function"); 248 | onFulfilled(); 249 | } 250 | }; 251 | return x; 252 | } 253 | 254 | testPromiseResolution(xFactory, function (promise, done) { 255 | promise.then(function () { 256 | done(); 257 | }); 258 | }); 259 | }); 260 | 261 | describe("Uses the original value of `then`", function () { 262 | var numberOfTimesThenWasRetrieved = null; 263 | 264 | beforeEach(function () { 265 | numberOfTimesThenWasRetrieved = 0; 266 | }); 267 | 268 | function xFactory() { 269 | return Object.create(Object.prototype, { 270 | then: { 271 | get: function () { 272 | if (numberOfTimesThenWasRetrieved === 0) { 273 | return function (onFulfilled) { 274 | onFulfilled(); 275 | }; 276 | } 277 | return null; 278 | } 279 | } 280 | }); 281 | } 282 | 283 | testPromiseResolution(xFactory, function (promise, done) { 284 | promise.then(function () { 285 | done(); 286 | }); 287 | }); 288 | }); 289 | 290 | describe("2.3.3.3.1: If/when `resolvePromise` is called with value `y`, run `[[Resolve]](promise, y)`", 291 | function () { 292 | describe("`y` is not a thenable", function () { 293 | testCallingResolvePromiseFulfillsWith(function () { return undefined; }, "`undefined`", undefined); 294 | testCallingResolvePromiseFulfillsWith(function () { return null; }, "`null`", null); 295 | testCallingResolvePromiseFulfillsWith(function () { return false; }, "`false`", false); 296 | testCallingResolvePromiseFulfillsWith(function () { return 5; }, "`5`", 5); 297 | testCallingResolvePromiseFulfillsWith(function () { return sentinel; }, "an object", sentinel); 298 | testCallingResolvePromiseFulfillsWith(function () { return sentinelArray; }, "an array", sentinelArray); 299 | }); 300 | 301 | describe("`y` is a thenable", function () { 302 | Object.keys(thenables.fulfilled).forEach(function (stringRepresentation) { 303 | function yFactory() { 304 | return thenables.fulfilled[stringRepresentation](sentinel); 305 | } 306 | 307 | testCallingResolvePromiseFulfillsWith(yFactory, stringRepresentation, sentinel); 308 | }); 309 | 310 | Object.keys(thenables.rejected).forEach(function (stringRepresentation) { 311 | function yFactory() { 312 | return thenables.rejected[stringRepresentation](sentinel); 313 | } 314 | 315 | testCallingResolvePromiseRejectsWith(yFactory, stringRepresentation, sentinel); 316 | }); 317 | }); 318 | 319 | describe("`y` is a thenable for a thenable", function () { 320 | Object.keys(thenables.fulfilled).forEach(function (outerStringRepresentation) { 321 | var outerThenableFactory = thenables.fulfilled[outerStringRepresentation]; 322 | 323 | Object.keys(thenables.fulfilled).forEach(function (innerStringRepresentation) { 324 | var innerThenableFactory = thenables.fulfilled[innerStringRepresentation]; 325 | 326 | var stringRepresentation = outerStringRepresentation + " for " + innerStringRepresentation; 327 | 328 | function yFactory() { 329 | return outerThenableFactory(innerThenableFactory(sentinel)); 330 | } 331 | 332 | testCallingResolvePromiseFulfillsWith(yFactory, stringRepresentation, sentinel); 333 | }); 334 | 335 | Object.keys(thenables.rejected).forEach(function (innerStringRepresentation) { 336 | var innerThenableFactory = thenables.rejected[innerStringRepresentation]; 337 | 338 | var stringRepresentation = outerStringRepresentation + " for " + innerStringRepresentation; 339 | 340 | function yFactory() { 341 | return outerThenableFactory(innerThenableFactory(sentinel)); 342 | } 343 | 344 | testCallingResolvePromiseRejectsWith(yFactory, stringRepresentation, sentinel); 345 | }); 346 | }); 347 | }); 348 | }); 349 | 350 | describe("2.3.3.3.2: If/when `rejectPromise` is called with reason `r`, reject `promise` with `r`", 351 | function () { 352 | Object.keys(reasons).forEach(function (stringRepresentation) { 353 | testCallingRejectPromiseRejectsWith(reasons[stringRepresentation](), stringRepresentation); 354 | }); 355 | }); 356 | 357 | describe("2.3.3.3.3: If both `resolvePromise` and `rejectPromise` are called, or multiple calls to the same " + 358 | "argument are made, the first call takes precedence, and any further calls are ignored.", 359 | function () { 360 | describe("calling `resolvePromise` then `rejectPromise`, both synchronously", function () { 361 | function xFactory() { 362 | return { 363 | then: function (resolvePromise, rejectPromise) { 364 | resolvePromise(sentinel); 365 | rejectPromise(other); 366 | } 367 | }; 368 | } 369 | 370 | testPromiseResolution(xFactory, function (promise, done) { 371 | promise.then(function (value) { 372 | assert.strictEqual(value, sentinel); 373 | done(); 374 | }); 375 | }); 376 | }); 377 | 378 | describe("calling `resolvePromise` synchronously then `rejectPromise` asynchronously", function () { 379 | function xFactory() { 380 | return { 381 | then: function (resolvePromise, rejectPromise) { 382 | resolvePromise(sentinel); 383 | 384 | setTimeout(function () { 385 | rejectPromise(other); 386 | }, 0); 387 | } 388 | }; 389 | } 390 | 391 | testPromiseResolution(xFactory, function (promise, done) { 392 | promise.then(function (value) { 393 | assert.strictEqual(value, sentinel); 394 | done(); 395 | }); 396 | }); 397 | }); 398 | 399 | describe("calling `resolvePromise` then `rejectPromise`, both asynchronously", function () { 400 | function xFactory() { 401 | return { 402 | then: function (resolvePromise, rejectPromise) { 403 | setTimeout(function () { 404 | resolvePromise(sentinel); 405 | }, 0); 406 | 407 | setTimeout(function () { 408 | rejectPromise(other); 409 | }, 0); 410 | } 411 | }; 412 | } 413 | 414 | testPromiseResolution(xFactory, function (promise, done) { 415 | promise.then(function (value) { 416 | assert.strictEqual(value, sentinel); 417 | done(); 418 | }); 419 | }); 420 | }); 421 | 422 | describe("calling `resolvePromise` with an asynchronously-fulfilled promise, then calling " + 423 | "`rejectPromise`, both synchronously", function () { 424 | function xFactory() { 425 | var d = deferred(); 426 | setTimeout(function () { 427 | d.resolve(sentinel); 428 | }, 50); 429 | 430 | return { 431 | then: function (resolvePromise, rejectPromise) { 432 | resolvePromise(d.promise); 433 | rejectPromise(other); 434 | } 435 | }; 436 | } 437 | 438 | testPromiseResolution(xFactory, function (promise, done) { 439 | promise.then(function (value) { 440 | assert.strictEqual(value, sentinel); 441 | done(); 442 | }); 443 | }); 444 | }); 445 | 446 | describe("calling `resolvePromise` with an asynchronously-rejected promise, then calling " + 447 | "`rejectPromise`, both synchronously", function () { 448 | function xFactory() { 449 | var d = deferred(); 450 | setTimeout(function () { 451 | d.reject(sentinel); 452 | }, 50); 453 | 454 | return { 455 | then: function (resolvePromise, rejectPromise) { 456 | resolvePromise(d.promise); 457 | rejectPromise(other); 458 | } 459 | }; 460 | } 461 | 462 | testPromiseResolution(xFactory, function (promise, done) { 463 | promise.then(null, function (reason) { 464 | assert.strictEqual(reason, sentinel); 465 | done(); 466 | }); 467 | }); 468 | }); 469 | 470 | describe("calling `rejectPromise` then `resolvePromise`, both synchronously", function () { 471 | function xFactory() { 472 | return { 473 | then: function (resolvePromise, rejectPromise) { 474 | rejectPromise(sentinel); 475 | resolvePromise(other); 476 | } 477 | }; 478 | } 479 | 480 | testPromiseResolution(xFactory, function (promise, done) { 481 | promise.then(null, function (reason) { 482 | assert.strictEqual(reason, sentinel); 483 | done(); 484 | }); 485 | }); 486 | }); 487 | 488 | describe("calling `rejectPromise` synchronously then `resolvePromise` asynchronously", function () { 489 | function xFactory() { 490 | return { 491 | then: function (resolvePromise, rejectPromise) { 492 | rejectPromise(sentinel); 493 | 494 | setTimeout(function () { 495 | resolvePromise(other); 496 | }, 0); 497 | } 498 | }; 499 | } 500 | 501 | testPromiseResolution(xFactory, function (promise, done) { 502 | promise.then(null, function (reason) { 503 | assert.strictEqual(reason, sentinel); 504 | done(); 505 | }); 506 | }); 507 | }); 508 | 509 | describe("calling `rejectPromise` then `resolvePromise`, both asynchronously", function () { 510 | function xFactory() { 511 | return { 512 | then: function (resolvePromise, rejectPromise) { 513 | setTimeout(function () { 514 | rejectPromise(sentinel); 515 | }, 0); 516 | 517 | setTimeout(function () { 518 | resolvePromise(other); 519 | }, 0); 520 | } 521 | }; 522 | } 523 | 524 | testPromiseResolution(xFactory, function (promise, done) { 525 | promise.then(null, function (reason) { 526 | assert.strictEqual(reason, sentinel); 527 | done(); 528 | }); 529 | }); 530 | }); 531 | 532 | describe("calling `resolvePromise` twice synchronously", function () { 533 | function xFactory() { 534 | return { 535 | then: function (resolvePromise) { 536 | resolvePromise(sentinel); 537 | resolvePromise(other); 538 | } 539 | }; 540 | } 541 | 542 | testPromiseResolution(xFactory, function (promise, done) { 543 | promise.then(function (value) { 544 | assert.strictEqual(value, sentinel); 545 | done(); 546 | }); 547 | }); 548 | }); 549 | 550 | describe("calling `resolvePromise` twice, first synchronously then asynchronously", function () { 551 | function xFactory() { 552 | return { 553 | then: function (resolvePromise) { 554 | resolvePromise(sentinel); 555 | 556 | setTimeout(function () { 557 | resolvePromise(other); 558 | }, 0); 559 | } 560 | }; 561 | } 562 | 563 | testPromiseResolution(xFactory, function (promise, done) { 564 | promise.then(function (value) { 565 | assert.strictEqual(value, sentinel); 566 | done(); 567 | }); 568 | }); 569 | }); 570 | 571 | describe("calling `resolvePromise` twice, both times asynchronously", function () { 572 | function xFactory() { 573 | return { 574 | then: function (resolvePromise) { 575 | setTimeout(function () { 576 | resolvePromise(sentinel); 577 | }, 0); 578 | 579 | setTimeout(function () { 580 | resolvePromise(other); 581 | }, 0); 582 | } 583 | }; 584 | } 585 | 586 | testPromiseResolution(xFactory, function (promise, done) { 587 | promise.then(function (value) { 588 | assert.strictEqual(value, sentinel); 589 | done(); 590 | }); 591 | }); 592 | }); 593 | 594 | describe("calling `resolvePromise` with an asynchronously-fulfilled promise, then calling it again, both " + 595 | "times synchronously", function () { 596 | function xFactory() { 597 | var d = deferred(); 598 | setTimeout(function () { 599 | d.resolve(sentinel); 600 | }, 50); 601 | 602 | return { 603 | then: function (resolvePromise) { 604 | resolvePromise(d.promise); 605 | resolvePromise(other); 606 | } 607 | }; 608 | } 609 | 610 | testPromiseResolution(xFactory, function (promise, done) { 611 | promise.then(function (value) { 612 | assert.strictEqual(value, sentinel); 613 | done(); 614 | }); 615 | }); 616 | }); 617 | 618 | describe("calling `resolvePromise` with an asynchronously-rejected promise, then calling it again, both " + 619 | "times synchronously", function () { 620 | function xFactory() { 621 | var d = deferred(); 622 | setTimeout(function () { 623 | d.reject(sentinel); 624 | }, 50); 625 | 626 | return { 627 | then: function (resolvePromise) { 628 | resolvePromise(d.promise); 629 | resolvePromise(other); 630 | } 631 | }; 632 | } 633 | 634 | testPromiseResolution(xFactory, function (promise, done) { 635 | promise.then(null, function (reason) { 636 | assert.strictEqual(reason, sentinel); 637 | done(); 638 | }); 639 | }); 640 | }); 641 | 642 | describe("calling `rejectPromise` twice synchronously", function () { 643 | function xFactory() { 644 | return { 645 | then: function (resolvePromise, rejectPromise) { 646 | rejectPromise(sentinel); 647 | rejectPromise(other); 648 | } 649 | }; 650 | } 651 | 652 | testPromiseResolution(xFactory, function (promise, done) { 653 | promise.then(null, function (reason) { 654 | assert.strictEqual(reason, sentinel); 655 | done(); 656 | }); 657 | }); 658 | }); 659 | 660 | describe("calling `rejectPromise` twice, first synchronously then asynchronously", function () { 661 | function xFactory() { 662 | return { 663 | then: function (resolvePromise, rejectPromise) { 664 | rejectPromise(sentinel); 665 | 666 | setTimeout(function () { 667 | rejectPromise(other); 668 | }, 0); 669 | } 670 | }; 671 | } 672 | 673 | testPromiseResolution(xFactory, function (promise, done) { 674 | promise.then(null, function (reason) { 675 | assert.strictEqual(reason, sentinel); 676 | done(); 677 | }); 678 | }); 679 | }); 680 | 681 | describe("calling `rejectPromise` twice, both times asynchronously", function () { 682 | function xFactory() { 683 | return { 684 | then: function (resolvePromise, rejectPromise) { 685 | setTimeout(function () { 686 | rejectPromise(sentinel); 687 | }, 0); 688 | 689 | setTimeout(function () { 690 | rejectPromise(other); 691 | }, 0); 692 | } 693 | }; 694 | } 695 | 696 | testPromiseResolution(xFactory, function (promise, done) { 697 | promise.then(null, function (reason) { 698 | assert.strictEqual(reason, sentinel); 699 | done(); 700 | }); 701 | }); 702 | }); 703 | 704 | describe("saving and abusing `resolvePromise` and `rejectPromise`", function () { 705 | var savedResolvePromise, savedRejectPromise; 706 | 707 | function xFactory() { 708 | return { 709 | then: function (resolvePromise, rejectPromise) { 710 | savedResolvePromise = resolvePromise; 711 | savedRejectPromise = rejectPromise; 712 | } 713 | }; 714 | } 715 | 716 | beforeEach(function () { 717 | savedResolvePromise = null; 718 | savedRejectPromise = null; 719 | }); 720 | 721 | testPromiseResolution(xFactory, function (promise, done) { 722 | var timesFulfilled = 0; 723 | var timesRejected = 0; 724 | 725 | promise.then( 726 | function () { 727 | ++timesFulfilled; 728 | }, 729 | function () { 730 | ++timesRejected; 731 | } 732 | ); 733 | 734 | if (savedResolvePromise && savedRejectPromise) { 735 | savedResolvePromise(dummy); 736 | savedResolvePromise(dummy); 737 | savedRejectPromise(dummy); 738 | savedRejectPromise(dummy); 739 | } 740 | 741 | setTimeout(function () { 742 | savedResolvePromise(dummy); 743 | savedResolvePromise(dummy); 744 | savedRejectPromise(dummy); 745 | savedRejectPromise(dummy); 746 | }, 50); 747 | 748 | setTimeout(function () { 749 | assert.strictEqual(timesFulfilled, 1); 750 | assert.strictEqual(timesRejected, 0); 751 | done(); 752 | }, 100); 753 | }); 754 | }); 755 | }); 756 | 757 | describe("2.3.3.3.4: If calling `then` throws an exception `e`,", function () { 758 | describe("2.3.3.3.4.1: If `resolvePromise` or `rejectPromise` have been called, ignore it.", function () { 759 | describe("`resolvePromise` was called with a non-thenable", function () { 760 | function xFactory() { 761 | return { 762 | then: function (resolvePromise) { 763 | resolvePromise(sentinel); 764 | throw other; 765 | } 766 | }; 767 | } 768 | 769 | testPromiseResolution(xFactory, function (promise, done) { 770 | promise.then(function (value) { 771 | assert.strictEqual(value, sentinel); 772 | done(); 773 | }); 774 | }); 775 | }); 776 | 777 | describe("`resolvePromise` was called with an asynchronously-fulfilled promise", function () { 778 | function xFactory() { 779 | var d = deferred(); 780 | setTimeout(function () { 781 | d.resolve(sentinel); 782 | }, 50); 783 | 784 | return { 785 | then: function (resolvePromise) { 786 | resolvePromise(d.promise); 787 | throw other; 788 | } 789 | }; 790 | } 791 | 792 | testPromiseResolution(xFactory, function (promise, done) { 793 | promise.then(function (value) { 794 | assert.strictEqual(value, sentinel); 795 | done(); 796 | }); 797 | }); 798 | }); 799 | 800 | describe("`resolvePromise` was called with an asynchronously-rejected promise", function () { 801 | function xFactory() { 802 | var d = deferred(); 803 | setTimeout(function () { 804 | d.reject(sentinel); 805 | }, 50); 806 | 807 | return { 808 | then: function (resolvePromise) { 809 | resolvePromise(d.promise); 810 | throw other; 811 | } 812 | }; 813 | } 814 | 815 | testPromiseResolution(xFactory, function (promise, done) { 816 | promise.then(null, function (reason) { 817 | assert.strictEqual(reason, sentinel); 818 | done(); 819 | }); 820 | }); 821 | }); 822 | 823 | describe("`rejectPromise` was called", function () { 824 | function xFactory() { 825 | return { 826 | then: function (resolvePromise, rejectPromise) { 827 | rejectPromise(sentinel); 828 | throw other; 829 | } 830 | }; 831 | } 832 | 833 | testPromiseResolution(xFactory, function (promise, done) { 834 | promise.then(null, function (reason) { 835 | assert.strictEqual(reason, sentinel); 836 | done(); 837 | }); 838 | }); 839 | }); 840 | 841 | describe("`resolvePromise` then `rejectPromise` were called", function () { 842 | function xFactory() { 843 | return { 844 | then: function (resolvePromise, rejectPromise) { 845 | resolvePromise(sentinel); 846 | rejectPromise(other); 847 | throw other; 848 | } 849 | }; 850 | } 851 | 852 | testPromiseResolution(xFactory, function (promise, done) { 853 | promise.then(function (value) { 854 | assert.strictEqual(value, sentinel); 855 | done(); 856 | }); 857 | }); 858 | }); 859 | 860 | describe("`rejectPromise` then `resolvePromise` were called", function () { 861 | function xFactory() { 862 | return { 863 | then: function (resolvePromise, rejectPromise) { 864 | rejectPromise(sentinel); 865 | resolvePromise(other); 866 | throw other; 867 | } 868 | }; 869 | } 870 | 871 | testPromiseResolution(xFactory, function (promise, done) { 872 | promise.then(null, function (reason) { 873 | assert.strictEqual(reason, sentinel); 874 | done(); 875 | }); 876 | }); 877 | }); 878 | }); 879 | 880 | describe("2.3.3.3.4.2: Otherwise, reject `promise` with `e` as the reason.", function () { 881 | describe("straightforward case", function () { 882 | function xFactory() { 883 | return { 884 | then: function () { 885 | throw sentinel; 886 | } 887 | }; 888 | } 889 | 890 | testPromiseResolution(xFactory, function (promise, done) { 891 | promise.then(null, function (reason) { 892 | assert.strictEqual(reason, sentinel); 893 | done(); 894 | }); 895 | }); 896 | }); 897 | 898 | describe("`resolvePromise` is called asynchronously before the `throw`", function () { 899 | function xFactory() { 900 | return { 901 | then: function (resolvePromise) { 902 | setTimeout(function () { 903 | resolvePromise(other); 904 | }, 0); 905 | throw sentinel; 906 | } 907 | }; 908 | } 909 | 910 | testPromiseResolution(xFactory, function (promise, done) { 911 | promise.then(null, function (reason) { 912 | assert.strictEqual(reason, sentinel); 913 | done(); 914 | }); 915 | }); 916 | }); 917 | 918 | describe("`rejectPromise` is called asynchronously before the `throw`", function () { 919 | function xFactory() { 920 | return { 921 | then: function (resolvePromise, rejectPromise) { 922 | setTimeout(function () { 923 | rejectPromise(other); 924 | }, 0); 925 | throw sentinel; 926 | } 927 | }; 928 | } 929 | 930 | testPromiseResolution(xFactory, function (promise, done) { 931 | promise.then(null, function (reason) { 932 | assert.strictEqual(reason, sentinel); 933 | done(); 934 | }); 935 | }); 936 | }); 937 | }); 938 | }); 939 | }); 940 | 941 | describe("2.3.3.4: If `then` is not a function, fulfill promise with `x`", function () { 942 | function testFulfillViaNonFunction(then, stringRepresentation) { 943 | var x = null; 944 | 945 | beforeEach(function () { 946 | x = { then: then }; 947 | }); 948 | 949 | function xFactory() { 950 | return x; 951 | } 952 | 953 | describe("`then` is " + stringRepresentation, function () { 954 | testPromiseResolution(xFactory, function (promise, done) { 955 | promise.then(function (value) { 956 | assert.strictEqual(value, x); 957 | done(); 958 | }); 959 | }); 960 | }); 961 | } 962 | 963 | testFulfillViaNonFunction(5, "`5`"); 964 | testFulfillViaNonFunction({}, "an object"); 965 | testFulfillViaNonFunction([function () { }], "an array containing a function"); 966 | testFulfillViaNonFunction(/a-b/i, "a regular expression"); 967 | testFulfillViaNonFunction(Object.create(Function.prototype), "an object inheriting from `Function.prototype`"); 968 | }); 969 | }); 970 | -------------------------------------------------------------------------------- /lib/tests/2.3.4.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var testFulfilled = require("./helpers/testThreeCases").testFulfilled; 5 | var testRejected = require("./helpers/testThreeCases").testRejected; 6 | 7 | var dummy = { dummy: "dummy" }; // we fulfill or reject with this when we don't intend to test against it 8 | 9 | describe("2.3.4: If `x` is not an object or function, fulfill `promise` with `x`", function () { 10 | function testValue(expectedValue, stringRepresentation, beforeEachHook, afterEachHook) { 11 | describe("The value is " + stringRepresentation, function () { 12 | if (typeof beforeEachHook === "function") { 13 | beforeEach(beforeEachHook); 14 | } 15 | if (typeof afterEachHook === "function") { 16 | afterEach(afterEachHook); 17 | } 18 | 19 | testFulfilled(dummy, function (promise1, done) { 20 | var promise2 = promise1.then(function onFulfilled() { 21 | return expectedValue; 22 | }); 23 | 24 | promise2.then(function onPromise2Fulfilled(actualValue) { 25 | assert.strictEqual(actualValue, expectedValue); 26 | done(); 27 | }); 28 | }); 29 | testRejected(dummy, function (promise1, done) { 30 | var promise2 = promise1.then(null, function onRejected() { 31 | return expectedValue; 32 | }); 33 | 34 | promise2.then(function onPromise2Fulfilled(actualValue) { 35 | assert.strictEqual(actualValue, expectedValue); 36 | done(); 37 | }); 38 | }); 39 | }); 40 | } 41 | 42 | testValue(undefined, "`undefined`"); 43 | testValue(null, "`null`"); 44 | testValue(false, "`false`"); 45 | testValue(true, "`true`"); 46 | testValue(0, "`0`"); 47 | 48 | testValue( 49 | true, 50 | "`true` with `Boolean.prototype` modified to have a `then` method", 51 | function () { 52 | Boolean.prototype.then = function () {}; 53 | }, 54 | function () { 55 | delete Boolean.prototype.then; 56 | } 57 | ); 58 | 59 | testValue( 60 | 1, 61 | "`1` with `Number.prototype` modified to have a `then` method", 62 | function () { 63 | Number.prototype.then = function () {}; 64 | }, 65 | function () { 66 | delete Number.prototype.then; 67 | } 68 | ); 69 | }); 70 | -------------------------------------------------------------------------------- /lib/tests/helpers/reasons.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // This module exports some valid rejection reason factories, keyed by human-readable versions of their names. 4 | 5 | var adapter = global.adapter; 6 | var resolved = adapter.resolved; 7 | var rejected = adapter.rejected; 8 | 9 | var dummy = { dummy: "dummy" }; 10 | 11 | exports["`undefined`"] = function () { 12 | return undefined; 13 | }; 14 | 15 | exports["`null`"] = function () { 16 | return null; 17 | }; 18 | 19 | exports["`false`"] = function () { 20 | return false; 21 | }; 22 | 23 | exports["`0`"] = function () { 24 | return 0; 25 | }; 26 | 27 | exports["an error"] = function () { 28 | return new Error(); 29 | }; 30 | 31 | exports["an error without a stack"] = function () { 32 | var error = new Error(); 33 | delete error.stack; 34 | 35 | return error; 36 | }; 37 | 38 | exports["a date"] = function () { 39 | return new Date(); 40 | }; 41 | 42 | exports["an object"] = function () { 43 | return {}; 44 | }; 45 | 46 | exports["an always-pending thenable"] = function () { 47 | return { then: function () { } }; 48 | }; 49 | 50 | exports["a fulfilled promise"] = function () { 51 | return resolved(dummy); 52 | }; 53 | 54 | exports["a rejected promise"] = function () { 55 | return rejected(dummy); 56 | }; 57 | -------------------------------------------------------------------------------- /lib/tests/helpers/testThreeCases.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var adapter = global.adapter; 4 | var resolved = adapter.resolved; 5 | var rejected = adapter.rejected; 6 | var deferred = adapter.deferred; 7 | 8 | exports.testFulfilled = function (value, test) { 9 | specify("already-fulfilled", function (done) { 10 | test(resolved(value), done); 11 | }); 12 | 13 | specify("immediately-fulfilled", function (done) { 14 | var d = deferred(); 15 | test(d.promise, done); 16 | d.resolve(value); 17 | }); 18 | 19 | specify("eventually-fulfilled", function (done) { 20 | var d = deferred(); 21 | test(d.promise, done); 22 | setTimeout(function () { 23 | d.resolve(value); 24 | }, 50); 25 | }); 26 | }; 27 | 28 | exports.testRejected = function (reason, test) { 29 | specify("already-rejected", function (done) { 30 | test(rejected(reason), done); 31 | }); 32 | 33 | specify("immediately-rejected", function (done) { 34 | var d = deferred(); 35 | test(d.promise, done); 36 | d.reject(reason); 37 | }); 38 | 39 | specify("eventually-rejected", function (done) { 40 | var d = deferred(); 41 | test(d.promise, done); 42 | setTimeout(function () { 43 | d.reject(reason); 44 | }, 50); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /lib/tests/helpers/thenables.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var adapter = global.adapter; 4 | var resolved = adapter.resolved; 5 | var rejected = adapter.rejected; 6 | var deferred = adapter.deferred; 7 | 8 | var other = { other: "other" }; // a value we don't want to be strict equal to 9 | 10 | exports.fulfilled = { 11 | "a synchronously-fulfilled custom thenable": function (value) { 12 | return { 13 | then: function (onFulfilled) { 14 | onFulfilled(value); 15 | } 16 | }; 17 | }, 18 | 19 | "an asynchronously-fulfilled custom thenable": function (value) { 20 | return { 21 | then: function (onFulfilled) { 22 | setTimeout(function () { 23 | onFulfilled(value); 24 | }, 0); 25 | } 26 | }; 27 | }, 28 | 29 | "a synchronously-fulfilled one-time thenable": function (value) { 30 | var numberOfTimesThenRetrieved = 0; 31 | return Object.create(null, { 32 | then: { 33 | get: function () { 34 | if (numberOfTimesThenRetrieved === 0) { 35 | ++numberOfTimesThenRetrieved; 36 | return function (onFulfilled) { 37 | onFulfilled(value); 38 | }; 39 | } 40 | return null; 41 | } 42 | } 43 | }); 44 | }, 45 | 46 | "a thenable that tries to fulfill twice": function (value) { 47 | return { 48 | then: function (onFulfilled) { 49 | onFulfilled(value); 50 | onFulfilled(other); 51 | } 52 | }; 53 | }, 54 | 55 | "a thenable that fulfills but then throws": function (value) { 56 | return { 57 | then: function (onFulfilled) { 58 | onFulfilled(value); 59 | throw other; 60 | } 61 | }; 62 | }, 63 | 64 | "an already-fulfilled promise": function (value) { 65 | return resolved(value); 66 | }, 67 | 68 | "an eventually-fulfilled promise": function (value) { 69 | var d = deferred(); 70 | setTimeout(function () { 71 | d.resolve(value); 72 | }, 50); 73 | return d.promise; 74 | } 75 | }; 76 | 77 | exports.rejected = { 78 | "a synchronously-rejected custom thenable": function (reason) { 79 | return { 80 | then: function (onFulfilled, onRejected) { 81 | onRejected(reason); 82 | } 83 | }; 84 | }, 85 | 86 | "an asynchronously-rejected custom thenable": function (reason) { 87 | return { 88 | then: function (onFulfilled, onRejected) { 89 | setTimeout(function () { 90 | onRejected(reason); 91 | }, 0); 92 | } 93 | }; 94 | }, 95 | 96 | "a synchronously-rejected one-time thenable": function (reason) { 97 | var numberOfTimesThenRetrieved = 0; 98 | return Object.create(null, { 99 | then: { 100 | get: function () { 101 | if (numberOfTimesThenRetrieved === 0) { 102 | ++numberOfTimesThenRetrieved; 103 | return function (onFulfilled, onRejected) { 104 | onRejected(reason); 105 | }; 106 | } 107 | return null; 108 | } 109 | } 110 | }); 111 | }, 112 | 113 | "a thenable that immediately throws in `then`": function (reason) { 114 | return { 115 | then: function () { 116 | throw reason; 117 | } 118 | }; 119 | }, 120 | 121 | "an object with a throwing `then` accessor": function (reason) { 122 | return Object.create(null, { 123 | then: { 124 | get: function () { 125 | throw reason; 126 | } 127 | } 128 | }); 129 | }, 130 | 131 | "an already-rejected promise": function (reason) { 132 | return rejected(reason); 133 | }, 134 | 135 | "an eventually-rejected promise": function (reason) { 136 | var d = deferred(); 137 | setTimeout(function () { 138 | d.reject(reason); 139 | }, 50); 140 | return d.promise; 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promises-aplus-tests", 3 | "description": "Compliance test suite for Promises/A+", 4 | "keywords": ["promises", "promises-aplus"], 5 | "version": "2.1.2", 6 | "implements": ["Promises/A+ 1.1.0"], 7 | "author": "Domenic Denicola (https://domenic.me)", 8 | "license": "WTFPL", 9 | "repository": "promises-aplus/promises-tests", 10 | "main": "lib/programmaticRunner.js", 11 | "bin": "lib/cli.js", 12 | "files": [ 13 | "lib/" 14 | ], 15 | "scripts": { 16 | "lint": "jshint lib", 17 | "test": "mocha", 18 | "prepublish": "node ./scripts/generateTestFiles.js" 19 | }, 20 | "dependencies": { 21 | "mocha": "^2.5.3", 22 | "sinon": "^1.10.3", 23 | "underscore": "~1.8.3" 24 | }, 25 | "devDependencies": { 26 | "jshint": "^2.9.2" 27 | }, 28 | "browser": { 29 | "mocha": false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/generateTestFiles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var fs = require("fs"); 3 | var path = require("path"); 4 | 5 | var testsDir = path.resolve(__dirname, "../lib/tests"); 6 | var testDirFiles = fs.readdirSync(testsDir); 7 | 8 | var outFile = fs.createWriteStream(path.resolve(__dirname, "../lib/testFiles.js"), { encoding: "utf-8" }); 9 | 10 | outFile.write("\"use strict\";\n"); 11 | 12 | testDirFiles.forEach(function (file) { 13 | if (path.extname(file) !== ".js") { 14 | return; 15 | } 16 | 17 | outFile.write("require(\"./"); 18 | outFile.write("tests/" + path.basename(file, ".js")); 19 | outFile.write("\");\n"); 20 | }); 21 | 22 | outFile.end(function (err) { 23 | if (err) { 24 | throw err; 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /test/getMochaOptsTest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var getMochaOpts = require("../lib/getMochaOpts"); 5 | 6 | describe("getMochaOpts", function() { 7 | function test(argsString, expectedOpts) { 8 | var opts = getMochaOpts(argsString.split(" ")); 9 | assert.deepEqual(opts, expectedOpts); 10 | } 11 | 12 | it("parses the provided options to an object", function () { 13 | test("--reporter spec --ui bdd", { 14 | reporter: "spec", 15 | ui: "bdd" 16 | }); 17 | }); 18 | 19 | it("sets the value for no-arg options to true", function () { 20 | test("--bail --ui bdd", { 21 | bail: true, 22 | ui: "bdd" 23 | }); 24 | }); 25 | 26 | it("supports no-arg options as the last option", function () { 27 | test("--reporter spec --bail", { 28 | reporter: "spec", 29 | bail: true 30 | }); 31 | }); 32 | }); 33 | --------------------------------------------------------------------------------