diff --git a/README.md b/README.md index a7da8c41..ff2ae87a 100644 --- a/README.md +++ b/README.md @@ -206,9 +206,9 @@ 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-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][] | -| [`await-fire-event`](./docs/rules/await-fire-event.md) | Enforce promises from `fireEvent` methods to be handled | | ![vue-badge][] ![marko-badge][] | | [`consistent-data-testid`](./docs/rules/consistent-data-testid.md) | Ensures consistent usage of `data-testid` | | | | [`no-await-sync-events`](./docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | | | [`no-await-sync-query`](./docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] ![marko-badge][] | diff --git a/docs/rules/await-async-event.md b/docs/rules/await-async-event.md new file mode 100644 index 00000000..ef555409 --- /dev/null +++ b/docs/rules/await-async-event.md @@ -0,0 +1,143 @@ +# Enforce promises from async event methods are handled (`testing-library/await-async-event`) + +Ensure that promises returned by `userEvent` (v14+) async methods or `fireEvent` (only Vue and Marko) async methods are handled properly. + +## Rule Details + +This rule aims to prevent users from forgetting to handle promise returned from async event +methods. + +> ⚠️ `fireEvent` methods are async only on following Testing Library packages: +> +> - `@testing-library/vue` (supported by this plugin) +> - `@testing-library/svelte` (not supported yet by this plugin) +> - `@marko/testing-library` (supported by this plugin) + +Examples of **incorrect** code for this rule: + +```js +fireEvent.click(getByText('Click me')); + +fireEvent.focus(getByLabelText('username')); +fireEvent.blur(getByLabelText('username')); + +// wrap a fireEvent method within a function... +function triggerEvent() { + return fireEvent.click(button); +} +triggerEvent(); // ...but not handling promise from it is incorrect too +``` + +```js +userEvent.click(getByText('Click me')); +userEvent.tripleClick(getByText('Click me')); +userEvent.keyboard('foo'); + +// wrap a userEvent method within a function... +function triggerEvent() { + return userEvent.click(button); +} +triggerEvent(); // ...but not handling promise from it is incorrect too +``` + +Examples of **correct** code for this rule: + +```js +// `await` operator is correct +await fireEvent.focus(getByLabelText('username')); +await fireEvent.blur(getByLabelText('username')); + +// `then` method is correct +fireEvent.click(getByText('Click me')).then(() => { + // ... +}); + +// return the promise within a function is correct too! +const clickMeArrowFn = () => fireEvent.click(getByText('Click me')); + +// wrap a fireEvent method within a function... +function triggerEvent() { + return fireEvent.click(button); +} +await triggerEvent(); // ...and handling promise from it is correct also + +// using `Promise.all` or `Promise.allSettled` with an array of promises is valid +await Promise.all([ + fireEvent.focus(getByLabelText('username')), + fireEvent.blur(getByLabelText('username')), +]); +``` + +```js +// `await` operator is correct +await userEvent.click(getByText('Click me')); +await userEvent.tripleClick(getByText('Click me')); + +// `then` method is correct +userEvent.keyboard('foo').then(() => { + // ... +}); + +// return the promise within a function is correct too! +const clickMeArrowFn = () => userEvent.click(getByText('Click me')); + +// wrap a userEvent method within a function... +function triggerEvent() { + return userEvent.click(button); +} +await triggerEvent(); // ...and handling promise from it is correct also + +// using `Promise.all` or `Promise.allSettled` with an array of promises is valid +await Promise.all([ + userEvent.click(getByText('Click me')); + userEvent.tripleClick(getByText('Click me')); +]); +``` + +## Options + +- `eventModule`: `string` or `string[]`. Which event module should be linted for async event methods. Defaults to `userEvent` which should be used after v14. `fireEvent` should only be used with frameworks that have async fire event methods. + +## Example + +```json +{ + "testing-library/await-async-event": [ + 2, + { + "eventModule": "userEvent" + } + ] +} +``` + +```json +{ + "testing-library/await-async-event": [ + 2, + { + "eventModule": "fireEvent" + } + ] +} +``` + +```json +{ + "testing-library/await-async-event": [ + 2, + { + "eventModule": ["fireEvent", "userEvent"] + } + ] +} +``` + +## When Not To Use It + +- `userEvent` is below v14, before all event methods are async +- `fireEvent` methods are sync for most Testing Library packages. If you are not using Testing Library package with async events, you shouldn't use this rule. + +## Further Reading + +- [Vue Testing Library fireEvent](https://testing-library.com/docs/vue-testing-library/api#fireevent) diff --git a/docs/rules/await-fire-event.md b/docs/rules/await-fire-event.md deleted file mode 100644 index 54643c60..00000000 --- a/docs/rules/await-fire-event.md +++ /dev/null @@ -1,66 +0,0 @@ -# Enforce promises from fire event methods to be handled (`testing-library/await-fire-event`) - -Ensure that promises returned by `fireEvent` methods are handled -properly. - -## Rule Details - -This rule aims to prevent users from forgetting to handle promise returned from `fireEvent` -methods. - -> ⚠️ `fireEvent` methods are async only on following Testing Library packages: -> -> - `@testing-library/vue` (supported by this plugin) -> - `@testing-library/svelte` (not supported yet by this plugin) -> - `@marko/testing-library` (supported by this plugin) - -Examples of **incorrect** code for this rule: - -```js -fireEvent.click(getByText('Click me')); - -fireEvent.focus(getByLabelText('username')); -fireEvent.blur(getByLabelText('username')); - -// wrap a fireEvent method within a function... -function triggerEvent() { - return fireEvent.click(button); -} -triggerEvent(); // ...but not handling promise from it is incorrect too -``` - -Examples of **correct** code for this rule: - -```js -// `await` operator is correct -await fireEvent.focus(getByLabelText('username')); -await fireEvent.blur(getByLabelText('username')); - -// `then` method is correct -fireEvent.click(getByText('Click me')).then(() => { - // ... -}); - -// return the promise within a function is correct too! -const clickMeArrowFn = () => fireEvent.click(getByText('Click me')); - -// wrap a fireEvent method within a function... -function triggerEvent() { - return fireEvent.click(button); -} -await triggerEvent(); // ...and handling promise from it is correct also - -// using `Promise.all` or `Promise.allSettled` with an array of promises is valid -await Promise.all([ - fireEvent.focus(getByLabelText('username')), - fireEvent.blur(getByLabelText('username')), -]); -``` - -## When Not To Use It - -`fireEvent` methods are not async on all Testing Library packages. If you are not using Testing Library package with async fire event, you shouldn't use this rule. - -## Further Reading - -- [Vue Testing Library fireEvent](https://testing-library.com/docs/vue-testing-library/api#fireevent) diff --git a/docs/rules/no-await-sync-events.md b/docs/rules/no-await-sync-events.md index ae891577..39cfdf45 100644 --- a/docs/rules/no-await-sync-events.md +++ b/docs/rules/no-await-sync-events.md @@ -105,5 +105,4 @@ Example: ## Notes - Since `user-event` v14 all its methods are async, so you should disable reporting them by setting the `eventModules` to just `"fire-event"` so `user-event` methods are not reported. -- There is another rule `await-fire-event`, which is only in Vue Testing - Library. Please do not confuse with this rule. +- There is another rule `await-async-event`, which is for awaiting async events for `user-event` v14 or `fire-event` only in Vue Testing Library. Please do not confuse with this rule. diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index b19f7397..e0a14a8b 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -5,6 +5,10 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: 'userEvent' }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index cad0f7f4..13fbc8d7 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -5,6 +5,10 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: 'userEvent' }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', diff --git a/lib/configs/marko.ts b/lib/configs/marko.ts index d4f5f427..fbbc67a4 100644 --- a/lib/configs/marko.ts +++ b/lib/configs/marko.ts @@ -5,9 +5,12 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: ['fireEvent', 'userEvent'] }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/await-fire-event': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index e9beeb6f..e52476fc 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -5,6 +5,10 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: 'userEvent' }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', 'testing-library/no-await-sync-query': 'error', diff --git a/lib/configs/vue.ts b/lib/configs/vue.ts index e2ff94a6..bb4b3a21 100644 --- a/lib/configs/vue.ts +++ b/lib/configs/vue.ts @@ -5,9 +5,12 @@ export = { plugins: ['testing-library'], rules: { + 'testing-library/await-async-event': [ + 'error', + { eventModule: ['fireEvent', 'userEvent'] }, + ], 'testing-library/await-async-query': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/await-fire-event': 'error', 'testing-library/no-await-sync-query': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'error', diff --git a/lib/node-utils/index.ts b/lib/node-utils/index.ts index 33b8cce4..2e805ab0 100644 --- a/lib/node-utils/index.ts +++ b/lib/node-utils/index.ts @@ -234,7 +234,7 @@ export function isPromiseHandled(nodeIdentifier: TSESTree.Identifier): boolean { } export function getVariableReferences( - context: TSESLint.RuleContext, + context: TSESLint.RuleContext, node: TSESTree.Node ): TSESLint.Scope.Reference[] { if (ASTUtils.isVariableDeclarator(node)) { diff --git a/lib/rules/await-async-event.ts b/lib/rules/await-async-event.ts new file mode 100644 index 00000000..0ad2e433 --- /dev/null +++ b/lib/rules/await-async-event.ts @@ -0,0 +1,163 @@ +import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; + +import { createTestingLibraryRule } from '../create-testing-library-rule'; +import { + findClosestCallExpressionNode, + getFunctionName, + getInnermostReturningFunction, + getVariableReferences, + isPromiseHandled, +} from '../node-utils'; +import { EVENTS_SIMULATORS } from '../utils'; + +export const RULE_NAME = 'await-async-event'; +export type MessageIds = 'awaitAsyncEvent' | 'awaitAsyncEventWrapper'; +const FIRE_EVENT_NAME = 'fireEvent'; +const USER_EVENT_NAME = 'userEvent'; +type EventModules = typeof EVENTS_SIMULATORS[number]; +export type Options = [ + { + eventModule: EventModules | EventModules[]; + } +]; + +export default createTestingLibraryRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Enforce promises from async event methods are handled', + recommendedConfig: { + dom: ['error', { eventModule: 'userEvent' }], + angular: ['error', { eventModule: 'userEvent' }], + react: ['error', { eventModule: 'userEvent' }], + vue: ['error', { eventModule: ['fireEvent', 'userEvent'] }], + marko: ['error', { eventModule: ['fireEvent', 'userEvent'] }], + }, + }, + messages: { + awaitAsyncEvent: + 'Promise returned from async event method `{{ name }}` must be handled', + awaitAsyncEventWrapper: + 'Promise returned from `{{ name }}` wrapper over async event method must be handled', + }, + schema: [ + { + type: 'object', + default: {}, + additionalProperties: false, + properties: { + eventModule: { + default: USER_EVENT_NAME, + oneOf: [ + { + type: 'string', + enum: EVENTS_SIMULATORS, + }, + { + type: 'array', + items: { + type: 'string', + enum: EVENTS_SIMULATORS, + }, + }, + ], + }, + }, + }, + ], + }, + defaultOptions: [ + { + eventModule: USER_EVENT_NAME, + }, + ], + + create(context, [options], helpers) { + const functionWrappersNames: string[] = []; + + function reportUnhandledNode( + node: TSESTree.Identifier, + closestCallExpressionNode: TSESTree.CallExpression, + messageId: MessageIds = 'awaitAsyncEvent' + ): void { + if (!isPromiseHandled(node)) { + context.report({ + node: closestCallExpressionNode.callee, + messageId, + data: { name: node.name }, + }); + } + } + + function detectEventMethodWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + functionWrappersNames.push(getFunctionName(innerFunction)); + } + } + + const eventModules = + typeof options.eventModule === 'string' + ? [options.eventModule] + : options.eventModule; + const isFireEventEnabled = eventModules.includes(FIRE_EVENT_NAME); + const isUserEventEnabled = eventModules.includes(USER_EVENT_NAME); + + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if ( + (isFireEventEnabled && helpers.isFireEventMethod(node)) || + (isUserEventEnabled && helpers.isUserEventMethod(node)) + ) { + detectEventMethodWrapper(node); + + const closestCallExpression = findClosestCallExpressionNode( + node, + true + ); + + if (!closestCallExpression || !closestCallExpression.parent) { + return; + } + + const references = getVariableReferences( + context, + closestCallExpression.parent + ); + + if (references.length === 0) { + reportUnhandledNode(node, closestCallExpression); + } else { + for (const reference of references) { + if (ASTUtils.isIdentifier(reference.identifier)) { + reportUnhandledNode( + reference.identifier, + closestCallExpression + ); + } + } + } + } else if (functionWrappersNames.includes(node.name)) { + // report promise returned from function wrapping fire event method + // previously detected + const closestCallExpression = findClosestCallExpressionNode( + node, + true + ); + + if (!closestCallExpression) { + return; + } + + reportUnhandledNode( + node, + closestCallExpression, + 'awaitAsyncEventWrapper' + ); + } + }, + }; + }, +}); diff --git a/lib/rules/await-fire-event.ts b/lib/rules/await-fire-event.ts deleted file mode 100644 index bfc298e0..00000000 --- a/lib/rules/await-fire-event.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { ASTUtils, TSESTree } from '@typescript-eslint/utils'; - -import { createTestingLibraryRule } from '../create-testing-library-rule'; -import { - findClosestCallExpressionNode, - getFunctionName, - getInnermostReturningFunction, - getVariableReferences, - isPromiseHandled, -} from '../node-utils'; - -export const RULE_NAME = 'await-fire-event'; -export type MessageIds = 'awaitFireEvent' | 'fireEventWrapper'; -type Options = []; - -export default createTestingLibraryRule({ - name: RULE_NAME, - meta: { - type: 'problem', - docs: { - description: 'Enforce promises from `fireEvent` methods to be handled', - recommendedConfig: { - dom: false, - angular: false, - react: false, - vue: 'error', - marko: 'error', - }, - }, - messages: { - awaitFireEvent: - 'Promise returned from `fireEvent.{{ name }}` must be handled', - fireEventWrapper: - 'Promise returned from `{{ name }}` wrapper over fire event method must be handled', - }, - schema: [], - }, - defaultOptions: [], - - create(context, _, helpers) { - const functionWrappersNames: string[] = []; - - function reportUnhandledNode( - node: TSESTree.Identifier, - closestCallExpressionNode: TSESTree.CallExpression, - messageId: MessageIds = 'awaitFireEvent' - ): void { - if (!isPromiseHandled(node)) { - context.report({ - node: closestCallExpressionNode.callee, - messageId, - data: { name: node.name }, - }); - } - } - - function detectFireEventMethodWrapper(node: TSESTree.Identifier): void { - const innerFunction = getInnermostReturningFunction(context, node); - - if (innerFunction) { - functionWrappersNames.push(getFunctionName(innerFunction)); - } - } - - return { - 'CallExpression Identifier'(node: TSESTree.Identifier) { - if (helpers.isFireEventMethod(node)) { - detectFireEventMethodWrapper(node); - - const closestCallExpression = findClosestCallExpressionNode( - node, - true - ); - - if (!closestCallExpression || !closestCallExpression.parent) { - return; - } - - const references = getVariableReferences( - context, - closestCallExpression.parent - ); - - if (references.length === 0) { - reportUnhandledNode(node, closestCallExpression); - } else { - for (const reference of references) { - if (ASTUtils.isIdentifier(reference.identifier)) { - reportUnhandledNode( - reference.identifier, - closestCallExpression - ); - } - } - } - } else if (functionWrappersNames.includes(node.name)) { - // report promise returned from function wrapping fire event method - // previously detected - const closestCallExpression = findClosestCallExpressionNode( - node, - true - ); - - if (!closestCallExpression) { - return; - } - - reportUnhandledNode(node, closestCallExpression, 'fireEventWrapper'); - } - }, - }; - }, -}); diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index f608f04d..4dc40ef7 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -7,6 +7,12 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": "userEvent", + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", @@ -36,6 +42,12 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": "userEvent", + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", @@ -56,9 +68,17 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": Array [ + "fireEvent", + "userEvent", + ], + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/await-fire-event": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "error", @@ -87,6 +107,12 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": "userEvent", + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", "testing-library/no-await-sync-query": "error", @@ -118,9 +144,17 @@ Object { "testing-library", ], "rules": Object { + "testing-library/await-async-event": Array [ + "error", + Object { + "eventModule": Array [ + "fireEvent", + "userEvent", + ], + }, + ], "testing-library/await-async-query": "error", "testing-library/await-async-utils": "error", - "testing-library/await-fire-event": "error", "testing-library/no-await-sync-query": "error", "testing-library/no-container": "error", "testing-library/no-debugging-utils": "error", diff --git a/tests/lib/rules/await-async-event.test.ts b/tests/lib/rules/await-async-event.test.ts new file mode 100644 index 00000000..54718d24 --- /dev/null +++ b/tests/lib/rules/await-async-event.test.ts @@ -0,0 +1,717 @@ +import rule, { Options, RULE_NAME } from '../../../lib/rules/await-async-event'; +import { createRuleTester } from '../test-utils'; + +const ruleTester = createRuleTester(); + +const FIRE_EVENT_ASYNC_FUNCTIONS = [ + 'click', + 'change', + 'focus', + 'blur', + 'keyDown', +] as const; +const USER_EVENT_ASYNC_FUNCTIONS = [ + 'click', + 'dblClick', + 'tripleClick', + 'hover', + 'unhover', + 'tab', + 'keyboard', + 'copy', + 'cut', + 'paste', + 'pointer', + 'clear', + 'deselectOptions', + 'selectOptions', + 'type', + 'upload', +] as const; +const FIRE_EVENT_ASYNC_FRAMEWORKS = [ + '@testing-library/vue', + '@marko/testing-library', +] as const; +const USER_EVENT_ASYNC_FRAMEWORKS = ['@testing-library/user-event'] as const; + +ruleTester.run(RULE_NAME, rule, { + valid: [ + ...FIRE_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('event method not called is valid', () => { + fireEvent.${eventMethod} + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await promise from event method is valid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await several promises from event methods is valid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await promise kept in a var from event method is valid', async () => { + const promise = fireEvent.${eventMethod}(getByLabelText('username')) + await promise + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('chain then method to promise from event method is valid', async (done) => { + fireEvent.${eventMethod}(getByLabelText('username')) + .then(() => { done() }) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('chain then method to several promises from event methods is valid', async (done) => { + fireEvent.${eventMethod}(getByLabelText('username')).then(() => { + fireEvent.${eventMethod}(getByLabelText('username')).then(() => { done() }) + }) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + { + code: ` + import { fireEvent } from '${testingFramework}' + test('event methods wrapped with Promise.all are valid', async () => { + await Promise.all([ + fireEvent.${FIRE_EVENT_ASYNC_FUNCTIONS[0]}(getByText('Click me')), + fireEvent.${FIRE_EVENT_ASYNC_FUNCTIONS[1]}(getByText('Click me')), + ]) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + }, + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('return promise from event methods is valid', () => { + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('await promise returned from function wrapping event method is valid', () => { + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + + await triggerEvent() + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from 'somewhere-else' + test('unhandled promise from event not related to TL is valid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from 'test-utils' + test('await promise from event method imported from custom module is valid', async () => { + await fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + })), + { + // edge case for coverage: + // valid use case without call expression + // so there is no innermost function scope found + code: ` + import { fireEvent } from 'test-utils' + test('edge case for innermost function without call expression', async () => { + function triggerEvent() { + doSomething() + return fireEvent.focus(getByLabelText('username')) + } + + const reassignedFunction = triggerEvent + }) + `, + options: [{ eventModule: 'fireEvent' }] as const, + }, + ]), + + ...USER_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('event method not called is valid', () => { + userEvent.${eventMethod} + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise from event method is valid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await several promises from event methods is valid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise kept in a var from event method is valid', async () => { + const promise = userEvent.${eventMethod}(getByLabelText('username')) + await promise + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('chain then method to promise from event method is valid', async (done) => { + userEvent.${eventMethod}(getByLabelText('username')) + .then(() => { done() }) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('chain then method to several promises from event methods is valid', async (done) => { + userEvent.${eventMethod}(getByLabelText('username')).then(() => { + userEvent.${eventMethod}(getByLabelText('username')).then(() => { done() }) + }) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + { + code: ` + import userEvent from '${testingFramework}' + test('event methods wrapped with Promise.all are valid', async () => { + await Promise.all([ + userEvent.${USER_EVENT_ASYNC_FUNCTIONS[0]}(getByText('Click me')), + userEvent.${USER_EVENT_ASYNC_FUNCTIONS[1]}(getByText('Click me')), + ]) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + }, + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('return promise from event methods is valid', () => { + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise returned from function wrapping event method is valid', () => { + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + + await triggerEvent() + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import userEvent from 'somewhere-else' + test('unhandled promise from event not related to TL is valid', async () => { + userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import userEvent from 'test-utils' + test('await promise from event method imported from custom module is valid', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + })), + ...USER_EVENT_ASYNC_FUNCTIONS.map((eventMethod) => ({ + code: ` + import userEvent from '${testingFramework}' + test('await promise from userEvent relying on default options', async () => { + await userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + })), + { + // edge case for coverage: + // valid use case without call expression + // so there is no innermost function scope found + code: ` + import userEvent from 'test-utils' + test('edge case for innermost function without call expression', async () => { + function triggerEvent() { + doSomething() + return userEvent.focus(getByLabelText('username')) + } + + const reassignedFunction = triggerEvent + }) + `, + options: [{ eventModule: 'userEvent' }] as const, + }, + { + code: ` + import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}' + import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}' + test('await promises from multiple event modules', async () => { + await fireEvent.click(getByLabelText('username')) + await userEvent.click(getByLabelText('username')) + }) + `, + options: [{ eventModule: ['userEvent', 'fireEvent'] }] as Options, + }, + ]), + ], + + invalid: [ + ...FIRE_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise from event method is invalid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 19 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent as testingLibraryFireEvent } from '${testingFramework}' + test('unhandled promise from aliased event method is invalid', async () => { + testingLibraryFireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 33 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import * as testingLibrary from '${testingFramework}' + test('unhandled promise from wildcard imported event method is invalid', async () => { + testingLibrary.fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 34 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('several unhandled promises from event methods is invalid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + { + line: 5, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from 'test-utils' + test( + 'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid', + () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + settings: { + 'testing-library/utils-module': 'test-utils', + }, + code: ` + import { fireEvent } from '${testingFramework}' + test( + 'unhandled promise from event method imported from default module with aggressive reporting opted-out is invalid', + () => { + fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test( + 'unhandled promise from event method kept in a var is invalid', + () => { + const promise = fireEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 25, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ...FIRE_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import { fireEvent } from '${testingFramework}' + test('unhandled promise returned from function wrapping event method is invalid', () => { + function triggerEvent() { + doSomething() + return fireEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() + }) + `, + errors: [ + { + line: 9, + column: 9, + messageId: 'awaitAsyncEventWrapper', + data: { name: 'triggerEvent' }, + }, + ], + options: [{ eventModule: 'fireEvent' }], + } as const) + ), + ]), + ...USER_EVENT_ASYNC_FRAMEWORKS.flatMap((testingFramework) => [ + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test('unhandled promise from event method is invalid', async () => { + userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 19 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import testingLibraryUserEvent from '${testingFramework}' + test('unhandled promise imported from alternate name event method is invalid', async () => { + testingLibraryUserEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + endColumn: 33 + eventMethod.length, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test('several unhandled promises from event methods is invalid', async () => { + userEvent.${eventMethod}(getByLabelText('username')) + userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 4, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + { + line: 5, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test( + 'unhandled promise from event method kept in a var is invalid', + () => { + const promise = userEvent.${eventMethod}(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 25, + messageId: 'awaitAsyncEvent', + data: { name: eventMethod }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } as const) + ), + ...USER_EVENT_ASYNC_FUNCTIONS.map( + (eventMethod) => + ({ + code: ` + import userEvent from '${testingFramework}' + test('unhandled promise returned from function wrapping event method is invalid', () => { + function triggerEvent() { + doSomething() + return userEvent.${eventMethod}(getByLabelText('username')) + } + + triggerEvent() + }) + `, + errors: [ + { + line: 9, + column: 9, + messageId: 'awaitAsyncEventWrapper', + data: { name: 'triggerEvent' }, + }, + ], + options: [{ eventModule: 'userEvent' }], + } 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 () => { + fireEvent.click(getByLabelText('username')) + userEvent.click(getByLabelText('username')) + }) + `, + errors: [ + { + line: 5, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: 'click' }, + }, + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: 'click' }, + }, + ], + options: [{ eventModule: ['userEvent', 'fireEvent'] }] as Options, + }, + { + code: ` + 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')) + userEvent.click(getByLabelText('username')) + }) + `, + errors: [ + { + line: 6, + column: 9, + messageId: 'awaitAsyncEvent', + data: { name: 'click' }, + }, + ], + }, + ], +}); diff --git a/tests/lib/rules/await-fire-event.test.ts b/tests/lib/rules/await-fire-event.test.ts deleted file mode 100644 index 62b860e2..00000000 --- a/tests/lib/rules/await-fire-event.test.ts +++ /dev/null @@ -1,353 +0,0 @@ -import rule, { RULE_NAME } from '../../../lib/rules/await-fire-event'; -import { createRuleTester } from '../test-utils'; - -const ruleTester = createRuleTester(); - -const COMMON_FIRE_EVENT_METHODS: string[] = [ - 'click', - 'change', - 'focus', - 'blur', - 'keyDown', -]; -const SUPPORTED_TESTING_FRAMEWORKS = [ - '@testing-library/vue', - '@marko/testing-library', -]; - -ruleTester.run(RULE_NAME, rule, { - valid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('fire event method not called is valid', () => { - fireEvent.${fireEventMethod} - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await promise from fire event method is valid', async () => { - await fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await several promises from fire event methods is valid', async () => { - await fireEvent.${fireEventMethod}(getByLabelText('username')) - await fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await promise kept in a var from fire event method is valid', async () => { - const promise = fireEvent.${fireEventMethod}(getByLabelText('username')) - await promise - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('chain then method to promise from fire event method is valid', async (done) => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - .then(() => { done() }) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('chain then method to several promises from fire event methods is valid', async (done) => { - fireEvent.${fireEventMethod}(getByLabelText('username')).then(() => { - fireEvent.${fireEventMethod}(getByLabelText('username')).then(() => { done() }) - }) - }) - `, - })), - { - code: ` - import { fireEvent } from '${testingFramework}' - test('fireEvent methods wrapped with Promise.all are valid', async () => { - await Promise.all([ - fireEvent.blur(getByText('Click me')), - fireEvent.click(getByText('Click me')), - ]) - }) - `, - }, - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('return promise from fire event methods is valid', () => { - function triggerEvent() { - doSomething() - return fireEvent.${fireEventMethod}(getByLabelText('username')) - } - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('await promise returned from function wrapping fire event method is valid', () => { - function triggerEvent() { - doSomething() - return fireEvent.${fireEventMethod}(getByLabelText('username')) - } - - await triggerEvent() - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from 'somewhere-else' - test('unhandled promise from fire event not related to TL is valid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - ...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from 'test-utils' - test('await promise from fire event method imported from custom module is valid', async () => { - await fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - })), - - { - // edge case for coverage: - // valid use case without call expression - // so there is no innermost function scope found - code: ` - import { fireEvent } from 'test-utils' - test('edge case for innermost function without call expression', async () => { - function triggerEvent() { - doSomething() - return fireEvent.focus(getByLabelText('username')) - } - - const reassignedFunction = triggerEvent - }) - `, - }, - ]), - - invalid: SUPPORTED_TESTING_FRAMEWORKS.flatMap((testingFramework) => [ - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('unhandled promise from fire event method is invalid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - endColumn: 19 + fireEventMethod.length, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent as testingLibraryFireEvent } from '${testingFramework}' - test('unhandled promise from aliased fire event method is invalid', async () => { - testingLibraryFireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - endColumn: 33 + fireEventMethod.length, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import * as testingLibrary from '${testingFramework}' - test('unhandled promise from wildcard imported fire event method is invalid', async () => { - testingLibrary.fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - endColumn: 34 + fireEventMethod.length, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('several unhandled promises from fire event methods is invalid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - { - line: 5, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from '${testingFramework}' - test('unhandled promise from fire event method with aggressive reporting opted-out is invalid', async () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 4, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from 'test-utils' - test( - 'unhandled promise from fire event method imported from custom module with aggressive reporting opted-out is invalid', - () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 6, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - settings: { - 'testing-library/utils-module': 'test-utils', - }, - code: ` - import { fireEvent } from '${testingFramework}' - test( - 'unhandled promise from fire event method imported from default module with aggressive reporting opted-out is invalid', - () => { - fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 6, - column: 9, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test( - 'unhandled promise from fire event method kept in a var is invalid', - () => { - const promise = fireEvent.${fireEventMethod}(getByLabelText('username')) - }) - `, - errors: [ - { - line: 6, - column: 25, - messageId: 'awaitFireEvent', - data: { name: fireEventMethod }, - }, - ], - } as const) - ), - ...COMMON_FIRE_EVENT_METHODS.map( - (fireEventMethod) => - ({ - code: ` - import { fireEvent } from '${testingFramework}' - test('unhandled promise returned from function wrapping fire event method is invalid', () => { - function triggerEvent() { - doSomething() - return fireEvent.${fireEventMethod}(getByLabelText('username')) - } - - triggerEvent() - }) - `, - errors: [ - { - line: 9, - column: 9, - messageId: 'fireEventWrapper', - data: { name: 'triggerEvent' }, - }, - ], - } as const) - ), - ]), -});