Skip to content

Commit 71b7e17

Browse files
authored
fix(valid-expect-in-promise): allow expect.resolve & expect.reject (#948)
We purposely don't check if the `expect` is `await`ed or returned, as that is the role of the `valid-expect` rule. Fixes #947
1 parent f783d9d commit 71b7e17

File tree

2 files changed

+129
-5
lines changed

2 files changed

+129
-5
lines changed

src/rules/__tests__/valid-expect-in-promise.test.ts

+84
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,50 @@ ruleTester.run('valid-expect-in-promise', rule, {
1515
"test('something', () => Promise.resolve().then(() => expect(1).toBe(2)));",
1616
'Promise.resolve().then(() => expect(1).toBe(2))',
1717
'const x = Promise.resolve().then(() => expect(1).toBe(2))',
18+
dedent`
19+
it('is valid', () => {
20+
const promise = loadNumber().then(number => {
21+
expect(typeof number).toBe('number');
22+
23+
return number + 1;
24+
});
25+
26+
expect(promise).resolves.toBe(1);
27+
});
28+
`,
29+
dedent`
30+
it('is valid', () => {
31+
const promise = loadNumber().then(number => {
32+
expect(typeof number).toBe('number');
33+
34+
return number + 1;
35+
});
36+
37+
expect(promise).resolves.not.toBe(2);
38+
});
39+
`,
40+
dedent`
41+
it('is valid', () => {
42+
const promise = loadNumber().then(number => {
43+
expect(typeof number).toBe('number');
44+
45+
return number + 1;
46+
});
47+
48+
expect(promise).rejects.toBe(1);
49+
});
50+
`,
51+
dedent`
52+
it('is valid', () => {
53+
const promise = loadNumber().then(number => {
54+
expect(typeof number).toBe('number');
55+
56+
return number + 1;
57+
});
58+
59+
expect(promise).rejects.not.toBe(2);
60+
});
61+
`,
1862
dedent`
1963
it('is valid', async () => {
2064
const promise = loadNumber().then(number => {
@@ -1469,5 +1513,45 @@ ruleTester.run('valid-expect-in-promise', rule, {
14691513
},
14701514
],
14711515
},
1516+
{
1517+
code: dedent`
1518+
it('is valid', async () => {
1519+
const promise = loadNumber().then(number => {
1520+
expect(typeof number).toBe('number');
1521+
1522+
return number + 1;
1523+
});
1524+
1525+
expect(promise).toBeInstanceOf(Promise);
1526+
});
1527+
`,
1528+
errors: [
1529+
{
1530+
messageId: 'expectInFloatingPromise',
1531+
line: 2,
1532+
column: 9,
1533+
},
1534+
],
1535+
},
1536+
{
1537+
code: dedent`
1538+
it('is valid', async () => {
1539+
const promise = loadNumber().then(number => {
1540+
expect(typeof number).toBe('number');
1541+
1542+
return number + 1;
1543+
});
1544+
1545+
expect(anotherPromise).resolves.toBe(1);
1546+
});
1547+
`,
1548+
errors: [
1549+
{
1550+
messageId: 'expectInFloatingPromise',
1551+
line: 2,
1552+
column: 9,
1553+
},
1554+
],
1555+
},
14721556
],
14731557
});

src/rules/valid-expect-in-promise.ts

+45-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from '@typescript-eslint/experimental-utils';
55
import {
66
KnownCallExpression,
7+
ModifierName,
78
createRule,
89
getAccessorValue,
910
getNodeName,
@@ -12,6 +13,7 @@ import {
1213
isIdentifier,
1314
isSupportedAccessor,
1415
isTestCaseCall,
16+
parseExpectCall,
1517
} from './utils';
1618

1719
type PromiseChainCallExpression = KnownCallExpression<
@@ -175,6 +177,28 @@ const isValueAwaitedInArguments = (
175177
return false;
176178
};
177179

180+
const getLeftMostCallExpression = (
181+
call: TSESTree.CallExpression,
182+
): TSESTree.CallExpression => {
183+
let leftMostCallExpression: TSESTree.CallExpression = call;
184+
let node: TSESTree.Node = call;
185+
186+
while (node) {
187+
if (node.type === AST_NODE_TYPES.CallExpression) {
188+
leftMostCallExpression = node;
189+
node = node.callee;
190+
}
191+
192+
if (node.type !== AST_NODE_TYPES.MemberExpression) {
193+
break;
194+
}
195+
196+
node = node.object;
197+
}
198+
199+
return leftMostCallExpression;
200+
};
201+
178202
/**
179203
* Attempts to determine if the runtime value represented by the given `identifier`
180204
* is `await`ed or `return`ed within the given `body` of statements
@@ -198,11 +222,27 @@ const isValueAwaitedOrReturned = (
198222

199223
if (node.type === AST_NODE_TYPES.ExpressionStatement) {
200224
// it's possible that we're awaiting the value as an argument
201-
if (
202-
node.expression.type === AST_NODE_TYPES.CallExpression &&
203-
isValueAwaitedInArguments(name, node.expression)
204-
) {
205-
return true;
225+
if (node.expression.type === AST_NODE_TYPES.CallExpression) {
226+
if (isValueAwaitedInArguments(name, node.expression)) {
227+
return true;
228+
}
229+
230+
const leftMostCall = getLeftMostCallExpression(node.expression);
231+
232+
if (
233+
isExpectCall(leftMostCall) &&
234+
leftMostCall.arguments.length > 0 &&
235+
isIdentifier(leftMostCall.arguments[0], name)
236+
) {
237+
const { modifier } = parseExpectCall(leftMostCall);
238+
239+
if (
240+
modifier?.name === ModifierName.resolves ||
241+
modifier?.name === ModifierName.rejects
242+
) {
243+
return true;
244+
}
245+
}
206246
}
207247

208248
if (

0 commit comments

Comments
 (0)