From 936521405ff10e75e0a29cbbd082d75c27db47bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katka=20Pil=C3=A1tov=C3=A1?= Date: Tue, 31 Dec 2024 18:27:32 +0100 Subject: [PATCH 1/2] Add React Testing Library config --- .github/ISSUE_TEMPLATE/bug-report.yml | 1 + .github/ISSUE_TEMPLATE/change-request.yml | 1 + .github/ISSUE_TEMPLATE/feature-request.yml | 1 + README.md | 74 ++++---- docs/icons/other/testing-library.png | Bin 0 -> 1387 bytes docs/react-testing-library.md | 72 ++++++++ package-lock.json | 194 ++++++++++++++++++-- package.json | 5 + scripts/helpers/configs.js | 22 ++- scripts/helpers/plugins.js | 3 + scripts/helpers/types.d.ts | 3 +- src/configs/react-testing-library.js | 47 +++++ tests/configs/react-testing-library.spec.js | 67 +++++++ tests/helpers/lint-utils.js | 23 ++- tests/helpers/lint-utils.spec.js | 14 ++ 15 files changed, 470 insertions(+), 57 deletions(-) create mode 100644 docs/icons/other/testing-library.png create mode 100644 docs/react-testing-library.md create mode 100644 src/configs/react-testing-library.js create mode 100644 tests/configs/react-testing-library.spec.js create mode 100644 tests/helpers/lint-utils.spec.js diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 88d8f44..1fedf1d 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -48,6 +48,7 @@ body: - NgRx - Jest - Vitest + - 'React Testing Library' - Cypress - Playwright - Storybook diff --git a/.github/ISSUE_TEMPLATE/change-request.yml b/.github/ISSUE_TEMPLATE/change-request.yml index 7ce4cbd..3750684 100644 --- a/.github/ISSUE_TEMPLATE/change-request.yml +++ b/.github/ISSUE_TEMPLATE/change-request.yml @@ -46,6 +46,7 @@ body: - NgRx - Jest - Vitest + - 'React Testing Library' - Cypress - Playwright - Storybook diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index d60d659..151bb1e 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -46,6 +46,7 @@ body: - NgRx - Jest - Vitest + - 'React Testing Library' - Cypress - Playwright - Storybook diff --git a/README.md b/README.md index 7968085..f9c8d85 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,20 @@ Recommended ESLint presets by [Code PushUp](https://github.com/code-pushup/cli/t ## ⚙️ Configs -| Stack | Config | Description | -| :-------------------------------------------------: | :--------------------------------- | :------------------------------------------------------------------ | -| ![javascript](./docs/icons/material/javascript.png) | [javascript](./docs/javascript.md) | Default config, suitable for any **JavaScript/TypeScript** project. | -| ![typescript](./docs/icons/material/typescript.png) | [typescript](./docs/typescript.md) | Config for strict **TypeScript** projects. | -| ![nodejs](./docs/icons/material/nodejs.png) | [node](./docs/node.md) | Config for **Node.js** projects. | -| ![angular](./docs/icons/material/angular.png) | [angular](./docs/angular.md) | Config for **Angular** projects. | -| ![ngrx](./docs/icons/other/ngrx.png) | [ngrx](./docs/ngrx.md) | Config for **Angular** projects using **NgRx** library. | -| ![graphql](./docs/icons/material/graphql.png) | [graphql](./docs/graphql.md) | Config for **GraphQL servers** implemented in Node.js. | -| ![jest](./docs/icons/material/jest.png) | [jest](./docs/jest.md) | Config for projects using **Jest** for testing. | -| ![vitest](./docs/icons/material/vitest.png) | [vitest](./docs/vitest.md) | Config for projects using **Vitest** for testing. | -| ![cypress](./docs/icons/material/cypress.png) | [cypress](./docs/cypress.md) | Config for projects using **Cypress** for testing. | -| ![playwright](./docs/icons/material/playwright.png) | [playwright](./docs/playwright.md) | Config for projects using **Playwright** for testing. | -| ![storybook](./docs/icons/material/storybook.png) | [storybook](./docs/storybook.md) | Config for projects using **Storybook** for UI components. | +| Stack | Config | Description | +| :--------------------------------------------------------: | :------------------------------------------------------- | :------------------------------------------------------------------ | +| ![javascript](./docs/icons/material/javascript.png) | [javascript](./docs/javascript.md) | Default config, suitable for any **JavaScript/TypeScript** project. | +| ![typescript](./docs/icons/material/typescript.png) | [typescript](./docs/typescript.md) | Config for strict **TypeScript** projects. | +| ![nodejs](./docs/icons/material/nodejs.png) | [node](./docs/node.md) | Config for **Node.js** projects. | +| ![angular](./docs/icons/material/angular.png) | [angular](./docs/angular.md) | Config for **Angular** projects. | +| ![ngrx](./docs/icons/other/ngrx.png) | [ngrx](./docs/ngrx.md) | Config for **Angular** projects using **NgRx** library. | +| ![graphql](./docs/icons/material/graphql.png) | [graphql](./docs/graphql.md) | Config for **GraphQL servers** implemented in Node.js. | +| ![jest](./docs/icons/material/jest.png) | [jest](./docs/jest.md) | Config for projects using **Jest** for testing. | +| ![vitest](./docs/icons/material/vitest.png) | [vitest](./docs/vitest.md) | Config for projects using **Vitest** for testing. | +| ![cypress](./docs/icons/material/cypress.png) | [cypress](./docs/cypress.md) | Config for projects using **Cypress** for testing. | +| ![playwright](./docs/icons/material/playwright.png) | [playwright](./docs/playwright.md) | Config for projects using **Playwright** for testing. | +| ![storybook](./docs/icons/material/storybook.png) | [storybook](./docs/storybook.md) | Config for projects using **Storybook** for UI components. | +| ![testing-library](./docs/icons/other/testing-library.png) | [react-testing-library](./docs/react-testing-library.md) | Config for projects using **React Testing Library** for testing. | Some configs extend other configs, as illustrated below. So, for example, extending `angular` config implicitly extends `typescript` and `javascript` configs as well. @@ -71,29 +72,30 @@ Depending on your tech stack, you may wish to extend other configs as well ([lis All peer dependencies used by `@code-pushup/eslint-config` are listed below, along with their supported versions. Only the default config's dependencies are required, others are optional. -| | NPM package | Version | Required | -| :-------------------------------------------------: | :--------------------------------------------------------------------------------------------------- | :--------: | :------: | -| ![eslint](./docs/icons/material/eslint.png) | [eslint](https://www.npmjs.com/package/eslint) | `^9.0.0` | ✅ | -| ![eslint](./docs/icons/material/eslint.png) | [@eslint/js](https://www.npmjs.com/package/@eslint/js) | `^9.0.0` | ✅ | -| ![lambda](./docs/icons/icons8/lambda.png) | [eslint-plugin-functional](https://www.npmjs.com/package/eslint-plugin-functional) | `^7.0.0` | ✅ | -| ![import](./docs/icons/icons8/import.png) | [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import) | `^2.31.0` | ✅ | -| ![import](./docs/icons/icons8/import.png) | [eslint-import-resolver-typescript](https://www.npmjs.com/package/eslint-import-resolver-typescript) | `^3.0.0` | | -| ![promise](./docs/icons/icons8/promise.png) | [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise) | `>=6.4.0` | ✅ | -| ![sonar](./docs/icons/other/sonar.png) | [eslint-plugin-sonarjs](https://www.npmjs.com/package/eslint-plugin-sonarjs) | `^1.0.4` | ✅ | -| ![unicorn](./docs/icons/icons8/unicorn.png) | [eslint-plugin-unicorn](https://www.npmjs.com/package/eslint-plugin-unicorn) | `>=50.0.0` | ✅ | -| ![global](./docs/icons/icons8/global.png) | [globals](https://www.npmjs.com/package/globals) | `>=14.0.0` | ✅ | -| ![typescript](./docs/icons/material/typescript.png) | [typescript-eslint](https://www.npmjs.com/package/typescript-eslint) | `^8.0.0` | ✅ | -| ![graphql](./docs/icons/material/graphql.png) | [@graphql-eslint/eslint-plugin](https://www.npmjs.com/package/@graphql-eslint/eslint-plugin) | `^3.0.0` | | -| ![ngrx](./docs/icons/other/ngrx.png) | [@ngrx/eslint-plugin](https://www.npmjs.com/package/@ngrx/eslint-plugin) | `^18.0.0` | | -| ![angular](./docs/icons/material/angular.png) | [angular-eslint](https://www.npmjs.com/package/angular-eslint) | `^18.0.0` | | -| ![cypress](./docs/icons/material/cypress.png) | [eslint-plugin-cypress](https://www.npmjs.com/package/eslint-plugin-cypress) | `>=3.3.0` | | -| ![jest](./docs/icons/material/jest.png) | [eslint-plugin-jest](https://www.npmjs.com/package/eslint-plugin-jest) | `^28.8.0` | | -| ![test](./docs/icons/icons8/test.png) | [eslint-plugin-jest-formatting](https://www.npmjs.com/package/eslint-plugin-jest-formatting) | `^3.0.0` | | -| ![nodejs](./docs/icons/material/nodejs.png) | [eslint-plugin-n](https://www.npmjs.com/package/eslint-plugin-n) | `>=17.0.0` | | -| ![playwright](./docs/icons/material/playwright.png) | [eslint-plugin-playwright](https://www.npmjs.com/package/eslint-plugin-playwright) | `^2.1.0` | | -| ![rxjs](./docs/icons/other/rxjs.png) | [eslint-plugin-rxjs-x](https://www.npmjs.com/package/eslint-plugin-rxjs-x) | `>=0.6.0` | | -| ![storybook](./docs/icons/material/storybook.png) | [eslint-plugin-storybook](https://www.npmjs.com/package/eslint-plugin-storybook) | `>=0.10.0` | | -| ![vitest](./docs/icons/material/vitest.png) | [eslint-plugin-vitest](https://www.npmjs.com/package/eslint-plugin-vitest) | `>=0.5.0` | | +| | NPM package | Version | Required | +| :--------------------------------------------------------: | :--------------------------------------------------------------------------------------------------- | :--------: | :------: | +| ![eslint](./docs/icons/material/eslint.png) | [eslint](https://www.npmjs.com/package/eslint) | `^9.0.0` | ✅ | +| ![eslint](./docs/icons/material/eslint.png) | [@eslint/js](https://www.npmjs.com/package/@eslint/js) | `^9.0.0` | ✅ | +| ![lambda](./docs/icons/icons8/lambda.png) | [eslint-plugin-functional](https://www.npmjs.com/package/eslint-plugin-functional) | `^7.0.0` | ✅ | +| ![import](./docs/icons/icons8/import.png) | [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import) | `^2.31.0` | ✅ | +| ![import](./docs/icons/icons8/import.png) | [eslint-import-resolver-typescript](https://www.npmjs.com/package/eslint-import-resolver-typescript) | `^3.0.0` | | +| ![promise](./docs/icons/icons8/promise.png) | [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise) | `>=6.4.0` | ✅ | +| ![sonar](./docs/icons/other/sonar.png) | [eslint-plugin-sonarjs](https://www.npmjs.com/package/eslint-plugin-sonarjs) | `^1.0.4` | ✅ | +| ![unicorn](./docs/icons/icons8/unicorn.png) | [eslint-plugin-unicorn](https://www.npmjs.com/package/eslint-plugin-unicorn) | `>=50.0.0` | ✅ | +| ![global](./docs/icons/icons8/global.png) | [globals](https://www.npmjs.com/package/globals) | `>=14.0.0` | ✅ | +| ![typescript](./docs/icons/material/typescript.png) | [typescript-eslint](https://www.npmjs.com/package/typescript-eslint) | `^8.0.0` | ✅ | +| ![graphql](./docs/icons/material/graphql.png) | [@graphql-eslint/eslint-plugin](https://www.npmjs.com/package/@graphql-eslint/eslint-plugin) | `^3.0.0` | | +| ![ngrx](./docs/icons/other/ngrx.png) | [@ngrx/eslint-plugin](https://www.npmjs.com/package/@ngrx/eslint-plugin) | `^18.0.0` | | +| ![angular](./docs/icons/material/angular.png) | [angular-eslint](https://www.npmjs.com/package/angular-eslint) | `^18.0.0` | | +| ![cypress](./docs/icons/material/cypress.png) | [eslint-plugin-cypress](https://www.npmjs.com/package/eslint-plugin-cypress) | `>=3.3.0` | | +| ![jest](./docs/icons/material/jest.png) | [eslint-plugin-jest](https://www.npmjs.com/package/eslint-plugin-jest) | `^28.8.0` | | +| ![test](./docs/icons/icons8/test.png) | [eslint-plugin-jest-formatting](https://www.npmjs.com/package/eslint-plugin-jest-formatting) | `^3.0.0` | | +| ![nodejs](./docs/icons/material/nodejs.png) | [eslint-plugin-n](https://www.npmjs.com/package/eslint-plugin-n) | `>=17.0.0` | | +| ![playwright](./docs/icons/material/playwright.png) | [eslint-plugin-playwright](https://www.npmjs.com/package/eslint-plugin-playwright) | `^2.1.0` | | +| ![rxjs](./docs/icons/other/rxjs.png) | [eslint-plugin-rxjs-x](https://www.npmjs.com/package/eslint-plugin-rxjs-x) | `>=0.6.0` | | +| ![storybook](./docs/icons/material/storybook.png) | [eslint-plugin-storybook](https://www.npmjs.com/package/eslint-plugin-storybook) | `>=0.10.0` | | +| ![testing-library](./docs/icons/other/testing-library.png) | [eslint-plugin-testing-library](https://www.npmjs.com/package/eslint-plugin-testing-library) | `^7.1.1` | | +| ![vitest](./docs/icons/material/vitest.png) | [eslint-plugin-vitest](https://www.npmjs.com/package/eslint-plugin-vitest) | `>=0.5.0` | | ### 🧪 Test overrides diff --git a/docs/icons/other/testing-library.png b/docs/icons/other/testing-library.png new file mode 100644 index 0000000000000000000000000000000000000000..f21d252543f07c4dcc68bc2fd5738f86f9ad0a5b GIT binary patch literal 1387 zcmV-x1(f=UP)Px)B}qg#~LFaULNkWr$YHd?{5U~@PiM56(hC<3ogfCnLPOA=sJFdp%n ziP(h$+4V?*J6T&@9VG?NQxudF^&G55gEEgrIL_6?G*sL5$7Hql1@B_xk9w9_qk( z_)K|i)@#ohuU(V>vkXo7hF2)(lP+lLpAImR)eCOde#7Oe1tiaun1~Z{zxY`-Gkwfs z3B;2Zapmd-Q6tUf`y{bABqaFC$~A2R%gbQ4hLvl->y%(?CxZ3&{?N{AZMBiu)j_e5 z30wC!a1%%PiS~PG+j}&w?bPv`F}rqj$E`+V-zlQHJ~N1e%pP8X{0<57?IIlK95H&6 z>2{am`W3HZ?f5D2+C|fDb;bM$Y&(nlwN;eW36&QN1FEaQwi}UQbT$%@0YlsD`KZk9c)aogYDgMiaTJvs1RDVbd-we7hZDs!THZUq)ki zcvxs7@t=$+As}OmFoLG$2NAtYrY4eCpJK%POK(Bc`sH$6Nm!VEMsu@OemOUF22MLx zBLA*k@_sho^yeq)HVg2a0vMVCCCAAdEFlk2KmkM3G65m|iY@jR%NN}w|q zgR8>{k=3QZ(2Pt%76JDl58O}A0A&vqpwh3w;c|gp5CHdygXHX7V8^D&$$p#c{^^oY zwAsp>x6NADi9@E(?-k<^KvP;ITPC>OfP034a`J3Y$HcX=J$p`2 zOdMtkI7LUo!yJR>FH=RNXMnrK2F}I@(0SJi!8N6zSh?O6ynLq2VB2+AaPQotv?XPL zCRz`wh)_`If}x?T6n-x*bWU$-RzE~gz_K5$I8&KJiEIyQ+(5hUX|xO*io>$EVE)Ci z@=+FkIE$J$tfdT>F8euvPoECLoUY@`^dUK`Bl_rfSI!H*a4rPT`( zQ|s$%0}0w^h;}Trc(J+(3IX_~=WEtcqmwoUDzav6#J$Fj54E>TOo!=gqdqq~pn6IK%q)CcbU_9z`QhvuQ<4Q_#(T#oCvXQ$w&INnKGz#OFq`G%_Mf> zy+J`^Qs=DuHFkH7&{gXz@Uh?2>#ux(D-LakxWQ=NwFp8^8+_e9-VXR t>Ul_={U!|f>eEN!Jn^F)$gckq`y2IxDK`=9NV)(3002ovPDHLkV1jJ|l8*oY literal 0 HcmV?d00001 diff --git a/docs/react-testing-library.md b/docs/react-testing-library.md new file mode 100644 index 0000000..94deead --- /dev/null +++ b/docs/react-testing-library.md @@ -0,0 +1,72 @@ +# `react-testing-library` config + +Config for projects using **React Testing Library** for testing. + +## 🏗️ Setup + +1. If you haven't already, make sure to [install `@code-pushup/eslint-config` and its required peer dependencies](../README.md#🏗️-setup). +2. Since this plugin requires additional peer dependencies, you have to install them as well: + + ```sh + npm install -D eslint-plugin-testing-library + ``` + +3. Add to your `eslint.config.js` file: + + ```js + import react-testing-library from '@code-pushup/eslint-config/react-testing-library.js'; + import tseslint from 'typescript-eslint'; + + export default tseslint.config( + ...react-testing-library, + { + // customize rules if needed: + rules: { + // e.g. to enforce using `queryBy*` or `getBy*` for matchers: + 'testing-library/prefer-query-matchers': [ + 'warn', + { validEntries: [{ matcher: 'toBeVisible', query: 'get' }] }, + ] + } + } + ); + ``` + +## 📏 Rules (25) + +> 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + +### 🚨 Errors (11) + +| Plugin | Rule | Options | Autofix | Overrides | +| :----------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :-----: | :-------: | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-events](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-events.md)
Enforce promises from async event methods are handled |
eventModule: userEvent
{
  "eventModule": "userEvent"
}
| 🔧 | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-queries.md)
Enforce promises from async queries to be handled | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-utils](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-utils.md)
Enforce promises from async utils to be awaited properly | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-dom-import](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-dom-import.md)
Disallow importing from DOM Testing Library |
react
"react"
| 🔧 | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-global-regexp-flag-in-query](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-global-regexp-flag-in-query.md)
Disallow the use of the global RegExp flag (/g) in queries | | 🔧 | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-manual-cleanup](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-manual-cleanup.md)
Disallow the use of `cleanup` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-promise-in-fire-event](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-promise-in-fire-event.md)
Disallow the use of promises passed to a `fireEvent` method | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-unnecessary-act](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-unnecessary-act.md)
Disallow wrapping Testing Library utils or empty callbacks in `act` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-side-effects](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-side-effects.md)
Disallow the use of side effects in `waitFor` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-snapshot](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-snapshot.md)
Ensures no snapshot is generated inside of a `waitFor` call | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-query-by-disappearance](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-query-by-disappearance.md)
Suggest using `queryBy*` queries when waiting for disappearance | | | | + +### ⚠️ Warnings (14) + +| Plugin | Rule | Options | Autofix | Overrides | +| :----------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :-----: | :-------: | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-await-sync-events](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-await-sync-events.md)
Disallow unnecessary `await` for sync events | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-await-sync-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-await-sync-queries.md)
Disallow unnecessary `await` for sync queries | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-container](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-container.md)
Disallow the use of `container` methods | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-debugging-utils](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-debugging-utils.md)
Disallow the use of debugging utilities like `debug` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-node-access](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-node-access.md)
Disallow direct Node access | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-render-in-lifecycle](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-render-in-lifecycle.md)
Disallow the use of `render` in testing frameworks setup functions | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-multiple-assertions](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-multiple-assertions.md)
Disallow the use of multiple `expect` calls inside `waitFor` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-explicit-assert](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-explicit-assert.md)
Suggest using explicit assertions rather than standalone queries | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-find-by](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-find-by.md)
Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements | | 🔧 | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-presence-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-presence-queries.md)
Ensure appropriate `get*`/`query*` queries are used with their respective matchers | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-query-matchers](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-query-matchers.md)
Ensure the configured `get*`/`query*` query is used with the corresponding matchers | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-screen-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-screen-queries.md)
Suggest using `screen` while querying | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-user-event](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-user-event.md)
Suggest using `userEvent` over `fireEvent` for simulating user interactions | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [render-result-naming-convention](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/render-result-naming-convention.md)
Enforce a valid naming for return value from `render` | | | | diff --git a/package-lock.json b/package-lock.json index 1337af0..a7fa660 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "eslint-plugin-rxjs-x": "^0.6.1", "eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-storybook": "^0.10.0", + "eslint-plugin-testing-library": "^7.1.1", "eslint-plugin-unicorn": "^50.0.0", "eslint-plugin-vitest": "^0.5.4", "globals": "^15.12.0", @@ -54,10 +55,12 @@ "eslint-plugin-jest": "^28.8.0", "eslint-plugin-jest-formatting": "^3.0.0", "eslint-plugin-n": ">=17.0.0", + "eslint-plugin-playwright": "^2.1.0", "eslint-plugin-promise": ">=6.4.0", "eslint-plugin-rxjs-x": ">=0.6.0", "eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-storybook": ">=0.10.0", + "eslint-plugin-testing-library": "^7.1.1", "eslint-plugin-unicorn": ">=50.0.0", "eslint-plugin-vitest": ">=0.5.0", "globals": ">=14.0.0", @@ -88,12 +91,18 @@ "eslint-plugin-n": { "optional": true }, + "eslint-plugin-playwright": { + "optional": true + }, "eslint-plugin-rxjs-x": { "optional": true }, "eslint-plugin-storybook": { "optional": true }, + "eslint-plugin-testing-library": { + "optional": true + }, "eslint-plugin-vitest": { "optional": true } @@ -2682,13 +2691,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", - "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", + "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0" + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2699,10 +2709,11 @@ } }, "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", - "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2829,6 +2840,86 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", + "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@typescript-eslint/utils": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.13.0.tgz", @@ -2950,13 +3041,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", - "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", + "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.14.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.19.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2967,10 +3059,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", - "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2979,6 +3072,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitest/expect": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz", @@ -5657,6 +5763,62 @@ "eslint": ">=6" } }, + "node_modules/eslint-plugin-testing-library": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-7.1.1.tgz", + "integrity": "sha512-nszC833aZPwB6tik1nMkbFqmtgIXTT0sfJEYs0zMBKMlkQ4to2079yUV96SvmLh00ovSBJI4pgcBC1TiIP8mXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "^8.15.0", + "@typescript-eslint/utils": "^8.15.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0", + "pnpm": "^9.14.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/utils": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", + "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/eslint-plugin-unicorn": { "version": "50.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-50.0.0.tgz", diff --git a/package.json b/package.json index c5b8182..1352374 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "eslint-plugin-rxjs-x": ">=0.6.0", "eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-storybook": ">=0.10.0", + "eslint-plugin-testing-library": "^7.1.1", "eslint-plugin-unicorn": ">=50.0.0", "eslint-plugin-vitest": ">=0.5.0", "globals": ">=14.0.0", @@ -94,6 +95,9 @@ "eslint-plugin-storybook": { "optional": true }, + "eslint-plugin-testing-library": { + "optional": true + }, "eslint-plugin-vitest": { "optional": true } @@ -120,6 +124,7 @@ "eslint-plugin-rxjs-x": "^0.6.1", "eslint-plugin-sonarjs": "^1.0.4", "eslint-plugin-storybook": "^0.10.0", + "eslint-plugin-testing-library": "^7.1.1", "eslint-plugin-unicorn": "^50.0.0", "eslint-plugin-vitest": "^0.5.4", "globals": "^15.12.0", diff --git a/scripts/helpers/configs.js b/scripts/helpers/configs.js index 4ff9101..bffedbc 100644 --- a/scripts/helpers/configs.js +++ b/scripts/helpers/configs.js @@ -15,6 +15,7 @@ const configDescriptions = { cypress: md`Config for projects using ${md.bold('Cypress')} for testing.`, playwright: md`Config for projects using ${md.bold('Playwright')} for testing.`, storybook: md`Config for projects using ${md.bold('Storybook')} for UI components.`, + 'react-testing-library': md`Config for projects using ${md.bold('React Testing Library')} for testing.`, }; /** @type {(keyof typeof configDescriptions)[]} */ @@ -34,6 +35,7 @@ const configIcons = { cypress: 'material/cypress', playwright: 'material/playwright', storybook: 'material/storybook', + 'react-testing-library': 'other/testing-library', }; /** @type {Partial>} */ @@ -44,6 +46,7 @@ const configPatterns = { cypress: '*.cy.ts', playwright: '*.spec.ts', storybook: '*.stories.ts', + 'react-testing-library': '*.spec.tsx', }; /** @type {Partial>} */ @@ -53,7 +56,13 @@ const configExtraPatterns = { }; /** @type {(keyof typeof configDescriptions)[]} */ -const testConfigs = ['jest', 'vitest', 'cypress', 'playwright']; +const testConfigs = [ + 'jest', + 'vitest', + 'cypress', + 'playwright', + 'react-testing-library', +]; const tsConfigDocsReference = md`Refer to ${md.link('./typescript.md#🏗️-setup', "step 3 in TypeScript config's setup docs")} for how to set up tsconfig properly.`; @@ -192,6 +201,17 @@ export const configsExtraEslintrc = { 'vitest/consistent-test-it': ['warn', { fn: 'test', withinDescribe: 'test' }] } }`, + 'react-testing-library': `, + { + // customize rules if needed: + rules: { + // e.g. to enforce using \`queryBy*\` or \`getBy*\` for matchers: + 'testing-library/prefer-query-matchers': [ + 'warn', + { validEntries: [{ matcher: 'toBeVisible', query: 'get' }] }, + ] + } + }`, }; /** diff --git a/scripts/helpers/plugins.js b/scripts/helpers/plugins.js index 0b6fef8..ad84ac9 100644 --- a/scripts/helpers/plugins.js +++ b/scripts/helpers/plugins.js @@ -24,6 +24,7 @@ const pluginIcons = { 'rxjs-x': 'other/rxjs', sonarjs: 'other/sonar', storybook: 'material/storybook', + 'testing-library': 'other/testing-library', unicorn: 'icons8/unicorn', vitest: 'material/vitest', }; @@ -51,6 +52,8 @@ const pluginDocsUrls = { playwright: 'https://github.com/playwright-community/eslint-plugin-playwright#readme', promise: 'https://github.com/eslint-community/eslint-plugin-promise#readme', + 'react-testing-library': + 'https://github.com/testing-library/eslint-plugin-testing-library#readme', react: 'https://github.com/jsx-eslint/eslint-plugin-react#readme', 'react-hooks': 'https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks#readme', diff --git a/scripts/helpers/types.d.ts b/scripts/helpers/types.d.ts index b5b26bc..2118152 100644 --- a/scripts/helpers/types.d.ts +++ b/scripts/helpers/types.d.ts @@ -55,4 +55,5 @@ export type Icon = | 'other/ngrx' | 'other/rx-angular' | 'other/rxjs' - | 'other/sonar'; + | 'other/sonar' + | 'other/testing-library'; diff --git a/src/configs/react-testing-library.js b/src/configs/react-testing-library.js new file mode 100644 index 0000000..cd121b8 --- /dev/null +++ b/src/configs/react-testing-library.js @@ -0,0 +1,47 @@ +// @ts-check + +import rtl from 'eslint-plugin-testing-library'; +import tseslint from 'typescript-eslint'; +import { UNIT_TEST_FILE_PATTERNS } from '../lib/patterns.js'; + +export default tseslint.config({ + files: UNIT_TEST_FILE_PATTERNS, + extends: [ + rtl.configs['flat/react'], + { + name: 'code-pushup/react-testing-library/customized', + rules: { + 'testing-library/no-await-sync-events': 'warn', + 'testing-library/no-await-sync-queries': 'warn', + 'testing-library/no-container': 'warn', + 'testing-library/no-node-access': 'warn', + 'testing-library/no-render-in-lifecycle': 'warn', + 'testing-library/no-wait-for-multiple-assertions': 'warn', + 'testing-library/prefer-find-by': 'warn', + 'testing-library/prefer-presence-queries': 'warn', + 'testing-library/prefer-query-matchers': 'warn', + 'testing-library/prefer-screen-queries': 'warn', + 'testing-library/render-result-naming-convention': 'warn', + }, + }, + { + name: 'code-pushup/react-testing-library/additional', + rules: { + 'testing-library/prefer-explicit-assert': 'warn', + 'testing-library/prefer-query-matchers': [ + 'warn', + { + validEntries: [ + { matcher: 'toBeVisible', query: 'get' }, + { matcher: 'toHaveTextContent', query: 'get' }, + { matcher: 'toBeEnabled', query: 'get' }, + { matcher: 'toBeDisabled', query: 'get' }, + { matcher: 'toBeChecked', query: 'get' }, + ], + }, + ], + 'testing-library/prefer-user-event': 'warn', + }, + }, + ], +}); diff --git a/tests/configs/react-testing-library.spec.js b/tests/configs/react-testing-library.spec.js new file mode 100644 index 0000000..f761a71 --- /dev/null +++ b/tests/configs/react-testing-library.spec.js @@ -0,0 +1,67 @@ +// @ts-check + +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { createLintUtils } from '../helpers/lint-utils'; + +describe('react-testing-library config', () => { + const { setup, teardown, loadConfig } = createLintUtils( + 'react-testing-library', + '*.spec.tsx', + ); + + beforeAll(setup); + + afterAll(teardown); + + it('should not include react-testing-library rules for non-test file', async () => { + const config = await loadConfig('components/Button.tsx'); + expect(Object.keys(config?.rules ?? {}).join(',')).not.toContain( + 'testing-library/', + ); + }); + + it('should include react-testing-library rules for test file', async () => { + const config = await loadConfig('components/Button.test.tsx'); + expect(Object.keys(config.rules ?? {}).join(',')).toContain( + 'testing-library/', + ); + }); + + it('should have rule from extended recommended react-testing-library config', async () => { + const config = await loadConfig(); + expect(config.rules).toHaveProperty('testing-library/await-async-events'); + }); + + it('should have explicitly added rule', async () => { + const config = await loadConfig(); + expect(config.rules).toHaveProperty('testing-library/prefer-user-event'); + }); + + it('should have customized severity level for rule from extended config', async () => { + const config = await loadConfig(); + expect(config.rules?.['testing-library/no-await-sync-queries']).toEqual([ + 1, + ]); + }); + + it('should have customized rule', async () => { + const config = await loadConfig(); + expect(config.rules).toHaveProperty( + 'testing-library/prefer-query-matchers', + ); + expect( + config.rules?.['testing-library/prefer-query-matchers'], + ).toStrictEqual([ + 1, + { + validEntries: [ + { matcher: 'toBeVisible', query: 'get' }, + { matcher: 'toHaveTextContent', query: 'get' }, + { matcher: 'toBeEnabled', query: 'get' }, + { matcher: 'toBeDisabled', query: 'get' }, + { matcher: 'toBeChecked', query: 'get' }, + ], + }, + ]); + }); +}); diff --git a/tests/helpers/lint-utils.js b/tests/helpers/lint-utils.js index 67ed5ac..9c8c49a 100644 --- a/tests/helpers/lint-utils.js +++ b/tests/helpers/lint-utils.js @@ -16,8 +16,9 @@ export function createLintUtils( defaultFilePath = '*.ts', filesToCreate = [], ) { - const cwd = path.join(process.cwd(), 'tmp', configName); + const camelCaseConfig = kebabToCamelCase(configName); + const cwd = path.join(process.cwd(), 'tmp', configName); const eslint = new ESLint({ cwd }); const setup = async () => { @@ -25,10 +26,10 @@ export function createLintUtils( await fs.mkdir(cwd, { recursive: true }); await fs.writeFile( path.join(cwd, 'eslint.config.js'), - `import ${configName} from '@code-pushup/eslint-config/${configName}.js' + `import ${camelCaseConfig} from '@code-pushup/eslint-config/${configName}.js' export default [ - ...${configName}, + ...${camelCaseConfig}, { languageOptions: { parserOptions: { @@ -116,3 +117,19 @@ export default [ getEnabledRuleIds, }; } + +/** + * Transforms kebab-case into camelCase + * @param {string} text + * @returns {string} + */ +export function kebabToCamelCase(text) { + return text + .split('-') + .map((word, index) => + index > 0 + ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + : word.toLowerCase(), + ) + .join(''); +} diff --git a/tests/helpers/lint-utils.spec.js b/tests/helpers/lint-utils.spec.js new file mode 100644 index 0000000..8c9de75 --- /dev/null +++ b/tests/helpers/lint-utils.spec.js @@ -0,0 +1,14 @@ +// @ts-check + +import { describe, expect, it } from 'vitest'; +import { kebabToCamelCase } from './lint-utils'; + +describe('kebabToCamelCase', () => { + it('should transform kebak-case to camelCase', async () => { + expect(kebabToCamelCase('kebab-case')).toBe('kebabCase'); + }); + + it('should capitalize subsequent words', async () => { + expect(kebabToCamelCase('ke-bAB-CAse')).toBe('keBabCase'); + }); +}); From 22547bb0ce8322b95cb49b957034ad679328ce07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katka=20Pil=C3=A1tov=C3=A1?= Date: Tue, 31 Dec 2024 19:31:35 +0100 Subject: [PATCH 2/2] Update rule severity and documentation for RTL --- docs/react-testing-library.md | 44 ++++++++++------------------ scripts/helpers/configs.js | 11 ------- src/configs/react-testing-library.js | 2 ++ 3 files changed, 18 insertions(+), 39 deletions(-) diff --git a/docs/react-testing-library.md b/docs/react-testing-library.md index 94deead..7bb6df7 100644 --- a/docs/react-testing-library.md +++ b/docs/react-testing-library.md @@ -17,42 +17,28 @@ Config for projects using **React Testing Library** for testing. import react-testing-library from '@code-pushup/eslint-config/react-testing-library.js'; import tseslint from 'typescript-eslint'; - export default tseslint.config( - ...react-testing-library, - { - // customize rules if needed: - rules: { - // e.g. to enforce using `queryBy*` or `getBy*` for matchers: - 'testing-library/prefer-query-matchers': [ - 'warn', - { validEntries: [{ matcher: 'toBeVisible', query: 'get' }] }, - ] - } - } - ); + export default tseslint.config(...react-testing-library); ``` ## 📏 Rules (25) > 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). -### 🚨 Errors (11) +### 🚨 Errors (9) -| Plugin | Rule | Options | Autofix | Overrides | -| :----------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :-----: | :-------: | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-events](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-events.md)
Enforce promises from async event methods are handled |
eventModule: userEvent
{
  "eventModule": "userEvent"
}
| 🔧 | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-queries.md)
Enforce promises from async queries to be handled | | | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-utils](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-utils.md)
Enforce promises from async utils to be awaited properly | | | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-dom-import](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-dom-import.md)
Disallow importing from DOM Testing Library |
react
"react"
| 🔧 | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-global-regexp-flag-in-query](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-global-regexp-flag-in-query.md)
Disallow the use of the global RegExp flag (/g) in queries | | 🔧 | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-manual-cleanup](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-manual-cleanup.md)
Disallow the use of `cleanup` | | | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-promise-in-fire-event](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-promise-in-fire-event.md)
Disallow the use of promises passed to a `fireEvent` method | | | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-unnecessary-act](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-unnecessary-act.md)
Disallow wrapping Testing Library utils or empty callbacks in `act` | | | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-side-effects](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-side-effects.md)
Disallow the use of side effects in `waitFor` | | | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-snapshot](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-snapshot.md)
Ensures no snapshot is generated inside of a `waitFor` call | | | | -| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-query-by-disappearance](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-query-by-disappearance.md)
Suggest using `queryBy*` queries when waiting for disappearance | | | | +| Plugin | Rule | Options | Autofix | Overrides | +| :----------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------- | :-----: | :-------: | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-events](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-events.md)
Enforce promises from async event methods are handled |
eventModule: userEvent
{
  "eventModule": "userEvent"
}
| 🔧 | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-queries.md)
Enforce promises from async queries to be handled | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [await-async-utils](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/await-async-utils.md)
Enforce promises from async utils to be awaited properly | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-dom-import](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-dom-import.md)
Disallow importing from DOM Testing Library |
react
"react"
| 🔧 | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-global-regexp-flag-in-query](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-global-regexp-flag-in-query.md)
Disallow the use of the global RegExp flag (/g) in queries | | 🔧 | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-promise-in-fire-event](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-promise-in-fire-event.md)
Disallow the use of promises passed to a `fireEvent` method | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-unnecessary-act](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-unnecessary-act.md)
Disallow wrapping Testing Library utils or empty callbacks in `act` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-side-effects](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-side-effects.md)
Disallow the use of side effects in `waitFor` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-snapshot](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-snapshot.md)
Ensures no snapshot is generated inside of a `waitFor` call | | | | -### ⚠️ Warnings (14) +### ⚠️ Warnings (16) | Plugin | Rule | Options | Autofix | Overrides | | :----------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :-----: | :-------: | @@ -60,12 +46,14 @@ Config for projects using **React Testing Library** for testing. | [![testing-library](./icons/other/testing-library.png)](undefined) | [no-await-sync-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-await-sync-queries.md)
Disallow unnecessary `await` for sync queries | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [no-container](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-container.md)
Disallow the use of `container` methods | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [no-debugging-utils](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-debugging-utils.md)
Disallow the use of debugging utilities like `debug` | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [no-manual-cleanup](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-manual-cleanup.md)
Disallow the use of `cleanup` | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [no-node-access](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-node-access.md)
Disallow direct Node access | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [no-render-in-lifecycle](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-render-in-lifecycle.md)
Disallow the use of `render` in testing frameworks setup functions | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [no-wait-for-multiple-assertions](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/no-wait-for-multiple-assertions.md)
Disallow the use of multiple `expect` calls inside `waitFor` | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-explicit-assert](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-explicit-assert.md)
Suggest using explicit assertions rather than standalone queries | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-find-by](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-find-by.md)
Suggest using `find(All)By*` query instead of `waitFor` + `get(All)By*` to wait for elements | | 🔧 | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-presence-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-presence-queries.md)
Ensure appropriate `get*`/`query*` queries are used with their respective matchers | | | | +| [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-query-by-disappearance](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-query-by-disappearance.md)
Suggest using `queryBy*` queries when waiting for disappearance | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-query-matchers](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-query-matchers.md)
Ensure the configured `get*`/`query*` query is used with the corresponding matchers | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-screen-queries](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-screen-queries.md)
Suggest using `screen` while querying | | | | | [![testing-library](./icons/other/testing-library.png)](undefined) | [prefer-user-event](https://github.com/testing-library/eslint-plugin-testing-library/tree/main/docs/rules/prefer-user-event.md)
Suggest using `userEvent` over `fireEvent` for simulating user interactions | | | | diff --git a/scripts/helpers/configs.js b/scripts/helpers/configs.js index bffedbc..787efe2 100644 --- a/scripts/helpers/configs.js +++ b/scripts/helpers/configs.js @@ -201,17 +201,6 @@ export const configsExtraEslintrc = { 'vitest/consistent-test-it': ['warn', { fn: 'test', withinDescribe: 'test' }] } }`, - 'react-testing-library': `, - { - // customize rules if needed: - rules: { - // e.g. to enforce using \`queryBy*\` or \`getBy*\` for matchers: - 'testing-library/prefer-query-matchers': [ - 'warn', - { validEntries: [{ matcher: 'toBeVisible', query: 'get' }] }, - ] - } - }`, }; /** diff --git a/src/configs/react-testing-library.js b/src/configs/react-testing-library.js index cd121b8..dcf50ce 100644 --- a/src/configs/react-testing-library.js +++ b/src/configs/react-testing-library.js @@ -14,11 +14,13 @@ export default tseslint.config({ 'testing-library/no-await-sync-events': 'warn', 'testing-library/no-await-sync-queries': 'warn', 'testing-library/no-container': 'warn', + 'testing-library/no-manual-cleanup': 'warn', 'testing-library/no-node-access': 'warn', 'testing-library/no-render-in-lifecycle': 'warn', 'testing-library/no-wait-for-multiple-assertions': 'warn', 'testing-library/prefer-find-by': 'warn', 'testing-library/prefer-presence-queries': 'warn', + 'testing-library/prefer-query-by-disappearance': 'warn', 'testing-library/prefer-query-matchers': 'warn', 'testing-library/prefer-screen-queries': 'warn', 'testing-library/render-result-naming-convention': 'warn',