| // Licensed to the Software Freedom Conservancy (SFC) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The SFC licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| 'use strict' |
| |
| const assert = require('node:assert') |
| |
| const testutil = require('./testutil') |
| const promise = require('selenium-webdriver/lib/promise') |
| |
| // Aliases for readability. |
| const NativePromise = Promise |
| const StubError = testutil.StubError |
| const assertIsStubError = testutil.assertIsStubError |
| const callbackPair = testutil.callbackPair |
| const throwStubError = testutil.throwStubError |
| const fail = () => assert.fail() |
| |
| // Refer to promise_aplus_test for promise compliance with standard behavior. |
| describe('promise', function () { |
| let app, uncaughtExceptions |
| |
| beforeEach(function setUp() { |
| if (promise.USE_PROMISE_MANAGER) { |
| promise.LONG_STACK_TRACES = false |
| uncaughtExceptions = [] |
| |
| app = promise.controlFlow() |
| app.on(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, (e) => uncaughtExceptions.push(e)) |
| } |
| }) |
| |
| afterEach(function tearDown() { |
| if (promise.USE_PROMISE_MANAGER) { |
| app.reset() |
| promise.setDefaultFlow(new promise.ControlFlow()) |
| assert.deepStrictEqual([], uncaughtExceptions, 'Did not expect any uncaught exceptions') |
| promise.LONG_STACK_TRACES = false |
| } |
| }) |
| |
| it('isPromise', () => { |
| const v = () => {} |
| const x = new Promise(v, v) |
| const p = createRejectedPromise('reject') |
| const q = Promise.resolve('resolved') |
| const t = { |
| then() {}, |
| } |
| const f = () => {} |
| f.then = () => {} |
| assert.equal(true, promise.isPromise(x)) |
| assert.equal(true, promise.isPromise(p)) |
| assert.equal(true, promise.isPromise(q)) |
| assert.equal(true, promise.isPromise(t)) |
| assert.equal(true, promise.isPromise(f)) |
| assert.equal(false, promise.isPromise(0)) |
| assert.equal(false, promise.isPromise(false)) |
| assert.equal(false, promise.isPromise(true)) |
| assert.equal(false, promise.isPromise(null)) |
| assert.equal(false, promise.isPromise(undefined)) |
| assert.equal(false, promise.isPromise('')) |
| assert.equal(false, promise.isPromise('promise')) |
| assert.equal(false, promise.isPromise(v)) |
| }) |
| |
| function defer() { |
| let d = {} |
| let promise = new Promise((resolve, reject) => { |
| Object.assign(d, { resolve, reject }) |
| }) |
| d.promise = promise |
| return d |
| } |
| |
| function createRejectedPromise(reason) { |
| var p = Promise.reject(reason) |
| p.catch(function () {}) // Silence unhandled rejection handlers. |
| return p |
| } |
| |
| describe('fullyResolved', function () { |
| it('primitives', function () { |
| function runTest(value) { |
| return promise.fullyResolved(value).then((resolved) => assert.strictEqual(value, resolved)) |
| } |
| |
| return runTest(true) |
| .then(() => runTest(function () {})) |
| .then(() => runTest(null)) |
| .then(() => runTest(123)) |
| .then(() => runTest('foo bar')) |
| .then(() => runTest(undefined)) |
| }) |
| |
| it('arrayOfPrimitives', function () { |
| var fn = function () {} |
| const array = [true, fn, null, 123, '', undefined, 1] |
| return promise.fullyResolved(array).then(function (resolved) { |
| assert.strictEqual(array, resolved) |
| assert.deepStrictEqual([true, fn, null, 123, '', undefined, 1], resolved) |
| }) |
| }) |
| |
| it('nestedArrayOfPrimitives', function () { |
| var fn = function () {} |
| const array = [true, [fn, null, 123], '', undefined] |
| return promise.fullyResolved(array).then(function (resolved) { |
| assert.strictEqual(array, resolved) |
| assert.deepStrictEqual([true, [fn, null, 123], '', undefined], resolved) |
| assert.deepStrictEqual([fn, null, 123], resolved[1]) |
| }) |
| }) |
| |
| it('arrayWithPromisedPrimitive', function () { |
| return promise.fullyResolved([Promise.resolve(123)]).then(function (resolved) { |
| assert.deepStrictEqual([123], resolved) |
| }) |
| }) |
| |
| it('promiseResolvesToPrimitive', function () { |
| return promise.fullyResolved(Promise.resolve(123)).then((resolved) => assert.strictEqual(123, resolved)) |
| }) |
| |
| it('promiseResolvesToArray', function () { |
| var fn = function () {} |
| const array = [true, [fn, null, 123], '', undefined] |
| const aPromise = Promise.resolve(array) |
| |
| var result = promise.fullyResolved(aPromise) |
| return result.then(function (resolved) { |
| assert.strictEqual(array, resolved) |
| assert.deepStrictEqual([true, [fn, null, 123], '', undefined], resolved) |
| assert.deepStrictEqual([fn, null, 123], resolved[1]) |
| }) |
| }) |
| |
| it('promiseResolvesToArrayWithPromises', function () { |
| var nestedPromise = Promise.resolve(123) |
| const aPromise = Promise.resolve([true, nestedPromise]) |
| return promise.fullyResolved(aPromise).then(function (resolved) { |
| assert.deepStrictEqual([true, 123], resolved) |
| }) |
| }) |
| |
| it('rejectsIfArrayPromiseRejects', function () { |
| var nestedPromise = createRejectedPromise(new StubError()) |
| const aPromise = Promise.resolve([true, nestedPromise]) |
| |
| return promise.fullyResolved(aPromise).then(assert.fail, assertIsStubError) |
| }) |
| |
| it('rejectsOnFirstArrayRejection', function () { |
| var e1 = new Error('foo') |
| var e2 = new Error('bar') |
| const aPromise = Promise.resolve([createRejectedPromise(e1), createRejectedPromise(e2)]) |
| |
| return promise.fullyResolved(aPromise).then(assert.fail, function (error) { |
| assert.strictEqual(e1, error) |
| }) |
| }) |
| |
| it('rejectsIfNestedArrayPromiseRejects', function () { |
| const aPromise = Promise.resolve([Promise.resolve([createRejectedPromise(new StubError())])]) |
| |
| return promise.fullyResolved(aPromise).then(assert.fail, assertIsStubError) |
| }) |
| |
| it('simpleHash', function () { |
| var hash = { a: 123 } |
| return promise.fullyResolved(hash).then(function (resolved) { |
| assert.strictEqual(hash, resolved) |
| assert.deepStrictEqual(hash, { a: 123 }) |
| }) |
| }) |
| |
| it('nestedHash', function () { |
| var nestedHash = { foo: 'bar' } |
| var hash = { a: 123, b: nestedHash } |
| |
| return promise.fullyResolved(hash).then(function (resolved) { |
| assert.strictEqual(hash, resolved) |
| assert.deepStrictEqual({ a: 123, b: { foo: 'bar' } }, resolved) |
| assert.strictEqual(nestedHash, resolved['b']) |
| }) |
| }) |
| |
| it('promiseResolvesToSimpleHash', function () { |
| var hash = { a: 123 } |
| const aPromise = Promise.resolve(hash) |
| |
| return promise.fullyResolved(aPromise).then((resolved) => assert.strictEqual(hash, resolved)) |
| }) |
| |
| it('promiseResolvesToNestedHash', function () { |
| var nestedHash = { foo: 'bar' } |
| var hash = { a: 123, b: nestedHash } |
| const aPromise = Promise.resolve(hash) |
| |
| return promise.fullyResolved(aPromise).then(function (resolved) { |
| assert.strictEqual(hash, resolved) |
| assert.strictEqual(nestedHash, resolved['b']) |
| assert.deepStrictEqual(hash, { a: 123, b: { foo: 'bar' } }) |
| }) |
| }) |
| |
| it('promiseResolvesToHashWithPromises', function () { |
| const aPromise = Promise.resolve({ |
| a: Promise.resolve(123), |
| }) |
| |
| return promise.fullyResolved(aPromise).then(function (resolved) { |
| assert.deepStrictEqual({ a: 123 }, resolved) |
| }) |
| }) |
| |
| it('rejectsIfHashPromiseRejects', function () { |
| const aPromise = Promise.resolve({ |
| a: createRejectedPromise(new StubError()), |
| }) |
| |
| return promise.fullyResolved(aPromise).then(assert.fail, assertIsStubError) |
| }) |
| |
| it('rejectsIfNestedHashPromiseRejects', function () { |
| const aPromise = Promise.resolve({ |
| a: { b: createRejectedPromise(new StubError()) }, |
| }) |
| |
| return promise.fullyResolved(aPromise).then(assert.fail, assertIsStubError) |
| }) |
| |
| it('instantiatedObject', function () { |
| function Foo() { |
| this.bar = 'baz' |
| } |
| |
| var foo = new Foo() |
| |
| return promise.fullyResolved(foo).then(function (resolvedFoo) { |
| assert.strictEqual(foo, resolvedFoo) |
| assert.ok(resolvedFoo instanceof Foo) |
| assert.deepStrictEqual(new Foo(), resolvedFoo) |
| }) |
| }) |
| |
| it('withEmptyArray', function () { |
| return promise.fullyResolved([]).then(function (resolved) { |
| assert.deepStrictEqual([], resolved) |
| }) |
| }) |
| |
| it('withEmptyHash', function () { |
| return promise.fullyResolved({}).then(function (resolved) { |
| assert.deepStrictEqual({}, resolved) |
| }) |
| }) |
| |
| it('arrayWithPromisedHash', function () { |
| var obj = { foo: 'bar' } |
| const array = [Promise.resolve(obj)] |
| |
| return promise.fullyResolved(array).then(function (resolved) { |
| assert.deepStrictEqual(resolved, [obj]) |
| }) |
| }) |
| }) |
| |
| describe('finally', function () { |
| it('successful callback does not suppress original error', async () => { |
| let p = Promise.reject(new StubError()) |
| let called = false |
| |
| try { |
| await promise.finally(p, function () { |
| called = true |
| }) |
| fail('should have thrown') |
| } catch (e) { |
| assertIsStubError(e) |
| assert.ok(called) |
| } |
| }) |
| |
| it('failing callback suppresses original error', async () => { |
| let p = Promise.reject(Error('original')) |
| |
| try { |
| await promise.finally(p, throwStubError) |
| fail('should have thrown') |
| } catch (e) { |
| assertIsStubError(e) |
| } |
| }) |
| |
| it('callback throws after fulfilled promise', async () => { |
| try { |
| await promise.finally(Promise.resolve(), throwStubError) |
| fail('should have thrown') |
| } catch (e) { |
| assertIsStubError(e) |
| } |
| }) |
| |
| it('callback returns rejected promise', async () => { |
| try { |
| await promise.finally(Promise.resolve(), () => Promise.reject(new StubError())) |
| fail('should have thrown') |
| } catch (e) { |
| assertIsStubError(e) |
| } |
| }) |
| |
| it('returned promise resolves with callback result', async () => { |
| let value = await promise.finally(Promise.resolve(1), () => 2) |
| assert.strictEqual(value, 2) |
| }) |
| }) |
| |
| describe('checkedNodeCall', function () { |
| it('functionThrows', function () { |
| return promise.checkedNodeCall(throwStubError).then(assert.fail, assertIsStubError) |
| }) |
| |
| it('functionReturnsAnError', function () { |
| return promise |
| .checkedNodeCall(function (callback) { |
| callback(new StubError()) |
| }) |
| .then(assert.fail, assertIsStubError) |
| }) |
| |
| it('functionReturnsSuccess', function () { |
| var success = 'success!' |
| return promise |
| .checkedNodeCall(function (callback) { |
| callback(null, success) |
| }) |
| .then((value) => assert.strictEqual(success, value)) |
| }) |
| |
| it('functionReturnsAndThrows', function () { |
| var error = new Error('boom') |
| var error2 = new Error('boom again') |
| return promise |
| .checkedNodeCall(function (callback) { |
| callback(error) |
| throw error2 |
| }) |
| .then(assert.fail, (e) => assert.strictEqual(error, e)) |
| }) |
| |
| it('functionThrowsAndReturns', function () { |
| var error = new Error('boom') |
| var error2 = new Error('boom again') |
| return promise |
| .checkedNodeCall(function (callback) { |
| setTimeout(() => callback(error), 10) |
| throw error2 |
| }) |
| .then(assert.fail, (e) => assert.strictEqual(error2, e)) |
| }) |
| }) |
| |
| describe('map', function () { |
| it('(base case)', function () { |
| const a = [1, 2, 3] |
| return promise |
| .map(a, function (value, index, a2) { |
| assert.strictEqual(a, a2) |
| assert.strictEqual('number', typeof index, 'not a number') |
| return value + 1 |
| }) |
| .then(function (value) { |
| assert.deepStrictEqual([2, 3, 4], value) |
| }) |
| }) |
| |
| it('omitsDeleted', function () { |
| const a = [0, 1, 2, 3, 4, 5, 6] |
| delete a[1] |
| delete a[3] |
| delete a[4] |
| delete a[6] |
| |
| const expected = [0, NaN, 4, NaN, NaN, 25, NaN] |
| |
| return promise |
| .map(a, function (value) { |
| return value * value |
| }) |
| .then(function (value) { |
| assert.deepStrictEqual(expected, value) |
| }) |
| }) |
| |
| it('emptyArray', function () { |
| return promise |
| .map([], function (value) { |
| return value + 1 |
| }) |
| .then(function (value) { |
| assert.deepStrictEqual([], value) |
| }) |
| }) |
| |
| it('inputIsPromise', function () { |
| var input = defer() |
| var result = promise.map(input.promise, function (value) { |
| return value + 1 |
| }) |
| |
| var pair = callbackPair(function (value) { |
| assert.deepStrictEqual([2, 3, 4], value) |
| }) |
| result = result.then(pair.callback, pair.errback) |
| |
| setTimeout(function () { |
| pair.assertNeither() |
| input.resolve([1, 2, 3]) |
| }, 10) |
| |
| return result |
| }) |
| |
| it('waitsForFunctionResultToResolve', function () { |
| var innerResults = [defer(), defer()] |
| |
| var result = promise.map([1, 2], function (_value, index) { |
| return innerResults[index].promise |
| }) |
| |
| var pair = callbackPair(function (value) { |
| assert.deepStrictEqual(['a', 'b'], value) |
| }) |
| result = result.then(pair.callback, pair.errback) |
| |
| return NativePromise.resolve() |
| .then(function () { |
| pair.assertNeither() |
| innerResults[0].resolve('a') |
| }) |
| .then(function () { |
| pair.assertNeither() |
| innerResults[1].resolve('b') |
| return result |
| }) |
| .then(pair.assertCallback) |
| }) |
| |
| it('rejectsPromiseIfFunctionThrows', function () { |
| return promise.map([1], throwStubError).then(assert.fail, assertIsStubError) |
| }) |
| |
| it('rejectsPromiseIfFunctionReturnsRejectedPromise', function () { |
| return promise |
| .map([1], function () { |
| return createRejectedPromise(new StubError()) |
| }) |
| .then(assert.fail, assertIsStubError) |
| }) |
| |
| it('stopsCallingFunctionIfPreviousIterationFailed', function () { |
| var count = 0 |
| return promise |
| .map([1, 2, 3, 4], function () { |
| count++ |
| if (count === 3) { |
| throw new StubError() |
| } |
| }) |
| .then(assert.fail, function (e) { |
| assertIsStubError(e) |
| assert.strictEqual(3, count) |
| }) |
| }) |
| |
| it('rejectsWithFirstRejectedPromise', function () { |
| var innerResult = [ |
| Promise.resolve(), |
| createRejectedPromise(new StubError()), |
| createRejectedPromise(Error('should be ignored')), |
| ] |
| var count = 0 |
| return promise |
| .map([1, 2, 3, 4], function (_value, index) { |
| count += 1 |
| return innerResult[index] |
| }) |
| .then(assert.fail, function (e) { |
| assertIsStubError(e) |
| assert.strictEqual(2, count) |
| }) |
| }) |
| |
| it('preservesOrderWhenMapReturnsPromise', function () { |
| var deferreds = [defer(), defer(), defer(), defer()] |
| var result = promise.map(deferreds, function (value) { |
| return value.promise |
| }) |
| |
| var pair = callbackPair(function (value) { |
| assert.deepStrictEqual([0, 1, 2, 3], value) |
| }) |
| result = result.then(pair.callback, pair.errback) |
| |
| return Promise.resolve() |
| .then(function () { |
| pair.assertNeither() |
| for (let i = deferreds.length; i > 0; i -= 1) { |
| deferreds[i - 1].resolve(i - 1) |
| } |
| return result |
| }) |
| .then(pair.assertCallback) |
| }) |
| }) |
| |
| describe('filter', function () { |
| it('basicFiltering', function () { |
| const a = [0, 1, 2, 3] |
| return promise |
| .filter(a, function (val, index, a2) { |
| assert.strictEqual(a, a2) |
| assert.strictEqual('number', typeof index, 'not a number') |
| return val > 1 |
| }) |
| .then(function (val) { |
| assert.deepStrictEqual([2, 3], val) |
| }) |
| }) |
| |
| it('omitsDeleted', function () { |
| const a = [0, 1, 2, 3, 4, 5, 6] |
| delete a[3] |
| delete a[4] |
| |
| return promise |
| .filter(a, function (value) { |
| return value > 1 && value < 6 |
| }) |
| .then(function (val) { |
| assert.deepStrictEqual([2, 5], val) |
| }) |
| }) |
| |
| it('preservesInputs', function () { |
| const a = [0, 1, 2, 3] |
| |
| return promise |
| .filter(a, function (_value, i, a2) { |
| assert.strictEqual(a, a2) |
| // Even if a function modifies the input array, the original value |
| // should be inserted into the new array. |
| a2[i] = a2[i] - 1 |
| return a2[i] >= 1 |
| }) |
| .then(function (val) { |
| assert.deepStrictEqual([2, 3], val) |
| }) |
| }) |
| |
| it('inputIsPromise', function () { |
| const input = defer() |
| let result = promise.filter(input.promise, function (value) { |
| return value > 1 && value < 3 |
| }) |
| |
| const pair = callbackPair(function (value) { |
| assert.deepStrictEqual([2], value) |
| }) |
| result = result.then(pair.callback, pair.errback) |
| return NativePromise.resolve() |
| .then(function () { |
| pair.assertNeither() |
| input.resolve([1, 2, 3]) |
| return result |
| }) |
| .then(pair.assertCallback) |
| }) |
| |
| it('waitsForFunctionResultToResolve', function () { |
| const innerResults = [defer(), defer()] |
| |
| let result = promise.filter([1, 2], function (_value, index) { |
| return innerResults[index].promise |
| }) |
| |
| const pair = callbackPair(function (value) { |
| assert.deepStrictEqual([2], value) |
| }) |
| result = result.then(pair.callback, pair.errback) |
| return NativePromise.resolve() |
| .then(function () { |
| pair.assertNeither() |
| innerResults[0].resolve(false) |
| }) |
| .then(function () { |
| pair.assertNeither() |
| innerResults[1].resolve(true) |
| return result |
| }) |
| .then(pair.assertCallback) |
| }) |
| |
| it('rejectsPromiseIfFunctionReturnsRejectedPromise', function () { |
| return promise |
| .filter([1], function () { |
| return createRejectedPromise(new StubError()) |
| }) |
| .then(assert.fail, assertIsStubError) |
| }) |
| |
| it('stopsCallingFunctionIfPreviousIterationFailed', function () { |
| var count = 0 |
| return promise |
| .filter([1, 2, 3, 4], function () { |
| count++ |
| if (count === 3) { |
| throw new StubError() |
| } |
| }) |
| .then(assert.fail, function (e) { |
| assertIsStubError(e) |
| assert.strictEqual(3, count) |
| }) |
| }) |
| |
| it('rejectsWithFirstRejectedPromise', function () { |
| var innerResult = [ |
| Promise.resolve(), |
| createRejectedPromise(new StubError()), |
| createRejectedPromise(Error('should be ignored')), |
| ] |
| |
| return promise |
| .filter([1, 2, 3, 4], function (_value, index) { |
| assert.ok(index < innerResult.length) |
| return innerResult[index] |
| }) |
| .then(assert.fail, assertIsStubError) |
| }) |
| |
| it('preservesOrderWhenFilterReturnsPromise', function () { |
| const deferreds = [defer(), defer(), defer(), defer()] |
| let result = promise.filter([0, 1, 2, 3], function (_value, index) { |
| return deferreds[index].promise |
| }) |
| |
| const pair = callbackPair(function (value) { |
| assert.deepStrictEqual([1, 2], value) |
| }) |
| result = result.then(pair.callback, pair.errback) |
| |
| return NativePromise.resolve() |
| .then(function () { |
| pair.assertNeither() |
| for (let i = deferreds.length - 1; i >= 0; i -= 1) { |
| deferreds[i].resolve(i > 0 && i < 3) |
| } |
| return result |
| }) |
| .then(pair.assertCallback) |
| }) |
| }) |
| }) |