Skip to content

Commit 2e608c1

Browse files
authored
fix: correct isPromise implementation (#13314)
1 parent c3a000e commit 2e608c1

8 files changed

Lines changed: 31 additions & 37 deletions

File tree

e2e/__tests__/__snapshots__/testFailingJasmine.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ FAIL __tests__/worksWithConcurrentMode.test.js
4646
15 | });
4747
16 |
4848
49-
at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:198:11)
49+
at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:195:11)
5050
at Suite.failing (__tests__/worksWithConcurrentMode.test.js:13:17)
5151
at Object.describe (__tests__/worksWithConcurrentMode.test.js:8:1)
5252
@@ -80,7 +80,7 @@ FAIL __tests__/worksWithConcurrentOnlyMode.test.js
8080
15 | });
8181
16 |
8282
83-
at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:198:11)
83+
at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:195:11)
8484
at Suite.failing (__tests__/worksWithConcurrentOnlyMode.test.js:13:22)
8585
at Object.describe (__tests__/worksWithConcurrentOnlyMode.test.js:8:1)
8686

packages/expect/src/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import {equals, iterableEquality, subsetEquality} from '@jest/expect-utils';
1212
import * as matcherUtils from 'jest-matcher-utils';
13+
import {isPromise} from 'jest-util';
1314
import {
1415
any,
1516
anything,
@@ -69,12 +70,6 @@ export class JestAssertionError extends Error {
6970
matcherResult?: Omit<SyncExpectationResult, 'message'> & {message: string};
7071
}
7172

72-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
73-
const isPromise = <T extends any>(obj: any): obj is PromiseLike<T> =>
74-
!!obj &&
75-
(typeof obj === 'object' || typeof obj === 'function') &&
76-
typeof obj.then === 'function';
77-
7873
const createToThrowErrorMatchingSnapshotMatcher = function (
7974
matcher: RawMatcherFn,
8075
) {

packages/jest-circus/src/utils.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import slash = require('slash');
1313
import StackUtils = require('stack-utils');
1414
import type {AssertionResult, Status} from '@jest/test-result';
1515
import type {Circus, Global} from '@jest/types';
16-
import {ErrorWithStack, convertDescriptorToString, formatTime} from 'jest-util';
16+
import {
17+
ErrorWithStack,
18+
convertDescriptorToString,
19+
formatTime,
20+
isPromise,
21+
} from 'jest-util';
1722
import {format as prettyFormat} from 'pretty-format';
1823
import {ROOT_DESCRIBE_BLOCK_NAME, getState} from './state';
1924

@@ -266,13 +271,7 @@ export const callAsyncCircusFn = (
266271
}
267272
}
268273

269-
// If it's a Promise, return it. Test for an object with a `then` function
270-
// to support custom Promise implementations.
271-
if (
272-
typeof returnedValue === 'object' &&
273-
returnedValue !== null &&
274-
typeof returnedValue.then === 'function'
275-
) {
274+
if (isPromise(returnedValue)) {
276275
returnedValue.then(() => resolve(), reject);
277276
return;
278277
}

packages/jest-jasmine2/src/jasmineAsyncInstall.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,12 @@ import co from 'co';
1414
import isGeneratorFn from 'is-generator-fn';
1515
import pLimit = require('p-limit');
1616
import type {Config, Global} from '@jest/types';
17+
import {isPromise} from 'jest-util';
1718
import isError from './isError';
1819
import type Spec from './jasmine/Spec';
1920
import type {DoneFn, QueueableFn} from './queueRunner';
2021
import type {Jasmine} from './types';
2122

22-
function isPromise(obj: any): obj is PromiseLike<unknown> {
23-
return obj && typeof obj.then === 'function';
24-
}
25-
2623
// eslint-disable-next-line @typescript-eslint/no-empty-function
2724
const doneFnNoop = () => {};
2825

packages/jest-util/src/__tests__/isPromise.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,12 @@ test('a resolved Promise', () => {
2323
test('a rejected Promise', () => {
2424
expect(isPromise(Promise.reject().catch(() => {}))).toBe(true);
2525
});
26+
27+
test('a thenable', () => {
28+
expect(isPromise({then: () => 'hello'})).toBe(true);
29+
});
30+
31+
test('an async function', () => {
32+
async function asyncFn() {}
33+
expect(isPromise(asyncFn())).toBe(true);
34+
});

packages/jest-util/src/isPromise.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
// capture globalThis.Promise before it may potentially be overwritten
9-
const Promise = globalThis.Promise;
10-
11-
// see ES2015 spec 25.4.4.5, https://stackoverflow.com/a/38339199
12-
const isPromise = (candidate: unknown): candidate is Promise<unknown> =>
13-
Promise.resolve(candidate) === candidate;
14-
export default isPromise;
8+
export default function isPromise<T = unknown>(
9+
candidate: unknown,
10+
): candidate is PromiseLike<T> {
11+
return (
12+
candidate != null &&
13+
(typeof candidate === 'object' || typeof candidate === 'function') &&
14+
typeof (candidate as any).then === 'function'
15+
);
16+
}

packages/jest-worker/src/workers/processChild.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import {isPromise} from 'jest-util';
89
import {
910
CHILD_MESSAGE_CALL,
1011
CHILD_MESSAGE_END,
@@ -158,11 +159,6 @@ function execMethod(method: string, args: Array<unknown>): void {
158159
execFunction(main.setup, main, setupArgs, execHelper, reportInitializeError);
159160
}
160161

161-
const isPromise = (obj: any): obj is PromiseLike<unknown> =>
162-
!!obj &&
163-
(typeof obj === 'object' || typeof obj === 'function') &&
164-
typeof obj.then === 'function';
165-
166162
function execFunction(
167163
fn: UnknownFunction,
168164
ctx: unknown,

packages/jest-worker/src/workers/threadChild.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import {isMainThread, parentPort} from 'worker_threads';
9+
import {isPromise} from 'jest-util';
910
import {
1011
CHILD_MESSAGE_CALL,
1112
CHILD_MESSAGE_END,
@@ -160,11 +161,6 @@ function execMethod(method: string, args: Array<unknown>): void {
160161
execFunction(main.setup, main, setupArgs, execHelper, reportInitializeError);
161162
}
162163

163-
const isPromise = (obj: any): obj is PromiseLike<unknown> =>
164-
!!obj &&
165-
(typeof obj === 'object' || typeof obj === 'function') &&
166-
typeof obj.then === 'function';
167-
168164
function execFunction(
169165
fn: UnknownFunction,
170166
ctx: unknown,

0 commit comments

Comments
 (0)