Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"use strict"; | ||
|
||
var fake = require("./fake"); | ||
var isRestorable = require("./util/core/is-restorable"); | ||
|
||
var STATUS_PENDING = "pending"; | ||
var STATUS_FULFILLED = "fulfilled"; | ||
var STATUS_REJECTED = "rejected"; | ||
|
||
/** | ||
* Returns a fake for a given function or undefined. If no functino is given, a | ||
* new fake is returned. If the given function is already a fake, it is | ||
* returned as is. Otherwise the given function is wrapped in a new fake. | ||
* | ||
* @param {Function} [executor] The optional executor function. | ||
* @returns {Function} | ||
*/ | ||
function getFakeExecutor(executor) { | ||
if (isRestorable(executor)) { | ||
return executor; | ||
} | ||
if (executor) { | ||
return fake(executor); | ||
} | ||
return fake(); | ||
} | ||
|
||
/** | ||
* Returns a new promise that exposes it's internal `status`, `resolvedValue` | ||
* and `rejectedValue` and can be resolved or rejected from the outside by | ||
* calling `resolve(value)` or `reject(reason)`. | ||
* | ||
* @param {Function} [executor] The optional executor function. | ||
* @returns {Promise} | ||
*/ | ||
function promise(executor) { | ||
var fakeExecutor = getFakeExecutor(executor); | ||
var sinonPromise = new Promise(fakeExecutor); | ||
|
||
sinonPromise.status = STATUS_PENDING; | ||
sinonPromise | ||
.then(function (value) { | ||
sinonPromise.status = STATUS_FULFILLED; | ||
sinonPromise.resolvedValue = value; | ||
}) | ||
.catch(function (reason) { | ||
sinonPromise.status = STATUS_REJECTED; | ||
sinonPromise.rejectedValue = reason; | ||
}); | ||
|
||
/** | ||
* Fulfills or rejects the promise with the given status and value. | ||
* | ||
* @param {string} status | ||
* @param {*} value | ||
* @param {Function} callback | ||
*/ | ||
function finalize(status, value, callback) { | ||
if (sinonPromise.status === STATUS_PENDING) { | ||
sinonPromise.status = status; | ||
callback(value); | ||
return; | ||
} | ||
throw new Error(`Promise already ${sinonPromise.status}`); | ||
} | ||
|
||
sinonPromise.resolve = function (value) { | ||
finalize(STATUS_FULFILLED, value, fakeExecutor.firstCall.args[0]); | ||
// Return the promise so that callers can await it: | ||
return sinonPromise; | ||
}; | ||
sinonPromise.reject = function (reason) { | ||
finalize(STATUS_REJECTED, reason, fakeExecutor.firstCall.args[1]); | ||
// Return a new promise that resolves when the sinon promise was | ||
// rejected, so that callers can await it: | ||
return new Promise(function (resolve) { | ||
sinonPromise.catch(() => resolve()); | ||
}); | ||
}; | ||
|
||
return sinonPromise; | ||
} | ||
|
||
module.exports = promise; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
"use strict"; | ||
|
||
var sinon = require("../lib/sinon.js"); | ||
var { assert, refute } = require("@sinonjs/referee"); | ||
|
||
async function getPromiseStatus(promise) { | ||
var status = "pending"; | ||
var value = null; | ||
promise | ||
.then(function (val) { | ||
status = "fulfilled"; | ||
value = val; | ||
}) | ||
.catch(function (reason) { | ||
status = "rejected"; | ||
value = reason; | ||
}); | ||
await new Promise(function (resolve) { | ||
setTimeout(resolve, 0); | ||
}); | ||
return { status, value }; | ||
} | ||
|
||
describe("promise", function () { | ||
context("with default executor", function () { | ||
it("returns an unresolved promise", async function () { | ||
var promise = sinon.promise(); | ||
|
||
var { status, value } = await getPromiseStatus(promise); | ||
assert.equals(promise.toString(), "[object Promise]"); | ||
assert.equals(status, "pending"); | ||
assert.isNull(value); | ||
assert.equals(promise.status, status); | ||
assert.isUndefined(promise.resolvedValue); | ||
assert.isUndefined(promise.rejectedValue); | ||
}); | ||
|
||
it("resolves the promise", async function () { | ||
var result = Symbol("promise result"); | ||
var promise = sinon.promise(); | ||
|
||
var returnValue = promise.resolve(result); | ||
|
||
var { status, value } = await getPromiseStatus(promise); | ||
assert.equals(status, "fulfilled"); | ||
assert.same(value, result); | ||
assert.equals(promise.status, status); | ||
assert.same(promise.resolvedValue, result); | ||
assert.isUndefined(promise.rejectedValue); | ||
assert.same(returnValue, promise); | ||
}); | ||
|
||
it("rejects the promise", async function () { | ||
var error = new Error("promise error"); | ||
var promise = sinon.promise(); | ||
|
||
var returnValue = promise.reject(error); | ||
|
||
var { status, value } = await getPromiseStatus(promise); | ||
assert.equals(status, "rejected"); | ||
assert.same(value, error); | ||
assert.equals(promise.status, status); | ||
assert.isUndefined(promise.resolvedValue); | ||
assert.same(promise.rejectedValue, error); | ||
refute.same(returnValue, promise); | ||
assert.equals(returnValue.toString(), "[object Promise]"); | ||
await assert.resolves(returnValue, undefined); | ||
}); | ||
|
||
context("with resolved promise", function () { | ||
var promise; | ||
|
||
beforeEach(function () { | ||
promise = sinon.promise(); | ||
promise.resolve(1); | ||
}); | ||
|
||
it("fails to resolve again", function () { | ||
assert.exception( | ||
() => { | ||
promise.resolve(2); | ||
}, | ||
{ | ||
name: "Error", | ||
message: "Promise already fulfilled", | ||
} | ||
); | ||
}); | ||
|
||
it("fails to reject", function () { | ||
assert.exception( | ||
() => { | ||
promise.reject(2); | ||
}, | ||
{ | ||
name: "Error", | ||
message: "Promise already fulfilled", | ||
} | ||
); | ||
}); | ||
}); | ||
|
||
context("with rejected promise", function () { | ||
var promise; | ||
|
||
beforeEach(function () { | ||
promise = sinon.promise(); | ||
promise.reject(1); | ||
}); | ||
|
||
it("fails to reject again", function () { | ||
assert.exception( | ||
() => { | ||
promise.reject(2); | ||
}, | ||
{ | ||
name: "Error", | ||
message: "Promise already rejected", | ||
} | ||
); | ||
}); | ||
|
||
it("fails to resolve", function () { | ||
assert.exception( | ||
() => { | ||
promise.resolve(2); | ||
}, | ||
{ | ||
name: "Error", | ||
message: "Promise already rejected", | ||
} | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
context("with custom executor", function () { | ||
it("accepts a fake as the custom executor", function () { | ||
var executor = sinon.fake(); | ||
|
||
sinon.promise(executor); | ||
|
||
assert.equals(executor.callCount, 1); | ||
assert.equals(executor.firstCall.args.length, 2); | ||
assert.isFunction(executor.firstCall.firstArg); | ||
assert.isFunction(executor.firstCall.lastArg); | ||
}); | ||
|
||
it("accepts a stub as the custom executor", function () { | ||
var executor = sinon.stub(); | ||
|
||
sinon.promise(executor); | ||
|
||
assert.equals(executor.callCount, 1); | ||
assert.equals(executor.firstCall.args.length, 2); | ||
assert.isFunction(executor.firstCall.firstArg); | ||
assert.isFunction(executor.firstCall.lastArg); | ||
}); | ||
|
||
it("accepts a function as the custom executor", function () { | ||
var args; | ||
function executor(resolve, reject) { | ||
args = [resolve, reject]; | ||
} | ||
|
||
sinon.promise(executor); | ||
|
||
assert.equals(args.length, 2); | ||
assert.isFunction(args[0]); | ||
assert.isFunction(args[1]); | ||
}); | ||
|
||
it("sets resolvedValue when custom executor resolves", async function () { | ||
var result = Symbol("executor result"); | ||
function executor(resolve) { | ||
resolve(result); | ||
} | ||
|
||
var promise = sinon.promise(executor); | ||
|
||
await assert.resolves(promise, result); | ||
assert.equals(promise.status, "fulfilled"); | ||
assert.same(promise.resolvedValue, result); | ||
assert.isUndefined(promise.rejectedValue); | ||
}); | ||
|
||
it("sets rejectedValue when custom executor fails", async function () { | ||
var reason = new Error("executor failure"); | ||
function executor(resolve, reject) { | ||
reject(reason); | ||
} | ||
|
||
var promise = sinon.promise(executor); | ||
|
||
await assert.rejects(promise, reason); | ||
assert.equals(promise.status, "rejected"); | ||
assert.same(promise.rejectedValue, reason); | ||
assert.isUndefined(promise.resolvedValue); | ||
}); | ||
|
||
it("resolves the promise", async function () { | ||
var result = Symbol("promise result"); | ||
var promise = sinon.promise(sinon.fake()); | ||
|
||
promise.resolve(result); | ||
|
||
await assert.resolves(promise, result); | ||
assert.equals(promise.status, "fulfilled"); | ||
assert.same(promise.resolvedValue, result); | ||
assert.isUndefined(promise.rejectedValue); | ||
}); | ||
|
||
it("rejects the promise", async function () { | ||
var error = new Error("promise error"); | ||
var promise = sinon.promise(sinon.fake()); | ||
|
||
promise.reject(error); | ||
|
||
await assert.rejects(promise, error); | ||
assert.equals(promise.status, "rejected"); | ||
assert.isUndefined(promise.resolvedValue); | ||
assert.same(promise.rejectedValue, error); | ||
}); | ||
}); | ||
}); |