New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(valid-expect): validate async expect calls #78
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also be a good idea for me to update the valid-expect
documentation to explain these cases. 👍
rules/valid-expect.js
Outdated
return false; | ||
} | ||
const callee = node.expression.callee; | ||
return ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm down with that. Will make coverage a bit easier as well 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have preferences over which dependency to add? get-value
is smaller, although I don't know if that's as big a deal with ESLint plugins (for example, I would be more concerned about adding the entire Lodash dependency for a web app than a command line tool).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no preference 🙂
rules/__tests__/valid-expect.test.js
Outdated
}, | ||
{ | ||
code: | ||
'test("foo", async () => expect(Promise.reject(2)).rejects.toBeDefined());', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is technically valid, since this arrow function implicitly returns the expect()
statement. However, the test()
callback function is async, so I figured we could warn when you're using async
without await
in this case.
Thoughts on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that should be fine as a separate rule.
https://github.com/avajs/eslint-plugin-ava/blob/master/docs/rules/no-async-fn-without-await.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I can remove that check from this rule then, and we can open an issue for another rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #81 so this wouldn't get lost
rules/__tests__/valid-expect.test.js
Outdated
'test("foo", () => expect(Promise.resolve(2)).resolves.toBeDefined());', | ||
'test("foo", async () => await expect(Promise.reject(2)).rejects.toBeDefined());', | ||
'test("foo", async () => { expect(await Promise.resolve(2)).resolves.toBeDefined(); });', | ||
'test("foo", async () => { expect(await Promise.reject(2)).rejects.toBeDefined(); });', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the comments in #54 (comment), I might remove the logic surrounding these test cases, as it's not valid to await the Promise inside expect()
and also use resolves
or rejects
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll work, it just doesn't make any sense. So I think we should warn in that case as you're doing currently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so if anything it should be added to invalid
🙂
2b1f60c
to
4fa5083
Compare
conflicts again, sorry |
Thank you for the review, @SimenB - I'll fix the conflicts and make the necessary changes today. |
4fa5083
to
49f3b1b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the test cases and documentation. This is now ready for review.
Please let me know if I should refactor anything for naming or extract any functions (specifically the filter()
callback functions). I inlined everything while I was addressing PR feedback to help me better see the flow of the code I wrote 😂
return { | ||
CallExpression(node) { | ||
if (util.isTestCase(node)) { | ||
validateAsyncExpects(node); | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are in a test case function, there is no need to proceed, since the node.callee.name
will never be expect
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome, thank you so much for tackling it!
return ( | ||
node.expression.type === 'CallExpression' && | ||
objectName === 'expect' && | ||
(propertyName === 'resolves' || propertyName === 'rejects') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can reuse the logic somehow with the other resolves
/rejects
checks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. I'll look into that while resolving the failing test case you reported below.
{ | ||
code: | ||
'test("foo", () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });', | ||
errors: [{ message: "Must return or await 'expect.resolves' statement" }], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you assert on the locations as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, will make those changes 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this should be expanded to support .not
.
This test fails:
{
code:
'test("foo", async () => { expect(await Promise.reject(undefined)).rejects.not.toBeDefined(); });',
errors: [
{ message: "Cannot use 'rejects' with an awaited expect expression" },
],
},
49f3b1b
to
deb8016
Compare
I don't get emails when people push to PRs I'm reviewing for some reason 🙁 So please ping me when this is ready |
Will do. I need to sit down this week and give this PR a solid look to see
how we can share logic between the current behavior and the new behavior
I’m introducing.
…On Sun, Feb 25, 2018 at 5:00 PM Simen Bekkhus ***@***.***> wrote:
I don't get emails when people push to PRs I'm reviewing for some reason
🙁 So please ping me when this is ready
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#78 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ACPEyZf4h27F9AZVXDmSdYRYrn5_e_pxks5tYdgIgaJpZM4SEM-b>
.
|
@SimenB I'm in-between jobs and moving right now and probably won't be available to contribute again until late March/early April. Thoughts on what to do with this PR? Just leave it open for now? |
@macklinu there is no rush, I think it's fine to leave it open. |
I may be able to re-approach this PR by using Here is a simplified code example to show how to check if an const rule = {
CallExpression(node) {
if (
node.callee.name === "expect" &&
context
.getAncestors()
.some(
n =>
n.type === "ReturnStatement" &&
n.argument.callee.object.object === node
)
) {
// expect() was returned
}
}
}; I think I'll be able to hack on this later today / this weekend - will see where I end up. 😄 |
deb8016
to
d096af7
Compare
I'm not sure this is the right approach, and I haven't been able to take a look for quite some time. Closing for now. |
Resolves #54
I first tried working my way up the tree - to find a parent node of an
expect()
call that was aReturnStatement
orAwaitStatement
and then finding the function that contained theexpect()
call, but I had trouble thinking through how to do that with the currentvalid-expect
rule code. I ended up checking for a test caseCallExpression
and working my way down the tree - into the body of the callback function:One downside is that this results in some really deeply nested property accessing (like
node.expression.callee.object.object.arguments[0].type
).Am I approaching this the right way? Feedback is certainly welcome on how to improve this.