Skip to content

Commit

Permalink
feat(await-async-event): add basic fixer (#656)
Browse files Browse the repository at this point in the history
  • Loading branch information
skovy authored and MichaelDeBoey committed Oct 4, 2022
1 parent b4ce9bb commit b4b5394
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -206,7 +206,7 @@ To enable this configuration use the `extends` property in your

| Name | Description | 🔧 | Included in configurations |
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | --- | ---------------------------------------------------------------------------------- |
| [`await-async-event`](./docs/rules/await-async-event.md) | Enforce promises from async event methods are handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] |
| [`await-async-event`](./docs/rules/await-async-event.md) | Enforce promises from async event methods are handled | 🔧 | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] |
| [`await-async-query`](./docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] |
| [`await-async-utils`](./docs/rules/await-async-utils.md) | Enforce promises from async utils to be awaited properly | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] |
| [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | |
Expand Down
52 changes: 37 additions & 15 deletions lib/rules/await-async-event.ts
@@ -1,11 +1,12 @@
import { ASTUtils, TSESTree } from '@typescript-eslint/utils';
import { ASTUtils, TSESLint, TSESTree } from '@typescript-eslint/utils';

import { createTestingLibraryRule } from '../create-testing-library-rule';
import {
findClosestCallExpressionNode,
getFunctionName,
getInnermostReturningFunction,
getVariableReferences,
isMemberExpression,
isPromiseHandled,
} from '../node-utils';
import { EVENTS_SIMULATORS } from '../utils';
Expand Down Expand Up @@ -41,6 +42,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
awaitAsyncEventWrapper:
'Promise returned from `{{ name }}` wrapper over async event method must be handled',
},
fixable: 'code',
schema: [
{
type: 'object',
Expand Down Expand Up @@ -76,16 +78,23 @@ export default createTestingLibraryRule<Options, MessageIds>({
create(context, [options], helpers) {
const functionWrappersNames: string[] = [];

function reportUnhandledNode(
node: TSESTree.Identifier,
closestCallExpressionNode: TSESTree.CallExpression,
messageId: MessageIds = 'awaitAsyncEvent'
): void {
function reportUnhandledNode({
node,
closestCallExpression,
messageId = 'awaitAsyncEvent',
fix,
}: {
node: TSESTree.Identifier;
closestCallExpression: TSESTree.CallExpression;
messageId?: MessageIds;
fix?: TSESLint.ReportFixFunction;
}): void {
if (!isPromiseHandled(node)) {
context.report({
node: closestCallExpressionNode.callee,
node: closestCallExpression.callee,
messageId,
data: { name: node.name },
fix,
});
}
}
Expand Down Expand Up @@ -128,14 +137,24 @@ export default createTestingLibraryRule<Options, MessageIds>({
);

if (references.length === 0) {
reportUnhandledNode(node, closestCallExpression);
reportUnhandledNode({
node,
closestCallExpression,
fix: (fixer) => {
if (isMemberExpression(node.parent)) {
return fixer.insertTextBefore(node.parent, 'await ');
}

return null;
},
});
} else {
for (const reference of references) {
if (ASTUtils.isIdentifier(reference.identifier)) {
reportUnhandledNode(
reference.identifier,
closestCallExpression
);
reportUnhandledNode({
node: reference.identifier,
closestCallExpression,
});
}
}
}
Expand All @@ -151,11 +170,14 @@ export default createTestingLibraryRule<Options, MessageIds>({
return;
}

reportUnhandledNode(
reportUnhandledNode({
node,
closestCallExpression,
'awaitAsyncEventWrapper'
);
messageId: 'awaitAsyncEventWrapper',
fix: (fixer) => {
return fixer.insertTextBefore(node, 'await ');
},
});
}
},
};
Expand Down
122 changes: 121 additions & 1 deletion tests/lib/rules/await-async-event.test.ts
Expand Up @@ -359,6 +359,12 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent } from '${testingFramework}'
test('unhandled promise from event method is invalid', async () => {
await fireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -380,6 +386,12 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent as testingLibraryFireEvent } from '${testingFramework}'
test('unhandled promise from aliased event method is invalid', async () => {
await testingLibraryFireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -401,6 +413,12 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import * as testingLibrary from '${testingFramework}'
test('unhandled promise from wildcard imported event method is invalid', async () => {
await testingLibrary.fireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
Expand Down Expand Up @@ -428,6 +446,13 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent } from '${testingFramework}'
test('several unhandled promises from event methods is invalid', async () => {
await fireEvent.${eventMethod}(getByLabelText('username'))
await fireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -451,6 +476,12 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent } from '${testingFramework}'
test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => {
await fireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -476,6 +507,14 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent } from 'test-utils'
test(
'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid',
() => {
await fireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -501,6 +540,14 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent } from '${testingFramework}'
test(
'unhandled promise from event method imported from default module with aggressive reporting opted-out is invalid',
() => {
await fireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),

Expand All @@ -524,6 +571,14 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent } from '${testingFramework}'
test(
'unhandled promise from event method kept in a var is invalid',
() => {
const promise = await fireEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -549,6 +604,17 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'fireEvent' }],
output: `
import { fireEvent } from '${testingFramework}'
test('unhandled promise returned from function wrapping event method is invalid', () => {
function triggerEvent() {
doSomething()
return fireEvent.${eventMethod}(getByLabelText('username'))
}
await triggerEvent()
})
`,
} as const)
),
]),
Expand All @@ -572,6 +638,12 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'userEvent' }],
output: `
import userEvent from '${testingFramework}'
test('unhandled promise from event method is invalid', async () => {
await userEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...USER_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -593,6 +665,12 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'userEvent' }],
output: `
import testingLibraryUserEvent from '${testingFramework}'
test('unhandled promise imported from alternate name event method is invalid', async () => {
await testingLibraryUserEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...USER_EVENT_ASYNC_FUNCTIONS.map(
Expand Down Expand Up @@ -620,6 +698,13 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'userEvent' }],
output: `
import userEvent from '${testingFramework}'
test('several unhandled promises from event methods is invalid', async () => {
await userEvent.${eventMethod}(getByLabelText('username'))
await userEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...USER_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -642,6 +727,14 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'userEvent' }],
output: `
import userEvent from '${testingFramework}'
test(
'unhandled promise from event method kept in a var is invalid',
() => {
const promise = await userEvent.${eventMethod}(getByLabelText('username'))
})
`,
} as const)
),
...USER_EVENT_ASYNC_FUNCTIONS.map(
Expand All @@ -667,14 +760,25 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: 'userEvent' }],
output: `
import userEvent from '${testingFramework}'
test('unhandled promise returned from function wrapping event method is invalid', () => {
function triggerEvent() {
doSomething()
return userEvent.${eventMethod}(getByLabelText('username'))
}
await triggerEvent()
})
`,
} as const)
),
]),
{
code: `
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}'
test('unhandled promises from multiple event modules', async () => {
test('unhandled promises from multiple event modules', async () => {
fireEvent.click(getByLabelText('username'))
userEvent.click(getByLabelText('username'))
})
Expand All @@ -694,6 +798,14 @@ ruleTester.run(RULE_NAME, rule, {
},
],
options: [{ eventModule: ['userEvent', 'fireEvent'] }] as Options,
output: `
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}'
test('unhandled promises from multiple event modules', async () => {
await fireEvent.click(getByLabelText('username'))
await userEvent.click(getByLabelText('username'))
})
`,
},
{
code: `
Expand All @@ -712,6 +824,14 @@ ruleTester.run(RULE_NAME, rule, {
data: { name: 'click' },
},
],
output: `
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}'
test('unhandled promise from userEvent relying on default options', async () => {
fireEvent.click(getByLabelText('username'))
await userEvent.click(getByLabelText('username'))
})
`,
},
],
});

0 comments on commit b4b5394

Please sign in to comment.