diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 50c5c1fc..b811ce19 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -20,8 +20,13 @@ concurrency: jobs: code_validation: - name: Code Validation + name: 'Code Validation: ${{ matrix.validation_script }}' runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + validation_script: + ['lint', 'type-check', 'format:check', 'generate-all:check'] steps: - name: Checkout @@ -37,23 +42,15 @@ jobs: with: useLockFile: false - - name: Check Types - run: npm run type-check - - - name: Lint code - run: npm run lint - - - name: Check format - run: npm run format:check - - - name: Check autogenerated docs - run: npm run generate-all && git diff --exit-code + - name: Run script + run: npm run ${{ matrix.validation_script }} tests: name: Tests (Node v${{ matrix.node }} - ESLint v${{ matrix.eslint }}) runs-on: ubuntu-latest strategy: + fail-fast: false matrix: # The .x indicates "the most recent one" node: [19.x, 18.x, 17.x, 16.x, 14.x, 14.17.0, 12.x, 12.22.0] @@ -105,7 +102,7 @@ jobs: - name: Build package run: npm run build - - name: Release new version to NPM + - name: Release the new version to NPM env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }} diff --git a/docs/rules/no-await-sync-events.md b/docs/rules/no-await-sync-events.md index 4c9e0519..8e953882 100644 --- a/docs/rules/no-await-sync-events.md +++ b/docs/rules/no-await-sync-events.md @@ -4,7 +4,7 @@ -Ensure that sync simulated events are not awaited unnecessarily. +Ensure that sync events are not awaited unnecessarily. ## Rule Details @@ -22,6 +22,12 @@ Some examples of simulating events not returning any Promise are: This rule aims to prevent users from waiting for those function calls. +> ⚠️ `fire-event` 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 @@ -87,13 +93,9 @@ const qux = async () => { This rule provides the following options: -- `eventModules`: array of strings. The possibilities are: `"fire-event"` and `"user-event"`. Defaults to `["fire-event", "user-event"]` - -### `eventModules` - -This option gives you more granular control of which event modules you want to report, so you can choose to only report methods from either `fire-event`, `user-event` or both. +- `eventModules`: array of strings. Defines which event module should be linted for sync event methods. The possibilities are: `"fire-event"` and `"user-event"`. Defaults to `["fire-event"]`. -Example: +### Example: ```js module.exports = { @@ -106,7 +108,11 @@ module.exports = { }; ``` +## When Not To Use It + +- `"fire-event"` option: should be disabled only for those Testing Library packages where fire-event methods are async. +- `"user-event"` option: should be disabled only if using v14 or greater. + ## 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-async-events`, 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. +There is another rule `await-async-events`, which is for awaiting async events for `user-event` v14 or `fire-event` only in Testing Library packages with async methods. Please do not confuse with this rule. diff --git a/lib/configs/angular.ts b/lib/configs/angular.ts index 8defb66b..270e4d48 100644 --- a/lib/configs/angular.ts +++ b/lib/configs/angular.ts @@ -11,7 +11,10 @@ export = { ], 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-events': 'error', + 'testing-library/no-await-sync-events': [ + 'error', + { eventModules: ['fire-event'] }, + ], 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', diff --git a/lib/configs/dom.ts b/lib/configs/dom.ts index 3e5830ac..ed568225 100644 --- a/lib/configs/dom.ts +++ b/lib/configs/dom.ts @@ -11,7 +11,10 @@ export = { ], 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-events': 'error', + 'testing-library/no-await-sync-events': [ + 'error', + { eventModules: ['fire-event'] }, + ], 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-global-regexp-flag-in-query': 'error', 'testing-library/no-node-access': 'error', diff --git a/lib/configs/react.ts b/lib/configs/react.ts index 538b4fc9..275a0c3a 100644 --- a/lib/configs/react.ts +++ b/lib/configs/react.ts @@ -11,7 +11,10 @@ export = { ], 'testing-library/await-async-queries': 'error', 'testing-library/await-async-utils': 'error', - 'testing-library/no-await-sync-events': 'error', + 'testing-library/no-await-sync-events': [ + 'error', + { eventModules: ['fire-event'] }, + ], 'testing-library/no-await-sync-queries': 'error', 'testing-library/no-container': 'error', 'testing-library/no-debugging-utils': 'warn', diff --git a/lib/rules/no-await-sync-events.ts b/lib/rules/no-await-sync-events.ts index e9fa9b9d..989ddcda 100644 --- a/lib/rules/no-await-sync-events.ts +++ b/lib/rules/no-await-sync-events.ts @@ -10,13 +10,17 @@ import { } from '../node-utils'; const USER_EVENT_ASYNC_EXCEPTIONS: string[] = ['type', 'keyboard']; -const VALID_EVENT_MODULES = ['fire-event', 'user-event'] as const; +const FIRE_EVENT_OPTION = 'fire-event' as const; +const USER_EVENT_OPTION = 'user-event' as const; +const VALID_EVENT_MODULES = [FIRE_EVENT_OPTION, USER_EVENT_OPTION]; +const DEFAULT_EVENT_MODULES = [FIRE_EVENT_OPTION]; export const RULE_NAME = 'no-await-sync-events'; export type MessageIds = 'noAwaitSyncEvents'; -type Options = [ - { eventModules?: readonly (typeof VALID_EVENT_MODULES)[number][] } -]; + +type ValidEventModules = (typeof VALID_EVENT_MODULES)[number]; +type EventModulesOptions = ReadonlyArray; +type Options = [{ eventModules?: EventModulesOptions }]; export default createTestingLibraryRule({ name: RULE_NAME, @@ -25,9 +29,9 @@ export default createTestingLibraryRule({ docs: { description: 'Disallow unnecessary `await` for sync events', recommendedConfig: { - dom: 'error', - angular: 'error', - react: 'error', + dom: ['error', { eventModules: DEFAULT_EVENT_MODULES }], + angular: ['error', { eventModules: DEFAULT_EVENT_MODULES }], + react: ['error', { eventModules: DEFAULT_EVENT_MODULES }], vue: false, marko: false, }, @@ -42,20 +46,19 @@ export default createTestingLibraryRule({ properties: { eventModules: { type: 'array', + items: { type: 'string', enum: VALID_EVENT_MODULES }, minItems: 1, - items: { - enum: VALID_EVENT_MODULES, - }, + default: DEFAULT_EVENT_MODULES, }, }, additionalProperties: false, }, ], }, - defaultOptions: [{ eventModules: VALID_EVENT_MODULES }], + defaultOptions: [{ eventModules: DEFAULT_EVENT_MODULES }], create(context, [options], helpers) { - const { eventModules = VALID_EVENT_MODULES } = options; + const { eventModules = DEFAULT_EVENT_MODULES } = options; let hasDelayDeclarationOrAssignmentGTZero: boolean; // userEvent.type() and userEvent.keyboard() are exceptions, which returns a @@ -107,10 +110,10 @@ export default createTestingLibraryRule({ return; } - if (isFireEventMethod && !eventModules.includes('fire-event')) { + if (isFireEventMethod && !eventModules.includes(FIRE_EVENT_OPTION)) { return; } - if (isUserEventMethod && !eventModules.includes('user-event')) { + if (isUserEventMethod && !eventModules.includes(USER_EVENT_OPTION)) { return; } diff --git a/package.json b/package.json index cf824c39..e157ac24 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "prebuild": "del-cli dist", "build": "tsc", "generate-all": "run-p \"generate:*\"", + "generate-all:check": "npm run generate-all && git diff --exit-code", "generate:configs": "ts-node tools/generate-configs", "generate:rules-doc": "npm run build && npm run rule-doc-generator", "format": "npm run prettier-base -- --write", diff --git a/tests/lib/rules/no-await-sync-events.test.ts b/tests/lib/rules/no-await-sync-events.test.ts index 8145138e..a7a93125 100644 --- a/tests/lib/rules/no-await-sync-events.test.ts +++ b/tests/lib/rules/no-await-sync-events.test.ts @@ -219,6 +219,16 @@ ruleTester.run(RULE_NAME, rule, { `, options: [{ eventModules: ['fire-event'] }], })), + + // valid tests for user-event with default options (user-event disabled) + ...USER_EVENT_SYNC_FUNCTIONS.map((func) => ({ + code: ` + import userEvent from '@testing-library/user-event'; + test('should not report userEvent.${func} by default', async() => { + await userEvent.${func}('foo'); + }); + `, + })), ], invalid: [ @@ -254,6 +264,7 @@ ruleTester.run(RULE_NAME, rule, { await userEvent.${func}('foo'); }); `, + options: [{ eventModules: ['user-event'] }], errors: [ { line: 4, @@ -277,7 +288,6 @@ ruleTester.run(RULE_NAME, rule, { await fireEvent.${func}('foo'); }); `, - options: [{ eventModules: ['fire-event'] }], errors: [ { line: 4, @@ -289,8 +299,7 @@ ruleTester.run(RULE_NAME, rule, { } as const) ) ), - // sync userEvent sync methods with await operator are not valid - // when only fire-event set in eventModules + ...USER_EVENT_SYNC_FUNCTIONS.map( (func) => ({ @@ -320,6 +329,7 @@ ruleTester.run(RULE_NAME, rule, { await userEvent.keyboard('foo'); }); `, + options: [{ eventModules: ['user-event'] }], errors: [ { line: 4, @@ -343,6 +353,7 @@ ruleTester.run(RULE_NAME, rule, { await userEvent.keyboard('foo', { delay: 0 }); }); `, + options: [{ eventModules: ['user-event'] }], errors: [ { line: 4, @@ -370,6 +381,7 @@ ruleTester.run(RULE_NAME, rule, { await renamedUserEvent.keyboard('foo', { delay: 0 }); }); `, + options: [{ eventModules: ['user-event', 'fire-event'] }], errors: [ { line: 6, @@ -397,6 +409,7 @@ ruleTester.run(RULE_NAME, rule, { await userEvent.type('foo', { delay }); } `, + options: [{ eventModules: ['user-event'] }], errors: [ { line: 3, @@ -414,6 +427,7 @@ ruleTester.run(RULE_NAME, rule, { await userEvent.type('foo', { delay, skipHover }); } `, + options: [{ eventModules: ['user-event'] }], errors: [ { line: 5, @@ -433,6 +447,7 @@ ruleTester.run(RULE_NAME, rule, { await userEvent.type('foo', { delay, skipHover }); } `, + options: [{ eventModules: ['user-event'] }], errors: [ { line: 7,