Skip to content

Commit 8f9a93c

Browse files
neriyardenMichaelDeBoey
authored andcommitted
feat: fix promise wrappers
1 parent d3af7bf commit 8f9a93c

File tree

2 files changed

+108
-3
lines changed

2 files changed

+108
-3
lines changed

lib/rules/await-async-queries.ts

+40
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/utils';
33
import { createTestingLibraryRule } from '../create-testing-library-rule';
44
import {
55
findClosestCallExpressionNode,
6+
findClosestFunctionExpressionNode,
67
getDeepestIdentifierNode,
78
getFunctionName,
89
getInnermostReturningFunction,
@@ -136,6 +137,45 @@ export default createTestingLibraryRule<Options, MessageIds>({
136137
node: identifierNode,
137138
messageId: 'asyncQueryWrapper',
138139
data: { name: identifierNode.name },
140+
fix: (fixer) => {
141+
const functionExpression =
142+
findClosestFunctionExpressionNode(node);
143+
144+
if (!functionExpression) return null;
145+
146+
let IdentifierNodeFixer;
147+
// If the wrapper is a property of an object,
148+
// add 'await' before the object, e.g.:
149+
// const obj = { wrapper: () => screen.findByText(/foo/i) };
150+
// await obj.wrapper();
151+
if (isMemberExpression(identifierNode.parent)) {
152+
IdentifierNodeFixer = fixer.insertTextBefore(
153+
identifierNode.parent,
154+
'await '
155+
);
156+
// Otherwise, add 'await' before the wrapper function, e.g.:
157+
// const wrapper = () => screen.findByText(/foo/i);
158+
// await wrapper();
159+
} else {
160+
IdentifierNodeFixer = fixer.insertTextBefore(
161+
identifierNode,
162+
'await '
163+
);
164+
}
165+
166+
if (functionExpression.async) {
167+
return IdentifierNodeFixer;
168+
} else {
169+
// Mutate the actual node so if other nodes exist in this
170+
// function expression body they don't also try to fix it.
171+
functionExpression.async = true;
172+
173+
return [
174+
IdentifierNodeFixer,
175+
fixer.insertTextBefore(functionExpression, 'async '),
176+
];
177+
}
178+
},
139179
});
140180
}
141181
},

tests/lib/rules/await-async-queries.test.ts

+68-3
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,26 @@ ruleTester.run(RULE_NAME, rule, {
497497
const element = queryWrapper()
498498
})
499499
500-
test("An invalid example test", async () => {
500+
test("A valid example test", async () => {
501501
const element = await queryWrapper()
502502
})
503503
`,
504504
errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }],
505+
output: `
506+
function queryWrapper() {
507+
doSomethingElse();
508+
509+
return screen.${query}('foo')
510+
}
511+
512+
test("An invalid example test", async () => {
513+
const element = await queryWrapper()
514+
})
515+
516+
test("A valid example test", async () => {
517+
const element = await queryWrapper()
518+
})
519+
`,
505520
} as const)
506521
),
507522
// unhandled promise from async query arrow function wrapper is invalid
@@ -519,11 +534,26 @@ ruleTester.run(RULE_NAME, rule, {
519534
const element = queryWrapper()
520535
})
521536
522-
test("An invalid example test", async () => {
537+
test("A valid example test", async () => {
523538
const element = await queryWrapper()
524539
})
525540
`,
526541
errors: [{ messageId: 'asyncQueryWrapper', line: 9, column: 27 }],
542+
output: `
543+
const queryWrapper = () => {
544+
doSomethingElse();
545+
546+
return ${query}('foo')
547+
}
548+
549+
test("An invalid example test", async () => {
550+
const element = await queryWrapper()
551+
})
552+
553+
test("A valid example test", async () => {
554+
const element = await queryWrapper()
555+
})
556+
`,
527557
} as const)
528558
),
529559
// unhandled promise implicitly returned from async query arrow function wrapper is invalid
@@ -537,11 +567,22 @@ ruleTester.run(RULE_NAME, rule, {
537567
const element = queryWrapper()
538568
})
539569
540-
test("An invalid example test", async () => {
570+
test("A valid example test", async () => {
541571
const element = await queryWrapper()
542572
})
543573
`,
544574
errors: [{ messageId: 'asyncQueryWrapper', line: 5, column: 27 }],
575+
output: `
576+
const queryWrapper = () => screen.${query}('foo')
577+
578+
test("An invalid example test", async () => {
579+
const element = await queryWrapper()
580+
})
581+
582+
test("A valid example test", async () => {
583+
const element = await queryWrapper()
584+
})
585+
`,
545586
} as const)
546587
),
547588

@@ -589,6 +630,30 @@ ruleTester.run(RULE_NAME, rule, {
589630
})
590631
`,
591632
errors: [{ messageId: 'asyncQueryWrapper', line: 19, column: 34 }],
633+
output: `// similar to issue #359 but forcing an error in no-awaited wrapper
634+
import { render, screen } from 'mocks/test-utils'
635+
import userEvent from '@testing-library/user-event'
636+
637+
const testData = {
638+
name: 'John Doe',
639+
640+
password: 'extremeSecret',
641+
}
642+
643+
const selectors = {
644+
username: () => screen.findByRole('textbox', { name: /username/i }),
645+
email: () => screen.findByRole('textbox', { name: /e-mail/i }),
646+
password: () => screen.findByLabelText(/password/i),
647+
}
648+
649+
test('this is a valid case', async () => {
650+
render(<SomeComponent />)
651+
userEvent.type(await selectors.username(), testData.name) // <-- unhandled here
652+
userEvent.type(await selectors.email(), testData.email)
653+
userEvent.type(await selectors.password(), testData.password)
654+
// ...
655+
})
656+
`,
592657
},
593658
],
594659
});

0 commit comments

Comments
 (0)