From ed366ef595abf58df65982448379b62bd074abf3 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 25 Jul 2017 23:06:12 -0700 Subject: [PATCH] Promise.prototype.finally: add tests --- .../finally/invokes-then-with-function.js | 53 +++++++++++++++++++ .../finally/invokes-then-with-non-function.js | 49 +++++++++++++++++ .../prototype/finally/is-a-function.js | 20 +++++++ .../Promise/prototype/finally/is-a-method.js | 16 ++++++ .../Promise/prototype/finally/length.js | 27 ++++++++++ .../Promise/prototype/finally/name.js | 28 ++++++++++ .../Promise/prototype/finally/prop-desc.js | 19 +++++++ .../finally/rejected-observable-then-calls.js | 46 ++++++++++++++++ .../finally/rejection-reason-no-fulfill.js | 23 ++++++++ .../rejection-reason-override-with-throw.js | 23 ++++++++ .../finally/resolution-value-no-override.js | 20 +++++++ .../finally/resolved-observable-then-calls.js | 46 ++++++++++++++++ .../finally/this-value-non-object.js | 17 ++++++ .../finally/this-value-then-not-callable.js | 49 +++++++++++++++++ .../finally/this-value-then-poisoned.js | 20 +++++++ .../finally/this-value-then-throws.js | 19 +++++++ 16 files changed, 475 insertions(+) create mode 100644 test/built-ins/Promise/prototype/finally/invokes-then-with-function.js create mode 100644 test/built-ins/Promise/prototype/finally/invokes-then-with-non-function.js create mode 100644 test/built-ins/Promise/prototype/finally/is-a-function.js create mode 100644 test/built-ins/Promise/prototype/finally/is-a-method.js create mode 100644 test/built-ins/Promise/prototype/finally/length.js create mode 100644 test/built-ins/Promise/prototype/finally/name.js create mode 100644 test/built-ins/Promise/prototype/finally/prop-desc.js create mode 100644 test/built-ins/Promise/prototype/finally/rejected-observable-then-calls.js create mode 100644 test/built-ins/Promise/prototype/finally/rejection-reason-no-fulfill.js create mode 100644 test/built-ins/Promise/prototype/finally/rejection-reason-override-with-throw.js create mode 100644 test/built-ins/Promise/prototype/finally/resolution-value-no-override.js create mode 100644 test/built-ins/Promise/prototype/finally/resolved-observable-then-calls.js create mode 100644 test/built-ins/Promise/prototype/finally/this-value-non-object.js create mode 100644 test/built-ins/Promise/prototype/finally/this-value-then-not-callable.js create mode 100644 test/built-ins/Promise/prototype/finally/this-value-then-poisoned.js create mode 100644 test/built-ins/Promise/prototype/finally/this-value-then-throws.js diff --git a/test/built-ins/Promise/prototype/finally/invokes-then-with-function.js b/test/built-ins/Promise/prototype/finally/invokes-then-with-function.js new file mode 100644 index 00000000000..6e12e364897 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/invokes-then-with-function.js @@ -0,0 +1,53 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: Promise.prototype.finally invokes `then` method +esid: sec-promise.prototype.finally +features: [Promise, Promise.prototype.finally] +---*/ + +var target = new Promise(function () {}); +var returnValue = {}; +var callCount = 0; +var thisValue = null; +var argCount = null; +var firstArg = null; +var secondArg = null; + +target.then = function(a, b) { + callCount += 1; + + thisValue = this; + argCount = arguments.length; + firstArg = a; + secondArg = b; + + return returnValue; +}; + +var originalFinallyHandler = function () {}; + +var result = Promise.prototype.finally.call(target, originalFinallyHandler, 2, 3); + +assert.sameValue(callCount, 1, 'Invokes `then` method exactly once'); +assert.sameValue( + thisValue, + target, + 'Invokes `then` method with the instance as the `this` value' +); +assert.sameValue(argCount, 2, 'Invokes `then` method with exactly two single arguments'); +assert.sameValue( + typeof firstArg, + 'function', + 'Invokes `then` method with a function as the first argument' +); +assert.notSameValue(firstArg, originalFinallyHandler, 'Invokes `then` method with a different fulfillment handler'); +assert.sameValue( + typeof secondArg, + 'function', + 'Invokes `then` method with a function as the second argument' +); +assert.notSameValue(secondArg, originalFinallyHandler, 'Invokes `then` method with a different fulfillment handler'); + +assert.sameValue(result, returnValue, 'Returns the result of the invocation of `then`'); diff --git a/test/built-ins/Promise/prototype/finally/invokes-then-with-non-function.js b/test/built-ins/Promise/prototype/finally/invokes-then-with-non-function.js new file mode 100644 index 00000000000..84d6ff50698 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/invokes-then-with-non-function.js @@ -0,0 +1,49 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: Promise.prototype.finally invokes `then` method +esid: sec-promise.prototype.finally +features: [Promise, Promise.prototype.finally] +---*/ + +var target = new Promise(function () {}); +var returnValue = {}; +var callCount = 0; +var thisValue = null; +var argCount = null; +var firstArg = null; +var secondArg = null; +var result = null; + +target.then = function(a, b) { + callCount += 1; + + thisValue = this; + argCount = arguments.length; + firstArg = a; + secondArg = b; + + return returnValue; +}; + +result = Promise.prototype.finally.call(target, 1, 2, 3); + +assert.sameValue(callCount, 1, 'Invokes `then` method exactly once'); +assert.sameValue( + thisValue, + target, + 'Invokes `then` method with the instance as the `this` value' +); +assert.sameValue(argCount, 2, 'Invokes `then` method with exactly two single arguments'); +assert.sameValue( + firstArg, + 1, + 'Invokes `then` method with the provided non-callable first argument' +); +assert.sameValue( + secondArg, + 1, + 'Invokes `then` method with the provided non-callable first argument' +); +assert.sameValue(result, returnValue, 'Returns the result of the invocation of `then`'); diff --git a/test/built-ins/Promise/prototype/finally/is-a-function.js b/test/built-ins/Promise/prototype/finally/is-a-function.js new file mode 100644 index 00000000000..5630fc3288c --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/is-a-function.js @@ -0,0 +1,20 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: Promise.prototype.finally is a function +esid: sec-promise.prototype.finally +features: [Promise.prototype.finally] +---*/ + +assert.sameValue( + Promise.prototype.finally instanceof Function, + true, + 'Expected Promise.prototype.finally to be instanceof Function' +); + +assert.sameValue( + typeof Promise.prototype.finally, + 'function', + 'Expected Promise.prototype.finally to be a function' +); diff --git a/test/built-ins/Promise/prototype/finally/is-a-method.js b/test/built-ins/Promise/prototype/finally/is-a-method.js new file mode 100644 index 00000000000..2b799d73bee --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/is-a-method.js @@ -0,0 +1,16 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: finally is a method on a Promise +esid: sec-promise.prototype.finally +features: [Promise.resolve, Promise.prototype.finally] +---*/ + +var p = Promise.resolve(3); + +assert.sameValue( + p.finally, + Promise.prototype.finally, + 'Expected the `finally` method on a Promise to be `Promise.prototype.finally`' +); diff --git a/test/built-ins/Promise/prototype/finally/length.js b/test/built-ins/Promise/prototype/finally/length.js new file mode 100644 index 00000000000..949fac5c56a --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/length.js @@ -0,0 +1,27 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: Promise.prototype.finally `length` property +esid: sec-promise.prototype.finally +info: > + ES6 Section 17: + Every built-in Function object, including constructors, has a length + property whose value is an integer. Unless otherwise specified, this value + is equal to the largest number of named arguments shown in the subclause + headings for the function description, including optional parameters. + + [...] + + Unless otherwise specified, the length property of a built-in Function + object has the attributes { [[Writable]]: false, [[Enumerable]]: false, + [[Configurable]]: true }. +includes: [propertyHelper.js] +features: [Promise.prototype.finally, Function.prototype.length] +---*/ + +assert.sameValue(Promise.prototype.finally.length, 1); + +verifyNotEnumerable(Promise.prototype.finally, 'length'); +verifyNotWritable(Promise.prototype.finally, 'length'); +verifyConfigurable(Promise.prototype.finally, 'length'); diff --git a/test/built-ins/Promise/prototype/finally/name.js b/test/built-ins/Promise/prototype/finally/name.js new file mode 100644 index 00000000000..c653fd31ed1 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/name.js @@ -0,0 +1,28 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: Promise.prototype.finally `name` property +esid: sec-promise.prototype.finally +info: > + ES6 Section 17: + + Every built-in Function object, including constructors, that is not + identified as an anonymous function has a name property whose value is a + String. Unless otherwise specified, this value is the name that is given to + the function in this specification. + + [...] + + Unless otherwise specified, the name property of a built-in Function + object, if it exists, has the attributes { [[Writable]]: false, + [[Enumerable]]: false, [[Configurable]]: true }. +includes: [propertyHelper.js] +features: [Promise.prototype.finally, Function.prototype.name] +---*/ + +assert.sameValue(Promise.prototype.finally.name, 'finally'); + +verifyNotEnumerable(Promise.prototype.finally, 'name'); +verifyNotWritable(Promise.prototype.finally, 'name'); +verifyConfigurable(Promise.prototype.finally, 'name'); diff --git a/test/built-ins/Promise/prototype/finally/prop-desc.js b/test/built-ins/Promise/prototype/finally/prop-desc.js new file mode 100644 index 00000000000..21c5b815954 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/prop-desc.js @@ -0,0 +1,19 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: Promise.prototype.finally property descriptor +esid: sec-promise.prototype.finally +info: > + Every other data property described in clauses 18 through 26 and in Annex + B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, + [[Configurable]]: true } unless otherwise specified. +includes: [propertyHelper.js] +features: [Promise.prototype.finally] +---*/ + +assert.sameValue(typeof Promise.prototype.finally, 'function'); + +verifyNotEnumerable(Promise.prototype, 'finally'); +verifyWritable(Promise.prototype, 'finally'); +verifyConfigurable(Promise.prototype, 'finally'); diff --git a/test/built-ins/Promise/prototype/finally/rejected-observable-then-calls.js b/test/built-ins/Promise/prototype/finally/rejected-observable-then-calls.js new file mode 100644 index 00000000000..1a950078c22 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/rejected-observable-then-calls.js @@ -0,0 +1,46 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: finally observably calls .then +esid: sec-promise.prototype.finally +features: [Promise.resolve, Promise.reject, Promise.prototype.finally] +flags: [async] +---*/ + +var initialThenCount = 0; +var noReason = {}; +var no = Promise.reject(noReason); +no.then = function () { + initialThenCount += 1; + return Promise.prototype.then.apply(this, arguments); +}; + +var onFinallyThenCount = 0; +var yesValue = {}; +var yes = Promise.resolve(yesValue); +yes.then = function () { + onFinallyThenCount += 1; + return Promise.prototype.then.apply(this, arguments); +}; + +var finallyCalled = false; +var catchCalled = false; + +no.catch(function (e) { + assert.sameValue(e, noReason); + throw e; +}).finally(function () { + finallyCalled = true; + return yes; +}).catch(function (e) { + catchCalled = true; + assert.sameValue(e, noReason); +}).then(function () { + assert.sameValue(finallyCalled, true, 'initial finally was called'); + assert.sameValue(initialThenCount, 1, 'initial finally invokes .then once'); + + assert.sameValue(catchCalled, true, 'catch was called'); + assert.sameValue(onFinallyThenCount, 1, 'onFinally return promise has .then invoked once'); + $DONE(); +}).catch($ERROR); diff --git a/test/built-ins/Promise/prototype/finally/rejection-reason-no-fulfill.js b/test/built-ins/Promise/prototype/finally/rejection-reason-no-fulfill.js new file mode 100644 index 00000000000..c60b3985c01 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/rejection-reason-no-fulfill.js @@ -0,0 +1,23 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: finally on a rejected promise can not convert to a fulfillment +esid: sec-promise.prototype.finally +features: [Promise, Promise.reject, Promise.prototype.finally] +flags: [async] +---*/ + +var original = {}; +var replacement = {}; + +var p = Promise.reject(original); + +p.finally(function () { + assert.sameValue(arguments.length, 0, 'onFinally receives zero args'); + return replacement; +}).then(function () { + $ERROR('promise is rejected pre-finally; onFulfill should not be called'); +}).catch(function (reason) { + assert.sameValue(reason, original, 'onFinally can not override the rejection value by returning'); +}).then($DONE).catch($ERROR); diff --git a/test/built-ins/Promise/prototype/finally/rejection-reason-override-with-throw.js b/test/built-ins/Promise/prototype/finally/rejection-reason-override-with-throw.js new file mode 100644 index 00000000000..d96921b3a9b --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/rejection-reason-override-with-throw.js @@ -0,0 +1,23 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: finally on a rejected promise can override the rejection reason +esid: sec-promise.prototype.finally +features: [Promise, Promise.reject, Promise.prototype.finally] +flags: [async] +---*/ + +var original = {}; +var thrown = {}; + +var p = Promise.reject(original); + +p.finally(function () { + assert.sameValue(arguments.length, 0, 'onFinally receives zero args'); + throw thrown; +}).then(function () { + $ERROR('promise is rejected; onFulfill should not be called'); +}).catch(function (reason) { + assert.sameValue(reason, thrown, 'onFinally can override the rejection reason by throwing'); +}).then($DONE).catch($ERROR); diff --git a/test/built-ins/Promise/prototype/finally/resolution-value-no-override.js b/test/built-ins/Promise/prototype/finally/resolution-value-no-override.js new file mode 100644 index 00000000000..5406d147898 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/resolution-value-no-override.js @@ -0,0 +1,20 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: finally on a fulfilled promise can not override the resolution value +esid: sec-promise.prototype.finally +features: [Promise, Promise.resolve, Promise.prototype.finally] +flags: [async] +---*/ + +var obj = {}; + +var p = Promise.resolve(obj); + +p.finally(function () { + assert.sameValue(arguments.length, 0, 'onFinally receives zero args'); + return {}; +}).then(function (x) { + assert.sameValue(x, obj, 'onFinally can not override the resolution value'); +}).then($DONE).catch($ERROR); diff --git a/test/built-ins/Promise/prototype/finally/resolved-observable-then-calls.js b/test/built-ins/Promise/prototype/finally/resolved-observable-then-calls.js new file mode 100644 index 00000000000..76ea38b8f2a --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/resolved-observable-then-calls.js @@ -0,0 +1,46 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: finally observably calls .then +esid: sec-promise.prototype.finally +features: [Promise.resolve, Promise.reject, Promise.prototype.finally] +flags: [async] +---*/ + +var initialThenCount = 0; +var yesValue = {}; +var yes = Promise.resolve(yesValue); +yes.then = function () { + initialThenCount += 1; + return Promise.prototype.then.apply(this, arguments); +}; + +var onFinallyThenCount = 0; +var noReason = {}; +var no = Promise.reject(noReason); +no.then = function () { + onFinallyThenCount += 1; + return Promise.prototype.then.apply(this, arguments); +}; + +var finallyCalled = false; +var catchCalled = false; + +yes.then(function (x) { + assert.sameValue(x, yesValue); + return x; +}).finally(function () { + finallyCalled = true; + return no; +}).catch(function (e) { + catchCalled = true; + assert.sameValue(e, noReason); +}).then(function () { + assert.sameValue(finallyCalled, true, 'initial finally was called'); + assert.sameValue(initialThenCount, 1, 'initial finally invokes .then once'); + + assert.sameValue(catchCalled, true, 'catch was called'); + assert.sameValue(onFinallyThenCount, 1, 'onFinally return promise has .then invoked once'); + $DONE(); +}).catch($ERROR); diff --git a/test/built-ins/Promise/prototype/finally/this-value-non-object.js b/test/built-ins/Promise/prototype/finally/this-value-non-object.js new file mode 100644 index 00000000000..7bfca2581ff --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/this-value-non-object.js @@ -0,0 +1,17 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: > + Promise.prototype.finally called with a non-object-coercible `this` value +esid: sec-promise.prototype.finally +features: [Promise.prototype.finally, Function.prototype.call] +---*/ + +assert.throws(TypeError, function() { + Promise.prototype.finally.call(undefined); +}, 'undefined'); + +assert.throws(TypeError, function() { + Promise.prototype.finally.call(null); +}, 'null'); diff --git a/test/built-ins/Promise/prototype/finally/this-value-then-not-callable.js b/test/built-ins/Promise/prototype/finally/this-value-then-not-callable.js new file mode 100644 index 00000000000..d1982605bbc --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/this-value-then-not-callable.js @@ -0,0 +1,49 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: > + Promise.prototype.finally called with a `this` value that does not define a + callable `then` property +esid: sec-promise.prototype.finally +features: [Symbol, Promise.prototype.finally, Function.prototype.call] +---*/ + +var symbol = Symbol(); + +var p = new Promise(function () {}); + +p.then = undefined; +assert.throws(TypeError, function() { + Promise.prototype.finally.call(p); +}, 'undefined'); + +p.then = null; +assert.throws(TypeError, function() { + Promise.prototype.finally.call(p); +}, 'null'); + +p.then = 1; +assert.throws(TypeError, function() { + Promise.prototype.finally.call(p); +}, 'number'); + +p.then = ''; +assert.throws(TypeError, function() { + Promise.prototype.finally.call(p); +}, 'string'); + +p.then = true; +assert.throws(TypeError, function() { + Promise.prototype.finally.call(p); +}, 'boolean'); + +p.then = symbol; +assert.throws(TypeError, function() { + Promise.prototype.finally.call(p); +}, 'symbol'); + +p.then = {}; +assert.throws(TypeError, function() { + Promise.prototype.finally.call(p); +}, 'ordinary object'); diff --git a/test/built-ins/Promise/prototype/finally/this-value-then-poisoned.js b/test/built-ins/Promise/prototype/finally/this-value-then-poisoned.js new file mode 100644 index 00000000000..5ab2385eb12 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/this-value-then-poisoned.js @@ -0,0 +1,20 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: > + Promise.prototype.finally called with a `this` value whose `then` property is + an accessor property that returns an abrupt completion +esid: sec-promise.prototype.finally +features: [Promise, Object.defineProperty, Promise.prototype.finally] +---*/ + +var poisonedThen = Object.defineProperty(new Promise(function () {}), 'then', { + get: function() { + throw new Test262Error(); + } +}); + +assert.throws(Test262Error, function() { + Promise.prototype.finally.call(poisonedThen); +}); diff --git a/test/built-ins/Promise/prototype/finally/this-value-then-throws.js b/test/built-ins/Promise/prototype/finally/this-value-then-throws.js new file mode 100644 index 00000000000..4395964cb61 --- /dev/null +++ b/test/built-ins/Promise/prototype/finally/this-value-then-throws.js @@ -0,0 +1,19 @@ +// Copyright (C) 2017 Jordan Harband. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +author: Jordan Harband +description: > + Promise.prototype.finally called with a `this` value that defines a `then` + method which returns an abrupt completion. +esid: sec-promise.prototype.finally +features: [Promise, Promise.prototype.finally] +---*/ + +var thrower = new Promise(function () {}); +thrower.then = function() { + throw new Test262Error(); +}; + +assert.throws(Test262Error, function() { + Promise.prototype.finally.call(thrower); +});