diff --git a/.all-contributorsrc b/.all-contributorsrc index ab24e55..bd7f94d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -165,6 +165,25 @@ "code", "bug" ] + }, + { + "login": "huyenltnguyen", + "name": "Huyen Nguyen", + "avatar_url": "https://avatars.githubusercontent.com/u/25715018?v=4", + "profile": "https://github.com/huyenltnguyen", + "contributions": [ + "doc" + ] + }, + { + "login": "mdotwills", + "name": "Matthew", + "avatar_url": "https://avatars.githubusercontent.com/u/5505611?v=4", + "profile": "https://github.com/mdotwills", + "contributions": [ + "bug", + "code" + ] } ] } diff --git a/.github/workflows/eslint-plugin-jest-dom.yml b/.github/workflows/eslint-plugin-jest-dom.yml deleted file mode 100644 index e8783a3..0000000 --- a/.github/workflows/eslint-plugin-jest-dom.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: eslint-plugin-jest-dom -on: - push: - branches: - - "master" - - "alpha" - pull_request: - -jobs: - test: - name: "node ${{ matrix.node }} ${{matrix.browser}} ${{ matrix.os }} " - runs-on: "${{ matrix.os }}" - strategy: - matrix: - os: [ubuntu-latest] - node: [10, 12, 14, 16] - steps: - - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - - uses: actions/checkout@v2 - - run: INIT_CWD="$(pwd)" npm install - - run: npm run validate - - run: npx codecov@3 - release: - runs-on: ubuntu-latest - needs: test - steps: - - uses: actions/setup-node@v2 - with: - node-version: 14 - - uses: actions/checkout@v2 - - run: npm install - - run: npm run build - - run: ls -asl dist - - run: npx semantic-release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index aa4f211..23844b4 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -2,21 +2,23 @@ name: Smoke test on: schedule: - - cron: '0 0 * * SUN' + - cron: "0 0 * * SUN" workflow_dispatch: jobs: - lint: + test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: 12.11 - - run: yarn install - - run: yarn build - - run: yarn link - - run: yarn link eslint-plugin-jest-dom - - run: yarn test:smoke - env: - CI: true \ No newline at end of file + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + - run: | + npm install + npm run build + npm link + npm link eslint-plugin-jest-dom + - uses: AriPerkkio/eslint-remote-tester-run-action@v3 + with: + issue-title: "Results of weekly scheduled smoke test" + eslint-remote-tester-config: smoke-test/eslint-remote-tester.config.js diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..9991d22 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,100 @@ +name: validate +on: + push: + branches: + - "+([0-9])?(.{+([0-9]),x}).x" + - "main" + - "next" + - "next-major" + - "beta" + - "alpha" + - "!all-contributors/**" + pull_request: {} +jobs: + main: + # ignore all-contributors PRs + if: ${{ !contains(github.head_ref, 'all-contributors') }} + strategy: + matrix: + eslint: [6.8.0, 6, 7.0.0, 7, 8.0.0, 8] + node: [12.22.0, 12, 14.17.0, 14, 16.0.0, 16] + runs-on: ubuntu-latest + steps: + - name: πŸ›‘ Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.11.0 + + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: βŽ” Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + + - name: πŸ“₯ Download deps + uses: bahmutov/npm-install@v1 + with: + useLockFile: false + + - name: Install ESLint v${{ matrix.eslint }} + run: npm install --no-save --force eslint@${{ matrix.eslint }} + + - name: ▢️ Run validate script (without linting) + if: ${{ matrix.eslint != 8 }} + run: npm run validate -- build,test:coverage + + - name: ▢️ Run validate script (with linting) + if: ${{ matrix.eslint == 8 }} + run: npm run validate + + - name: ▢️ Ensure docs are up-to-date + if: ${{ matrix.eslint == 8 && matrix.node == 16 }} + run: npm run lint:generate-readme-table + + - name: ⬆️ Upload coverage report + uses: codecov/codecov-action@v3 + + release: + needs: main + runs-on: ubuntu-latest + if: ${{ github.repository == 'testing-library/eslint-plugin-jest-dom' && + contains('refs/heads/main,refs/heads/beta,refs/heads/next,refs/heads/alpha', + github.ref) && github.event_name == 'push' }} + steps: + - name: πŸ›‘ Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.11.0 + + - name: ⬇️ Checkout repo + uses: actions/checkout@v3 + + - name: βŽ” Setup node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: πŸ“₯ Download deps + uses: bahmutov/npm-install@v1 + with: + useLockFile: false + + - name: πŸ— Run build script + run: npm run build + + - run: ls -asl dist + + - name: πŸš€ Release + uses: cycjimmy/semantic-release-action@v2 + with: + semantic_version: 18 + branches: | + [ + '+([0-9])?(.{+([0-9]),x}).x', + 'main', + 'next', + 'next-major', + {name: 'beta', prerelease: true}, + {name: 'alpha', prerelease: true} + ] + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 6c6bf8a..f66f450 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,7 @@ [![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] [![MIT License][license-badge]][license] - - -[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-) - +[![All Contributors][all-contributors-badge]](#contributors-) [![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] @@ -46,7 +43,7 @@ should be installed as one of your project's `devDependencies`: npm install --save-dev eslint-plugin-jest-dom ``` -This library has a required `peerDependencies` listing for [`eslint`][eslint] +This library has a required `peerDependencies` listing for [`ESLint`](https://eslint.org/). ## Usage @@ -95,27 +92,27 @@ module.exports = { ## Supported Rules -πŸ‘ indicates that a rule is recommended for all users. - -πŸ”§ indicates that a rule is fixable. + - +πŸ’Ό Configurations enabled in.\ +βœ… Set in the `recommended` configuration.\ +πŸ”§ Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix). -| Name | πŸ‘ | πŸ”§ | Description | -| ---------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | --------------------------------------------------------------------- | -| [prefer-checked](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-checked.md) | πŸ‘ | πŸ”§ | prefer toBeChecked over checking attributes | -| [prefer-empty](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-empty.md) | πŸ‘ | πŸ”§ | Prefer toBeEmpty over checking innerHTML | -| [prefer-enabled-disabled](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-enabled-disabled.md) | πŸ‘ | πŸ”§ | prefer toBeDisabled or toBeEnabled over checking attributes | -| [prefer-focus](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-focus.md) | πŸ‘ | πŸ”§ | prefer toHaveFocus over checking document.activeElement | -| [prefer-in-document](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-in-document.md) | πŸ‘ | πŸ”§ | Prefer .toBeInTheDocument() for asserting the existence of a DOM node | -| [prefer-required](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-required.md) | πŸ‘ | πŸ”§ | prefer toBeRequired over checking properties | -| [prefer-to-have-attribute](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-attribute.md) | πŸ‘ | πŸ”§ | prefer toHaveAttribute over checking getAttribute/hasAttribute | -| [prefer-to-have-class](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-class.md) | πŸ‘ | πŸ”§ | prefer toHaveClass over checking element className | -| [prefer-to-have-style](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-style.md) | πŸ‘ | πŸ”§ | prefer toHaveStyle over checking element style | -| [prefer-to-have-text-content](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-text-content.md) | πŸ‘ | πŸ”§ | Prefer toHaveTextContent over checking element.textContent | -| [prefer-to-have-value](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/prefer-to-have-value.md) | πŸ‘ | πŸ”§ | prefer toHaveValue over checking element.value | +| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  | Description | πŸ’Ό | πŸ”§ | +| :----------------------------------------------------------------------- | :-------------------------------------------------------------------- | :- | :- | +| [prefer-checked](docs/rules/prefer-checked.md) | prefer toBeChecked over checking attributes | βœ… | πŸ”§ | +| [prefer-empty](docs/rules/prefer-empty.md) | Prefer toBeEmpty over checking innerHTML | βœ… | πŸ”§ | +| [prefer-enabled-disabled](docs/rules/prefer-enabled-disabled.md) | prefer toBeDisabled or toBeEnabled over checking attributes | βœ… | πŸ”§ | +| [prefer-focus](docs/rules/prefer-focus.md) | prefer toHaveFocus over checking document.activeElement | βœ… | πŸ”§ | +| [prefer-in-document](docs/rules/prefer-in-document.md) | Prefer .toBeInTheDocument() for asserting the existence of a DOM node | βœ… | πŸ”§ | +| [prefer-required](docs/rules/prefer-required.md) | prefer toBeRequired over checking properties | βœ… | πŸ”§ | +| [prefer-to-have-attribute](docs/rules/prefer-to-have-attribute.md) | prefer toHaveAttribute over checking getAttribute/hasAttribute | βœ… | πŸ”§ | +| [prefer-to-have-class](docs/rules/prefer-to-have-class.md) | prefer toHaveClass over checking element className | βœ… | πŸ”§ | +| [prefer-to-have-style](docs/rules/prefer-to-have-style.md) | prefer toHaveStyle over checking element style | βœ… | πŸ”§ | +| [prefer-to-have-text-content](docs/rules/prefer-to-have-text-content.md) | Prefer toHaveTextContent over checking element.textContent | βœ… | πŸ”§ | +| [prefer-to-have-value](docs/rules/prefer-to-have-value.md) | prefer toHaveValue over checking element.value | βœ… | πŸ”§ | - + ## Issues @@ -163,6 +160,8 @@ Thanks goes to these people ([emoji key][emojis]):
Gareth Jones

⚠️ πŸ’» πŸ› +
Huyen Nguyen

πŸ“– +
Matthew

πŸ› πŸ’» @@ -181,8 +180,8 @@ MIT [npm]: https://www.npmjs.com [node]: https://nodejs.org -[build-badge]: https://img.shields.io/travis/testing-library/eslint-plugin-jest-dom.svg?style=flat-square -[build]: https://travis-ci.org/testing-library/eslint-plugin-jest-dom +[build-badge]: https://img.shields.io/github/workflow/status/testing-library/eslint-plugin-jest-dom/validate?logo=github&style=flat-square +[build]: https://github.com/testing-library/eslint-plugin-jest-dom/actions?query=workflow%3Avalidate [coverage-badge]: https://img.shields.io/codecov/c/github/testing-library/eslint-plugin-jest-dom.svg?style=flat-square [coverage]: https://codecov.io/github/testing-library/eslint-plugin-jest-dom [version-badge]: https://img.shields.io/npm/v/eslint-plugin-jest-dom.svg?style=flat-square @@ -190,11 +189,11 @@ MIT [downloads-badge]: https://img.shields.io/npm/dm/eslint-plugin-jest-dom.svg?style=flat-square [npmtrends]: http://www.npmtrends.com/eslint-plugin-jest-dom [license-badge]: https://img.shields.io/npm/l/eslint-plugin-jest-dom.svg?style=flat-square -[license]: https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/LICENSE +[license]: https://github.com/testing-library/eslint-plugin-jest-dom/blob/main/LICENSE [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square [prs]: http://makeapullrequest.com [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square -[coc]: https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/other/CODE_OF_CONDUCT.md +[coc]: https://github.com/testing-library/eslint-plugin-jest-dom/blob/main/other/CODE_OF_CONDUCT.md [emojis]: https://github.com/all-contributors/all-contributors#emoji-key [all-contributors]: https://github.com/all-contributors/all-contributors [bugs]: https://github.com/testing-library/eslint-plugin-jest-dom/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Acreated-desc+label%3Abug diff --git a/build/generate-readme-table.js b/build/generate-readme-table.js deleted file mode 100644 index 8e5cd22..0000000 --- a/build/generate-readme-table.js +++ /dev/null @@ -1,55 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const rules = require("..").rules; - -const README_LOCATION = path.resolve(__dirname, "..", "README.md"); -const BEGIN_TABLE_MARKER = "\n"; -const END_TABLE_MARKER = "\n"; - -const expectedTableLines = Object.keys(rules) - .sort() - .reduce( - (lines, ruleId) => { - const rule = rules[ruleId]; - - lines.push( - [ - `[${ruleId}](https://github.com/testing-library/eslint-plugin-jest-dom/blob/master/docs/rules/${ruleId}.md)`, - rule.meta.docs.recommended ? "πŸ‘" : "", - rule.meta.fixable ? "πŸ”§" : "", - rule.meta.docs.description, - ].join(" | ") - ); - - return lines; - }, - ["Name | πŸ‘ | πŸ”§ | Description", "----- | ----- | ----- | -----"] - ) - .join("\n"); - -const readmeContents = fs.readFileSync(README_LOCATION, "utf8"); - -if (!readmeContents.includes(BEGIN_TABLE_MARKER)) { - throw new Error( - `Could not find '${BEGIN_TABLE_MARKER}' marker in README.md.` - ); -} - -if (!readmeContents.includes(END_TABLE_MARKER)) { - throw new Error(`Could not find '${END_TABLE_MARKER}' marker in README.md.`); -} - -const linesStartIndex = - readmeContents.indexOf(BEGIN_TABLE_MARKER) + BEGIN_TABLE_MARKER.length; -const linesEndIndex = readmeContents.indexOf(END_TABLE_MARKER); - -const updatedReadmeContents = - readmeContents.slice(0, linesStartIndex) + - expectedTableLines + - readmeContents.slice(linesEndIndex); - -if (module.parent) { - module.exports = updatedReadmeContents; -} else { - fs.writeFileSync(README_LOCATION, updatedReadmeContents); -} diff --git a/docs/rules/prefer-checked.md b/docs/rules/prefer-checked.md index c2a4fc0..295e82a 100644 --- a/docs/rules/prefer-checked.md +++ b/docs/rules/prefer-checked.md @@ -1,4 +1,10 @@ -# prefer toBeChecked() or not.toBeChecked() over toHaveProperty('checked', true|false) (prefer-enabled-checked) +# Prefer toBeChecked over checking attributes (`jest-dom/prefer-checked`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + ## Rule Details diff --git a/docs/rules/prefer-empty.md b/docs/rules/prefer-empty.md index 30ead57..a9e3614 100644 --- a/docs/rules/prefer-empty.md +++ b/docs/rules/prefer-empty.md @@ -1,4 +1,10 @@ -# Prefer toBeEmptyDOMElement over checking innerHTML / firstChild (prefer-empty) +# Prefer toBeEmpty over checking innerHTML (`jest-dom/prefer-empty`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + This rule ensures people will use toBeEmptyDOMElement() rather than checking dom nodes/properties. It is primarily aimed at consistently using jest-dom for @@ -57,7 +63,7 @@ Don't use this rule if you don't care if people use `.toBeEmptyDOMElement()`. - + ## Changelog diff --git a/docs/rules/prefer-enabled-disabled.md b/docs/rules/prefer-enabled-disabled.md index 7a34e38..34ea28d 100644 --- a/docs/rules/prefer-enabled-disabled.md +++ b/docs/rules/prefer-enabled-disabled.md @@ -1,4 +1,10 @@ -# prefer toBeDisabled() or toBeEnabled() over toHaveProperty('disabled', true|false) (prefer-enabled-disabled) +# Prefer toBeDisabled or toBeEnabled over checking attributes (`jest-dom/prefer-enabled-disabled`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + ## Rule Details diff --git a/docs/rules/prefer-focus.md b/docs/rules/prefer-focus.md index a7e8dda..7d9e938 100644 --- a/docs/rules/prefer-focus.md +++ b/docs/rules/prefer-focus.md @@ -1,4 +1,10 @@ -# prefer-focus +# Prefer toHaveFocus over checking document.activeElement (`jest-dom/prefer-focus`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + prefer toHaveFocus over checking document.activeElement (prefer-focus) diff --git a/docs/rules/prefer-in-document.md b/docs/rules/prefer-in-document.md index ae1072d..a66848d 100644 --- a/docs/rules/prefer-in-document.md +++ b/docs/rules/prefer-in-document.md @@ -1,4 +1,10 @@ -# Prefer .toBeInTheDocument in favor of .toHaveLength(1) (prefer-in-document) +# Prefer .toBeInTheDocument() for asserting the existence of a DOM node (`jest-dom/prefer-in-document`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + ## Rule Details @@ -21,6 +27,10 @@ expect(queryByText("foo")).toEqual(null); expect(queryByText("foo")).not.toEqual(null); expect(queryByText("foo")).toBeDefined(); expect(queryByText("foo")).not.toBeDefined(); +expect(queryByText("foo")).toBeTruthy(); +expect(queryByText("foo")).not.toBeTruthy(); +expect(queryByText("foo")).toBeFalsy(); +expect(queryByText("foo")).not.toBeFalsy(); const foo = screen.getByText("foo"); expect(foo).toHaveLength(1); diff --git a/docs/rules/prefer-required.md b/docs/rules/prefer-required.md index 437638f..54c6617 100644 --- a/docs/rules/prefer-required.md +++ b/docs/rules/prefer-required.md @@ -1,4 +1,10 @@ -# prefer toBeRequired() or not.toBeRequired() over toHaveProperty('required', true|false) (prefer-enabled-required) +# Prefer toBeRequired over checking properties (`jest-dom/prefer-required`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + ## Rule Details diff --git a/docs/rules/prefer-to-have-attribute.md b/docs/rules/prefer-to-have-attribute.md index ed887a3..c290ceb 100644 --- a/docs/rules/prefer-to-have-attribute.md +++ b/docs/rules/prefer-to-have-attribute.md @@ -1,4 +1,10 @@ -# prefer toHaveAttribute over checking getAttribute/hasAttribute (prefer-to-have-attribute) +# Prefer toHaveAttribute over checking getAttribute/hasAttribute (`jest-dom/prefer-to-have-attribute`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + This rule is an autofixable rule that reports usages of `getAttribute` or `hasAttribute` in expect statements in preference of using the jest-dom diff --git a/docs/rules/prefer-to-have-class.md b/docs/rules/prefer-to-have-class.md index 5978794..f4f2937 100644 --- a/docs/rules/prefer-to-have-class.md +++ b/docs/rules/prefer-to-have-class.md @@ -1,4 +1,10 @@ -# prefer toHaveClass over checking element.class (prefer-to-have-class) +# Prefer toHaveClass over checking element className (`jest-dom/prefer-to-have-class`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + This rule is an autofixable rule that reports usages of checking element className or classList in expect statements in preference of using the jest-dom `toHaveClass` matcher. diff --git a/docs/rules/prefer-to-have-style.md b/docs/rules/prefer-to-have-style.md index 118030e..bf61d9e 100644 --- a/docs/rules/prefer-to-have-style.md +++ b/docs/rules/prefer-to-have-style.md @@ -1,4 +1,10 @@ -# prefer toHaveStyle over checking element.style (prefer-to-have-style) +# Prefer toHaveStyle over checking element style (`jest-dom/prefer-to-have-style`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + This rule is an autofixable rule that reports usages of checking element.style in expect statements in preference of using the jest-dom `toHaveStyle` matcher. diff --git a/docs/rules/prefer-to-have-text-content.md b/docs/rules/prefer-to-have-text-content.md index f10c6b2..a960908 100644 --- a/docs/rules/prefer-to-have-text-content.md +++ b/docs/rules/prefer-to-have-text-content.md @@ -1,4 +1,10 @@ -# Prefer toHaveTextContent over checking element.textContent (prefer-to-have-text-content) +# Prefer toHaveTextContent over checking element.textContent (`jest-dom/prefer-to-have-text-content`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + Please describe the origin of the rule here. diff --git a/docs/rules/prefer-to-have-value.md b/docs/rules/prefer-to-have-value.md index 35a6074..544b2f5 100644 --- a/docs/rules/prefer-to-have-value.md +++ b/docs/rules/prefer-to-have-value.md @@ -1,4 +1,10 @@ -# prefer toHaveAttribute over checking getAttribute/hasAttribute (prefer-to-have-attribute) +# Prefer toHaveValue over checking element.value (`jest-dom/prefer-to-have-value`) + +πŸ’Ό This rule is enabled in the βœ… `recommended` config. + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + This rule is an autofixable rule that encourages the use of toHaveValue over checking the value attribute. diff --git a/other/MAINTAINING.md b/other/MAINTAINING.md index c78fc0b..e0c40cc 100644 --- a/other/MAINTAINING.md +++ b/other/MAINTAINING.md @@ -48,7 +48,7 @@ to release. See the next section on Releases for more about that. ## Release -Our releases are automatic. They happen whenever code lands into `master`. A +Our releases are automatic. They happen whenever code lands into `main`. A travis build gets kicked off and if it's successful, a tool called [`semantic-release`](https://github.com/semantic-release/semantic-release) is used to automatically publish a new release to npm as well as a changelog to diff --git a/package.json b/package.json index 9271bef..1fd6f3b 100644 --- a/package.json +++ b/package.json @@ -30,49 +30,49 @@ ], "scripts": { "build": "kcd-scripts build", - "pregenerate-readme-table": "yarn build", - "generate-readme-table": "node build/generate-readme-table.js", + "pregenerate-readme-table": "npm run build", + "generate-readme-table": "eslint-doc-generator --ignore-config all", "lint": "kcd-scripts lint", + "lint:generate-readme-table": "npm run generate-readme-table -- --check", "setup": "npm install && npm run validate -s", "test": "kcd-scripts test", - "test:smoke": "eslint-remote-tester --config ./smoke-test/eslint-remote-tester.config.js", - "test:update": "npm test -- --updateSnapshot --coverage", + "test:coverage": "npm test -- --coverage", + "test:update": "npm test:coverage -- --updateSnapshot", "validate": "kcd-scripts validate" }, "dependencies": { - "@babel/runtime": "^7.9.6", - "@testing-library/dom": "^7.28.1", + "@babel/runtime": "^7.16.3", + "@testing-library/dom": "^8.11.1", "requireindex": "^1.2.0" }, "devDependencies": { - "@typescript-eslint/parser": "^4.8.2", - "eslint": "7.24", - "eslint-remote-tester": "^0.3.3", - "jest-extended": "^0.11.5", - "kcd-scripts": "6.5.1", - "typescript": "^4.1.2" + "@typescript-eslint/parser": "^5.9.1", + "eslint": "^8.7.0", + "eslint-doc-generator": "^0.19.0", + "eslint-remote-tester": "^3.0.0", + "eslint-remote-tester-repositories": "^0.0.7", + "kcd-scripts": "^12.0.0", + "typescript": "^4.5.3" }, "peerDependencies": { - "eslint": ">=6.8" + "eslint": "^6.8.0 || ^7.0.0 || ^8.0.0" }, "eslintConfig": { "extends": "./node_modules/kcd-scripts/eslint.js", "rules": { - "babel/quotes": "off", + "consistent-return": "off", "max-lines-per-function": "off", - "testing-library/no-dom-import": "off", - "consistent-return": "off" + "testing-library/no-dom-import": "off" } }, "eslintIgnore": [ "node_modules", "coverage", "dist", - ".cache-eslint-remote-tester", "eslint-remote-tester-results" ], "engines": { - "node": "^10.12.0 || >=12.0.0", + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", "npm": ">=6", "yarn": ">=1" } diff --git a/smoke-test/eslint-remote-tester.config.js b/smoke-test/eslint-remote-tester.config.js index e281c08..6b48dfe 100644 --- a/smoke-test/eslint-remote-tester.config.js +++ b/smoke-test/eslint-remote-tester.config.js @@ -1,37 +1,15 @@ -module.exports = { - /** Repositories to scan */ - repositories: require("./repositories.json"), +const { + getRepositories, + getPathIgnorePattern, +} = require("eslint-remote-tester-repositories"); - /** Extensions of files under scanning */ +module.exports = { + repositories: getRepositories({ randomize: true }), + pathIgnorePattern: getPathIgnorePattern(), extensions: ["js", "jsx", "ts", "tsx"], - - /** Optional pattern used to exclude paths */ - pathIgnorePattern: `(${[ - "node_modules", - "\\/\\.", // Any file or directory starting with dot, e.g. ".git" - "/dist/", - "/build/", - - // Common patterns for minified JS - "babel\\.js", - "vendor\\.js", - "vendors\\.js", - "chunk\\.js", - "bundle\\.js", - "react-dom\\.development\\.js", - "\\.min\\.js", // Any *.min.js - ].join("|")})`, - - /** Empty array since we are only interested in linter crashes */ - rulesUnderTesting: [], - - /** Maximum amount of tasks ran concurrently */ - concurrentTasks: 2, - - /** Optional boolean flag used to enable caching of cloned repositories. For CIs it's ideal to disable caching. Defauls to true. */ + concurrentTasks: 3, cache: false, - - /** ESLint configuration */ + logLevel: "info", eslintrc: { root: true, env: { @@ -45,7 +23,6 @@ module.exports = { jsx: true, }, }, - plugins: ["jest-dom"], extends: ["plugin:jest-dom/all"], }, }; diff --git a/smoke-test/repositories.json b/smoke-test/repositories.json deleted file mode 100644 index 10c53ac..0000000 --- a/smoke-test/repositories.json +++ /dev/null @@ -1,480 +0,0 @@ -[ - "gatsbyjs/gatsby", - "reduxjs/react-redux", - "ionic-team/ionic-framework", - "NervJS/taro", - "netlify/netlify-cms", - "Popmotion/popmotion", - "ctrlplusb/easy-peasy", - "cdapio/cdap", - "boltdesignsystem/bolt", - "webiny/webiny-js", - "alibaba/formily", - "trojanowski/react-apollo-hooks", - "kata-ai/aksara-ui", - "FirebaseExtended/reactfire", - "jaredpalmer/the-platform", - "namespace-ee/react-calendar-timeline", - "wsmd/react-use-form-state", - "donavon/use-persisted-state", - "auth0/cosmos", - "commercetools/merchant-center-application-kit", - "pismo/bolt", - "NDLANO/frontend-packages", - "anish000kumar/redux-box", - "concentjs/concent", - "tdeekens/flopflip", - "neo4j/neo4j-browser", - "the-dr-lazy/deox", - "solid/react-components", - "cdmbase/fullstack-pro", - "acl-services/paprika", - "react-cosmos/react-cosmos-classic", - "react-boilerplate/react-boilerplate", - "alvarotrigo/react-fullpage", - "techniq/react-fetch-component", - "derrickbeining/react-atom", - "plougsgaard/react-timeout", - "vtex/styleguide", - "Availity/availity-workflow", - "escaladesports/gatsby-plugin-prefetch-google-fonts", - "meetalva/alva", - "apollographql/fullstack-tutorial", - "marcin-piela/react-fetching-library", - "wmira/react-sidenav", - "dericgw/react-tiny-fab", - "davidjbradshaw/eslint-config-adjunct", - "livewire/livewire", - "framer/motion", - "Rocketseat/unform", - "kentcdodds/react-hooks", - "pietrzakadrian/bank", - "mobxjs/mst-gql", - "lostpebble/pullstate", - "davidgilbertson/react-recollect", - "jxom/react-loads", - "mpontus/react-modal-hook", - "willb335/chessboardjsx", - "proyecto26/ion-phaser", - "ticketmaster/aurora", - "goblindegook/littlefoot", - "roginfarrer/react-collapsed", - "pagarme/former-kit", - "quiltdata/t4", - "inrupt/solid-react-components", - "poanetwork/tokenbridge", - "liferay/com-liferay-commerce", - "inrupt/generator-solid-react", - "vtex/test-tools", - "techniq/react-odata", - "escaladesports/react-netlify-form", - "homelight/swan-form", - "repaygithub/cactus", - "aweary/react-copy-write", - "kentcdodds/advanced-react-patterns", - "async-library/react-webworker", - "theodo/falco", - "beizhedenglong/react-hooks-lib", - "skidding/react-testing-examples", - "saisandeepvaddi/ten-hands", - "ngxf/platform", - "WaftTech/WaftEngine", - "lightspeed/flame", - "googlemap-react/googlemap-react", - "devconzm/devconzm", - "mirego/react-boilerplate", - "stevey-p/react-localize", - "aml2610/react-painter", - "zopaUK/react-components", - "barnardos/design-system", - "wangtao0101/resa", - "easynvest/easyguide", - "MyCryptoHQ/ui", - "WTW-IM/es-components", - "neosiae/react-aria-offcanvas", - "ubergrape/grape-web-client", - "gokulkrishh/react-hooks-accordion", - "azdanov/azdanov.github.io", - "wx-chevalier/m-fe-commons", - "we-mak/w-design", - "sembark/gladio", - "mmmurray/mmm-scripts", - "boyney123/mockit", - "gregberge/react-teleporter", - "donavon/use-dark-mode", - "open-source-labs/spearmint", - "selbekk/calidation", - "entria/entria-fullstack", - "ilyalesik/react-fetch-hook", - "kentcdodds/jest-cypress-react-babel-webpack", - "revelcw/react-hooks-helper", - "Ephem/react-aldrin", - "dessa-oss/atlas", - "gpietro/react-numpad", - "topheman/npm-registry-browser", - "donavon/use-firebase-auth", - "salvoravida/react-redux-fork", - "italoiz/unform-community-packages", - "velopert/learn-react-testing", - "jamesplease/react-state-context", - "nyaruka/floweditor", - "coodoo/xstate-examples", - "neetjn/v-localize", - "sesilio/react-script-loader-hoc", - "ilkeryilmaz/bilyeli", - "bmcmahen/react-gesture-responder", - "MaximeHeckel/blog.maximeheckel.com", - "cellog/ion-router", - "jdlehman/switcheroo", - "vrk-kpa/suomifi-ui-components", - "rahsheen/react-wizard", - "unicorn-utterances/unicorn-utterances", - "uber/graph.gl", - "reime005/react-native-tapjoy", - "traveloka/react-diode", - "wyze/bs-jest-dom", - "Asjas/personal-website", - "ismailman/react-spho", - "lessp/react-is-visible", - "Patrickskiba/weborg", - "orbs-network/orbs-ethereum-contracts-v1", - "doitadrian/react-columned", - "International-Slackline-Association/Slackline-App", - "charliewilco/downwrite", - "summersky2014/bsl", - "alexandernanberg/formin", - "frinyvonnick/handling-peer-dependencies", - "prodo-ai/snoopy", - "jane/react-now-you-see-me", - "GetJobber/atlantis", - "quintype/quintype-node-components", - "thegrinder/react-handy-hooks", - "alpinejs/alpine", - "kentcdodds/react-testing-library-course", - "microsoft/BotFramework-Composer", - "kentcdodds/modern-react", - "Coding-Coach/coding-coach", - "blocknative/assist", - "tenon-io/tenon-ui", - "soska/keyboardist", - "ctaylo21/termy-the-terminal", - "kqito/use-global-context", - "adambrgmn/react-oauth-flow", - "commonlabs-id/next-with-analytics", - "traveloka/react-accio", - "factn/resilience-app", - "byai/topology", - "empathyco/solid-oh-my-pod", - "SubstraFoundation/substra-frontend", - "steamory/portal", - "donavon/use-firebase-app", - "Royal-Navy/standards-toolkit", - "albinotonnina/react-redux-magichat-demo-booking-system", - "ensdomains/ens-app", - "SubstraFoundation/substra-ui", - "tomwayson/create-arcgis-app", - "sensu/web", - "reime005/react-native-camera-hooks", - "ScalablyTyped/Converter", - "helixbass/ad-hok", - "First-Peoples-Cultural-Council/fv-web-ui", - "limistah/react-here-map", - "leonardodino/forex-web-app", - "TwinePlatform/twine-visitor", - "erezrokah/serverless-monitoring-app", - "awmleer/jorum", - "heydoctor/synaptik", - "donavon/splashr", - "clarisights/KnitUI", - "AntonRublev360/react-app-integration-tests-sample", - "fozg/react-light-state", - "WiredSolutions/react-azure-maps", - "zulucoda/react-swift-slider", - "tiendq/youtube-embed-video", - "mmmurray/react-super-state", - "Shilza/react-pretty-drawer", - "rishichawda/minimal-react-boilerplate", - "0xTracker/0x-tracker-client", - "prisma/dataguide", - "virtru/protect-and-track", - "inthepocket/itp-react-components", - "summerua/react-uforms", - "bopen/react-jsonschema-form-async", - "zurda/github-user-finder", - "Minivera/react-vitae", - "gustavobini/react-google-optimize", - "sean-macfarlane/Mapgram", - "gazpachu/sugui", - "jobn/braid", - "knpwrs/react-compose-components", - "Spring3/redshape", - "zhunor/threejs-dem-visualizer", - "kareemkibue/k2-react-utils", - "helpscout/brigade", - "veksen/homepage", - "alexkhismatulin/react-use-count-down", - "lightelligence-io/react", - "5monkeys/django-bananas.js", - "and-end/obibok", - "indiana-university/itpeople-app", - "stevenfitzpatrick/fitzy", - "debtcollective/disputes", - "elijahmanor/react-fun", - "ambanum/political-ads-crowdsourcing-client", - "eGroupAI/egroup-material", - "sunilshw/ThreatHunter", - "neo4j-devtools/relate-by-ui", - "horacehylee/chrome-ext-youtube-pickup", - "jguyon/nupum", - "azdanov/weather-react-native", - "virtru/virtuoso-design-system", - "nju33/react-dayo", - "MaXeraph/Portal", - "miguelangeltorresfp/official-apollo-fullstack-tutorial", - "lindsayjopson/apollo_tutorial", - "wirelineio/shogi", - "codacy/styleguide", - "FIL1994/spectre-react-lib", - "n1analytics/pihanga", - "diessetechnology/give-me-a-dog", - "ryanbas21/ryan_blog", - "umbiliko/um-react-core", - "ToniRib/apollo_fullstack_tutorial", - "dominicfallows/df-multichannel-app", - "groupystinks/formask", - "ishakuni/frontend", - "parthibankrpa/libraryFE", - "rodrigovive/website", - "Twistbioscience/DesignerComponents", - "bopen/react-jsonschema-form-field-geolocation", - "chuntley/dom-testing-extended", - "testing-library/dom-testing-library", - "frankieyan/custom-meta-input", - "villeheikkila/fullstackopen", - "kentcdodds/learn-react", - "kentcdodds/create-react-app-react-testing-library-example", - "avanslaars/egghead-react-boilerplate", - "leonardomso/react-bolt", - "rstacruz/penpad", - "timdeschryver/frontal", - "zsajjad/rtk-demo", - "mjtischler/react-muuri", - "PlayMa256/react-initial-bootstrap", - "donavon/use-visibility-change", - "inloco/orion", - "devthiago/react-ui-hooks", - "sync/hackerz", - "chrisjpatty/crooks", - "aaronnuu/react-layout", - "thiagoleitedev/rest-fullstack", - "juliettepretot/hookIntoProps", - "Raiffeisen-DGTL/ecom-payment-sdk", - "albizures/react-dynamic-layout", - "enzsft/react-cookie-consents", - "leoyli/addon-contexts", - "mbrn/material-table.com", - "failure-driven/bdd-workshop-app", - "jefflau/apollo-client-mock", - "x48-crypto/yearn-recycler", - "Lambda-School-Labs/labs9-employee-scheduler", - "Kodeworks/liquidator", - "tegraoss/luffie", - "rysolv/rysolv", - "ionic-team/stencil-ds-react-template", - "ionic-team/stencil-ds-plugins-demo", - "reactgraphqlacademy/bootcamp-hackathon", - "Yekon08/RiotApi", - "invinst/invisible-flow", - "donavon/use-prev-prop", - "somarmeteorologia/momentum", - "hamlim/react-preview-editor", - "x48-crypto/yearn-party", - "bsonntag/react-use-countdown", - "jpreecedev/premium-property-finder", - "TKT-ohjaajarekisteri/TKT-ohjaajarekisteri-front", - "react-hulks/react-hulks", - "dreamworld1101/react-redux", - "victormath12/lego-ds", - "ilyalesik/react-use-trigger", - "bibekg/react-audio-viz", - "bsonntag/react-use-toggle", - "kyleshevlin/react-edges", - "minwork/use-double-tap", - "doitadrian/react-butterfiles", - "xuqiongkai/ALTER", - "NoQuarterTeam/split", - "facebookincubator/fbc-js-core", - "arcticicestudio/nord-docs", - "syberos-team/syberh", - "man-of-sbk/reactjs-phe-phim-clone", - "reactway/saga", - "thanglv2/E-commerce-ReactJs", - "RegioneER/rer.bandi", - "WojciechMatuszewski/PartyPlanner", - "amille44420/react-fetcher", - "tmns/never-forget-app", - "SFDO-Tooling/MetaDeploy", - "chingu-voyages/v9-bears-team-38", - "functionalStoic/react-elements-selector", - "arminyahya/react-multiple-select-dropdown", - "xiaofan2406/fiora", - "9jaswag/eCommerce-frontend", - "aozora/react-showtime", - "enBonnet/hacktoberfest-dashboard", - "derozn/banterstudios", - "ipatate/react-scroll-progress-read", - "TobiasWalle/clean-forms", - "ahmadnoorniazi/react-advance-boilerplate", - "erictooth/react-accessible-form", - "emortlock/spartan-ui", - "zulucoda/react-tunes-player", - "fitfab/fitfab-ui", - "bob-lee/react-joanne", - "ericwooley/action-packed-react", - "beiatrix/apollo-tutorial", - "octopusthink/nautilus", - "gipsy/nyt-demo", - "echoghi/emilechoghi.com", - "exogen/react-dynamic-html", - "Minivera/cartomapper", - "mmmurray/react-responsive-layout", - "SpotlightData/nanowire-extensions", - "sifbuilder/eodoes", - "hemanthkodandarama/react-js-prototype-app", - "Valyay/blog", - "imaginerio/georio", - "tuanpt-0634/feathers-react-stack", - "ita-social-projects/horondi_client_fe", - "roguh/does-it-live-client", - "azdanov/translations", - "vtex-apps/store-icons", - "tedconf/react-ted-bootstrap", - "chandojo/climbbeta", - "daumie/buddy-demo-reactjs", - "kbrock84/react-svg-polygon", - "vtex-apps/bazaarvoice", - "Blockle/blockle-ui", - "hamlim/components", - "timc1/time-capsule", - "Lambda-School-Labs/rv-nav-fe", - "kaykayehnn/megeto", - "mario-iamb/list_me", - "baco16g/boilerplate_reactweb", - "doitadrian/commodo-fields-date", - "pmjonesg/algos", - "erictooth/react-menu-window", - "zepdev/design-system-website", - "cds-snc/loon-ui", - "kobi1021/kobi1021.github.io", - "jbelgard/rvnav-fe", - "MentorAPM/mentor-ui", - "perseids-project/woodhouse-js", - "lifeiscontent/react-svg-injector", - "GrayStrider/react-boilerplate-gs", - "DenisRebenok/react-github-profile", - "yama-group/Ajirni", - "fixate/react-show-styles", - "digital-detox/react-svg-use-external", - "doitadrian/react-hotkeyz", - "cdock1029/formr", - "Phlammariont/ferreira-front", - "qntln/react-redux", - "pfeilbr/jest-testing-framework-playground", - "doitadrian/commodo-fields-float", - "lirongfei123/react-store-driver", - "zimmerman-zimmerman/Zoom", - "debtcollective/header", - "fixate/react-xstream-store", - "joefazz/CodeXE", - "rubenamorim/second-personal-page", - "oleksiizapara/OpenLI", - "microcood/god-state", - "kristiyan-ASW-G-08/mern-reddit-clone", - "olaoluadesanya/route-optimizer", - "carlnjoku/extendedteam", - "Alghost/jira-dashboard", - "neo4j-apps/relatable", - "spidergon/visibility", - "quattro004/SolidReactDotnet", - "andrewcameronsims/zendesk-ticket-viewer", - "claydiffrient/simplydiffrient.com", - "linhdamwumbo/search-film", - "akhinchey/chess-puzzle-react", - "Kaus1247/my-react-starter", - "lg-uplus-compsight/compsight-front", - "kbrock84/react-radial-render", - "gs195/Weekly-Organiser", - "thejohnfreeman/loadable", - "jgprangshu/react-boilerplate-saga", - "mablin7/preact-gestures", - "startmt/react-redux-starter", - "chrislopresto/chrislopresto-gatsby", - "aileen-r/react-boilerplate", - "untitled-labs/metabase-custom", - "TwoStoryRobot/react-search-fuse", - "saadmas/space-explorer", - "jgprangshu/react-material-ui", - "man-of-sbk/reactjs-admin-panel", - "Blockle/blockle-form", - "origen-chat/blocks", - "Reopard226/track-book-styled-components", - "the-mmm-agency/muskoka-district-rentals", - "Muosvr/graphql-fullstack-tutorial", - "chengsig/react-boilerplate-string", - "justindujardin/rns-redux", - "utkuakguner/my-boilerplate", - "knight212/Netfly-CMS", - "lossless1/react-test", - "taiyeoguns/news-app-react", - "Happytreat/ApolloFullStackTutorial", - "AnnaMinasian/rustapedia", - "Bruce2107/anibook-ui", - "aniketbharambe/e-shopping", - "andreistroescu/music-webapp", - "Radi-Mortada/TasksApp", - "Opetushallitus/ehoks-ui", - "quanlatoi/Learn_boilerplate", - "austin-pham-goldenowl/test", - "zcam007/IMDB-Movie-Search", - "dajk/react-carousel", - "sinouiincubator/editable-data-table", - "vtex-apps/slider-layout", - "Teslima02/root", - "WangShuXian6/threejs-ts-es6-webpack-starter", - "ria3100/ts-react-parcel-boilerplate", - "masoodalamhunzai/Multiple-Emails-Invitation-React", - "42BV/react-spring-enums", - "VikrantShirvankar/Employee-App", - "CuongNguyen97/khoa-luan-tot-nghiep", - "linh626688/react-boilerplate-template", - "avirajsidhu/codetheft_client", - "absreim/apollo-fs-tutorial", - "dansbands/gifsearch", - "newmohib/react-boilerplate-api", - "uditparmar/oreo", - "newmohib/react-boilerplate-test", - "uditparmar/testapp", - "Ali-Doustkani/richtext", - "mmmurray/eslint-config-mmm", - "newmohib/react-boilerplate-demo-4", - "MrKolt01/react-oauth-flow-reborn", - "hoangmn3s/react-training", - "winner95725/react1", - "hoangenouvo/articulate", - "helixbass/ad-hok-js", - "prisoner-skills-app/frontend", - "jrs-innovation-center/design-system", - "cismet/wupp-topic-maps", - "arpit-krazybee/react-boilerplate", - "hageveld/ui", - "what-zen/what-zen-app", - "smileyKJ/Onsurity-Demo", - "jonathanfilbert/newSite2019", - "Stevenlee731/cache-app", - "gagaduday/boilerplate", - "quoclc-nexle/star-club-experiment", - "ning-cai/learning-react-redux", - "tejaretailio/ReactBoilerplate", - "zth/reason-relay", - "ProtonMail/react-components" -] diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index 9592ad6..ef29c50 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -1,5 +1,4 @@ -import { rules, generateRecommendedConfig } from "../index"; -import "jest-extended"; +import { generateRecommendedConfig, rules } from "../"; it("should have all the rules", () => { expect(Object.keys(rules).length).toBeGreaterThan(0); @@ -8,9 +7,9 @@ it("should have all the rules", () => { it.each(Object.keys(rules))("%s should export required fields", (ruleName) => { const rule = rules[ruleName]; expect(rule).toHaveProperty("create", expect.any(Function)); - expect(rule.meta.docs.url).not.toBeEmpty(); + expect(rule.meta.docs.url).not.toBe(""); expect(rule.meta.docs.category).toBe("Best Practices"); - expect(rule.meta.docs.description).not.toBeEmpty(); + expect(rule.meta.docs.description).not.toBe(""); }); it("should have a recommended config with recommended rules", () => { diff --git a/src/__tests__/lib/rules/prefer-empty.js b/src/__tests__/lib/rules/prefer-empty.js index 9480fd2..5a4b529 100644 --- a/src/__tests__/lib/rules/prefer-empty.js +++ b/src/__tests__/lib/rules/prefer-empty.js @@ -18,6 +18,7 @@ import * as rule from "../../../rules/prefer-empty"; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); ruleTester.run("prefer-empty", rule, { valid: [ + `expect().toBe(true)`, `expect(element.innerHTML).toBe('foo')`, `expect(element.innerHTML).toBe(foo)`, `expect(element.innerHTML).toBe(foo + bar)`, diff --git a/src/__tests__/lib/rules/prefer-focus.js b/src/__tests__/lib/rules/prefer-focus.js index 8e35dc3..aa4be8c 100644 --- a/src/__tests__/lib/rules/prefer-focus.js +++ b/src/__tests__/lib/rules/prefer-focus.js @@ -9,6 +9,7 @@ import * as rule from "../../../rules/prefer-focus"; const ruleTester = new RuleTester(); ruleTester.run("prefer-focus", rule, { valid: [ + `expect().toBe(true)`, `expect(input).not.toHaveFocus();`, `expect(input).toHaveFocus();`, `expect(document.activeElement).toBeNull()`, diff --git a/src/__tests__/lib/rules/prefer-in-document.js b/src/__tests__/lib/rules/prefer-in-document.js index 05ff2f5..adc3a8b 100644 --- a/src/__tests__/lib/rules/prefer-in-document.js +++ b/src/__tests__/lib/rules/prefer-in-document.js @@ -27,6 +27,7 @@ function invalidCase(code, output) { } const valid = [ + "expect().toBe(true)", ...["getByText", "getByRole"].map((q) => [ `expect(screen.${q}('foo')).toBeInTheDocument()`, `expect(${q}('foo')).toBeInTheDocument()`, @@ -50,6 +51,10 @@ const valid = [ expect(foo).not.toHaveLength(0)`, `let foo; expect(foo).toHaveLength(1);`, + `let foo; + expect(foo).toHaveLength()`, + `let foo; + expect(foo).toHaveLength(1, 2, 3)`, `expect(screen.notAQuery('foo-bar')).toHaveLength(1)`, `expect(screen.getAllByText('foo-bar')).toHaveLength(2)`, `import foo from "./foo"; @@ -87,8 +92,93 @@ const valid = [ `expect(screen.getAllByText("foo")).toHaveLength(getLength())`, `expect(screen.getAllByText("foo")).toBe(foo)`, `expect(screen.getAllByText("foo")).toEqual(foo)`, + ` + const element = getByText('value') + expect(element).toBeTruthy`, + ` + const element = getByText('value') + expect(element).toBe.truthy`, + ` + const element = getByText('value') + expect(element).toBeInTheDocument`, ]; const invalid = [ + invalidCase( + `expect(screen.getByText('foo')).toHaveLength()`, + `expect(screen.getByText('foo')).not.toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getAllByText('foo')).toHaveLength()`, + `expect(screen.getByText('foo')).not.toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getByRole('foo')).toHaveLength()`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength()`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getByRole('foo')).toHaveLength(0,2,3)`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(0,2,3,)`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getByRole('foo')).toHaveLength(1,2,3)`, + `expect(screen.getByRole('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(1,2,3,)`, + `expect(screen.getByRole('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(0//comment +)`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument(//comment +)` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(1,//comment +)`, + `expect(screen.getByRole('foo')).toBeInTheDocument(//comment +)` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(0,2,3//comment +)`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument(//comment +)` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(1,2,3,//comment +)`, + `expect(screen.getByRole('foo')).toBeInTheDocument(//comment +)` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(0,2,//comment +3,4)`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument(//comment +)` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(1,2,//comment +3,4,)`, + `expect(screen.getByRole('foo')).toBeInTheDocument(//comment +)` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(0,2/*comment*/,3)`, + `expect(screen.getByRole('foo')).not.toBeInTheDocument(/*comment*/)` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(1,2,/*comment*/3,)`, + `expect(screen.getByRole('foo')).toBeInTheDocument(/*comment*/)` + ), invalidCase( `expect(screen.getByText('foo')).toHaveLength(1)`, `expect(screen.getByText('foo')).toBeInTheDocument()` @@ -268,6 +358,22 @@ const invalid = [ `expect(queryByText('foo')) .not .toBeDefined()`, `expect(queryByText('foo')) .not .toBeInTheDocument()` ), + invalidCase( + `expect(queryByText('foo')).toBeFalsy()`, + `expect(queryByText('foo')).not.toBeInTheDocument()` + ), + invalidCase( + `expect(queryByText('foo')).not.toBeFalsy()`, + `expect(queryByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(queryByText('foo')).toBeTruthy()`, + `expect(queryByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(queryByText('foo')).not.toBeTruthy()`, + `expect(queryByText('foo')).not.toBeInTheDocument()` + ), invalidCase( `let foo; foo = screen.queryByText('foo'); diff --git a/src/__tests__/lib/rules/prefer-prefer-to-have-class.js b/src/__tests__/lib/rules/prefer-prefer-to-have-class.js index 3f1f58a..f06b496 100644 --- a/src/__tests__/lib/rules/prefer-prefer-to-have-class.js +++ b/src/__tests__/lib/rules/prefer-prefer-to-have-class.js @@ -5,6 +5,7 @@ const errors = [{ messageId: "use-to-have-class" }]; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); ruleTester.run("prefer-to-have-class", rule, { valid: [ + `expect().toBe(true)`, `const el = screen.getByText("foo"); expect(el).toHaveClass("bar")`, `const el = screen.getByText("foo"); expect(el.class).toEqual(foo)`, `const el = screen.getByText("foo"); expect(el).toHaveAttribute("class")`, diff --git a/src/__tests__/lib/rules/prefer-to-have-attribute.js b/src/__tests__/lib/rules/prefer-to-have-attribute.js index cf0cfe3..e55e03e 100644 --- a/src/__tests__/lib/rules/prefer-to-have-attribute.js +++ b/src/__tests__/lib/rules/prefer-to-have-attribute.js @@ -18,6 +18,7 @@ import * as rule from "../../../rules/prefer-to-have-attribute"; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); ruleTester.run("prefer-to-have-attribute", rule, { valid: [ + "expect().toBe(true)", "expect(element.foo).toBeTruthy()", "expect(element.getAttributeNode()).toBeNull()", `expect(element.getAttribute('foo')).toBeGreaterThan(2)`, @@ -44,8 +45,7 @@ ruleTester.run("prefer-to-have-attribute", rule, { output: `expect(element).toHaveAttribute('foo', expect.stringContaining('bar'));`, }, { - code: - "expect(element.getAttribute('foo')).toContain(`bar=${encodeURIComponent(baz.id)}`);", + code: "expect(element.getAttribute('foo')).toContain(`bar=${encodeURIComponent(baz.id)}`);", errors: [ { message: "Use toHaveAttribute instead of asserting on getAttribute", @@ -72,6 +72,24 @@ ruleTester.run("prefer-to-have-attribute", rule, { ], output: `expect(getByText("yes")).toHaveAttribute("data-blah", expect.stringMatching(/foo/))`, }, + { + code: `expect(getByText("yes").getAttribute("data-blah")).toBe("")`, + errors: [ + { + message: "Use toHaveAttribute instead of asserting on getAttribute", + }, + ], + output: `expect(getByText("yes")).toHaveAttribute("data-blah", "")`, + }, + { + code: `expect(getByText("yes").getAttribute("data-blah")).toBe('')`, + errors: [ + { + message: "Use toHaveAttribute instead of asserting on getAttribute", + }, + ], + output: `expect(getByText("yes")).toHaveAttribute("data-blah", '')`, + }, { code: `expect(getByText('foo').hasAttribute('foo')).toBe(null)`, errors: [ @@ -171,6 +189,24 @@ ruleTester.run("prefer-to-have-attribute", rule, { ], output: 'expect(element).toHaveAttribute("foo", "bar")', }, + { + code: `expect(getByText("yes").getAttribute("data-blah")).toEqual("")`, + errors: [ + { + message: "Use toHaveAttribute instead of asserting on getAttribute", + }, + ], + output: `expect(getByText("yes")).toHaveAttribute("data-blah", "")`, + }, + { + code: `expect(getByText("yes").getAttribute("data-blah")).toEqual('')`, + errors: [ + { + message: "Use toHaveAttribute instead of asserting on getAttribute", + }, + ], + output: `expect(getByText("yes")).toHaveAttribute("data-blah", '')`, + }, { code: 'expect(element.getAttribute("foo")).toStrictEqual("bar")', errors: [ @@ -180,6 +216,24 @@ ruleTester.run("prefer-to-have-attribute", rule, { ], output: 'expect(element).toHaveAttribute("foo", "bar")', }, + { + code: `expect(getByText("yes").getAttribute("data-blah")).toStrictEqual("")`, + errors: [ + { + message: "Use toHaveAttribute instead of asserting on getAttribute", + }, + ], + output: `expect(getByText("yes")).toHaveAttribute("data-blah", "")`, + }, + { + code: `expect(getByText("yes").getAttribute("data-blah")).toStrictEqual('')`, + errors: [ + { + message: "Use toHaveAttribute instead of asserting on getAttribute", + }, + ], + output: `expect(getByText("yes")).toHaveAttribute("data-blah", '')`, + }, { code: 'expect(element.getAttribute("foo")).toBe(null)', errors: [ diff --git a/src/__tests__/lib/rules/prefer-to-have-style.js b/src/__tests__/lib/rules/prefer-to-have-style.js index 9c8c21a..ac2b947 100644 --- a/src/__tests__/lib/rules/prefer-to-have-style.js +++ b/src/__tests__/lib/rules/prefer-to-have-style.js @@ -7,9 +7,14 @@ const errors = [ const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); ruleTester.run("prefer-to-have-style", rule, { valid: [ + `expect().toBe(true)`, `expect(el).toHaveStyle({foo:"bar"})`, `expect(el.style).toMatchSnapshot()`, + `expect(el.style).toEqual(1)`, `expect(el.style).toEqual(foo)`, + `expect(el.style[1]).toEqual([])`, + `expect(el.style[1]).toEqual({})`, + `expect(element.style[0]).toBe(new RegExp('reg'));`, `expect(el).toHaveAttribute("style")`, `React.useLayoutEffect(() => { if (foo) { @@ -131,5 +136,36 @@ ruleTester.run("prefer-to-have-style", rule, { errors, output: `expect(imageElement).not.toHaveStyle(\`box-shadow: inset 0px 0px 0px 400px 40px\`)`, }, + { + code: `expect(element.style[1]).toEqual('padding');`, + errors, + output: `expect(element).toHaveStyle({padding: expect.anything()});`, + }, + { + code: `expect(element.style[1]).toBe(\`padding\`);`, + errors, + output: `expect(element).toHaveStyle({[\`padding\`]: expect.anything()});`, + }, + { + code: `expect(element.style[1]).not.toEqual('padding');`, + errors, + }, + { + code: `expect(element.style[1]).not.toBe(\`padding\`);`, + errors, + }, + { + code: `expect(element.style[1]).toBe(x);`, + errors, + output: `expect(element).toHaveStyle({[x]: expect.anything()});`, + }, + { + code: `expect(element.style[0]).toBe(1);`, + errors, + }, + { + code: `expect(element.style[0]).toBe(/RegExp/);`, + errors, + }, ], }); diff --git a/src/__tests__/lib/rules/prefer-to-have-text-content.js b/src/__tests__/lib/rules/prefer-to-have-text-content.js index 9a8b146..1d47704 100644 --- a/src/__tests__/lib/rules/prefer-to-have-text-content.js +++ b/src/__tests__/lib/rules/prefer-to-have-text-content.js @@ -18,6 +18,7 @@ import * as rule from "../../../rules/prefer-to-have-text-content"; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); ruleTester.run("prefer-to-have-text-content", rule, { valid: [ + `expect().toBe(true)`, `expect(string).toBe("foo")`, `expect(element).toHaveTextContent("foo")`, `expect(container.lastNode).toBe("foo")`, diff --git a/src/__tests__/lib/rules/prefer-to-have-value.js b/src/__tests__/lib/rules/prefer-to-have-value.js index d965fbf..aed17c6 100644 --- a/src/__tests__/lib/rules/prefer-to-have-value.js +++ b/src/__tests__/lib/rules/prefer-to-have-value.js @@ -20,6 +20,7 @@ const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); const errors = [{ messageId: "use-to-have-value" }]; ruleTester.run("prefer-to-have-value", rule, { valid: [ + `expect().toBe(true)`, `expect(screen.getByRole("radio").value).toEqual("foo")`, `expect(screen.queryAllByRole("checkbox")[0].value).toStrictEqual("foo")`, `async function x() { expect((await screen.findByRole("button")).value).toBe("foo") }`, @@ -51,6 +52,10 @@ ruleTester.run("prefer-to-have-value", rule, { `const element = { value: 'foo' }; expect(element.value).not.toBe('foo');`, + ` + const res = makePath()(); + expect(res.value).toEqual('/repositories/create'); + `, ], invalid: [ { diff --git a/src/assignment-ast.js b/src/assignment-ast.js index dc8a9c8..4f5e987 100644 --- a/src/assignment-ast.js +++ b/src/assignment-ast.js @@ -74,7 +74,9 @@ export function getQueryNodeFrom(context, nodeWithValueProp) { }; } - const query = queryNode.callee.name || queryNode.callee.property.name; + const query = + queryNode.callee.name || + (queryNode.callee.property && queryNode.callee.property.name); const queryArg = queryNode.arguments[0] && queryNode.arguments[0].value; const isDTLQuery = queries.includes(query); diff --git a/src/rules/prefer-in-document.js b/src/rules/prefer-in-document.js index e6f4374..d9a5618 100644 --- a/src/rules/prefer-in-document.js +++ b/src/rules/prefer-in-document.js @@ -26,6 +26,7 @@ export const meta = { function isAntonymMatcher(matcherNode, matcherArguments) { return ( matcherNode.name === "toBeNull" || + matcherNode.name === "toBeFalsy" || usesToBeOrToEqualWithNull(matcherNode, matcherArguments) || usesToHaveLengthZero(matcherNode, matcherArguments) ); @@ -39,11 +40,17 @@ function usesToBeOrToEqualWithNull(matcherNode, matcherArguments) { } function usesToHaveLengthZero(matcherNode, matcherArguments) { - return matcherNode.name === "toHaveLength" && matcherArguments[0].value === 0; + // matcherArguments.length === 0: toHaveLength() will cause jest matcher error + // matcherArguments[0].value: toHaveLength(0, ...) means zero length + return ( + matcherNode.name === "toHaveLength" && + (matcherArguments.length === 0 || matcherArguments[0].value === 0) + ); } export const create = (context) => { - const alternativeMatchers = /^(toHaveLength|toBeDefined|toBeNull|toBe|toEqual)$/; + const alternativeMatchers = + /^(toHaveLength|toBeDefined|toBeNull|toBe|toEqual|toBeTruthy|toBeFalsy)$/; function getLengthValue(matcherArguments) { let lengthValue; @@ -69,6 +76,10 @@ export const create = (context) => { negatedMatcher, expect, }) { + if (matcherNode.parent.parent.type !== "CallExpression") { + return; + } + // only report on dom nodes which we can resolve to RTL queries. if (!queryNode || (!queryNode.name && !queryNode.property)) return; @@ -111,6 +122,12 @@ export const create = (context) => { // Remove any arguments in the matcher for (const argument of Array.from(matcherArguments)) { + const sourceCode = context.getSourceCode(); + const token = sourceCode.getTokenAfter(argument); + if (token.value === "," && token.type === "Punctuator") { + // Remove commas if toHaveLength had more than one argument or a trailing comma + operations.push(fixer.replaceText(token, "")); + } operations.push(fixer.remove(argument)); } @@ -215,6 +232,11 @@ export const create = (context) => { node ) { const arg = node.callee.object.arguments[0]; + + if (!arg) { + return; + } + const queryNode = arg.type === "AwaitExpression" ? arg.argument.callee : arg.callee; const matcherNode = node.callee.property; diff --git a/src/rules/prefer-to-have-attribute.js b/src/rules/prefer-to-have-attribute.js index 89dfcd9..5c0b16d 100644 --- a/src/rules/prefer-to-have-attribute.js +++ b/src/rules/prefer-to-have-attribute.js @@ -64,15 +64,14 @@ export const create = (context) => ({ node ) { const arg = node.parent.parent.parent.arguments; - const isNullOrEmpty = - arg.length > 0 && (arg[0].value === null || arg[0].value === ""); + const isNull = arg.length > 0 && arg[0].value === null; const sourceCode = context.getSourceCode(); context.report({ node: node.parent, message: `Use toHaveAttribute instead of asserting on getAttribute`, fix: (fixer) => { - const lastFixer = isNullOrEmpty + const lastFixer = isNull ? fixer.replaceText( node.parent.parent.parent.arguments[0], sourceCode.getText(node.arguments[0]) @@ -86,7 +85,7 @@ export const create = (context) => ({ fixer.removeRange([node.callee.object.range[1], node.range[1]]), fixer.replaceText( node.parent.parent.property, - `${isNullOrEmpty ? "not." : ""}toHaveAttribute` + `${isNull ? "not." : ""}toHaveAttribute` ), lastFixer, ]; diff --git a/src/rules/prefer-to-have-class.js b/src/rules/prefer-to-have-class.js index 0803455..ce950d5 100644 --- a/src/rules/prefer-to-have-class.js +++ b/src/rules/prefer-to-have-class.js @@ -116,14 +116,18 @@ export const create = (context) => ({ classValue.type === "CallExpression" && classValue.callee.type === "MemberExpression" && classValue.callee.object.name === "expect" - ) + ) { return; + } + context.report({ node: matcher, messageId, fix(fixer) { - if (checkedProp.name === "classList" && matcher.name !== "toContain") + if (checkedProp.name === "classList" && matcher.name !== "toContain") { return; + } + return [ fixer.removeRange([classNameProp.range[1], checkedProp.range[1]]), fixer.replaceText(matcher, "toHaveClass"), @@ -182,8 +186,9 @@ export const create = (context) => ({ node: matcher, messageId, fix(fixer) { - if (className.name === "classList" && matcher.name !== "toContain") + if (className.name === "classList" && matcher.name !== "toContain") { return; + } return [ fixer.removeRange([classNameProp.range[1], className.range[1]]), @@ -215,8 +220,9 @@ export const create = (context) => ({ if ( (matcher.name === "toHaveAttribute" && classNameValue !== "class") || (matcher.name === "toHaveProperty" && classNameValue !== "className") - ) + ) { return; + } const { isDTLQuery } = getQueryNodeFrom( context, @@ -255,8 +261,10 @@ export const create = (context) => ({ if ( (matcher.name === "toHaveAttribute" && classNameValue !== "class") || (matcher.name === "toHaveProperty" && classNameValue !== "className") - ) + ) { return; + } + const { isDTLQuery } = getQueryNodeFrom( context, node.callee.object.object.arguments[0] @@ -295,8 +303,9 @@ export const create = (context) => ({ if ( (matcher.name === "toHaveAttribute" && classNameValue !== "class") || (matcher.name === "toHaveProperty" && classNameValue !== "className") - ) + ) { return; + } const { isDTLQuery } = getQueryNodeFrom( context, diff --git a/src/rules/prefer-to-have-style.js b/src/rules/prefer-to-have-style.js index 4ee2e9e..1d0dead 100644 --- a/src/rules/prefer-to-have-style.js +++ b/src/rules/prefer-to-have-style.js @@ -18,6 +18,13 @@ export const meta = { }; export const create = (context) => { + function getReplacementObjectProperty(styleName) { + if (styleName.type === "Literal") { + return camelCase(styleName.value); + } + + return `[${context.getSourceCode().getText(styleName)}]`; + } function getReplacementStyleParam(styleName, styleValue) { return styleName.type === "Literal" ? `{${camelCase(styleName.value)}: ${context @@ -148,7 +155,7 @@ export const create = (context) => { }, //expect(el.style["foo-bar"]).toBe("baz") - [`MemberExpression[property.name=style][parent.computed=true][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.parent.parent.arguments.0.type=/(Template)?Literal/][parent.parent.callee.name=expect]`]( + [`MemberExpression[property.name=style][parent.computed=true][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.parent.parent.arguments.0.type=/((Template)?Literal|Identifier)/][parent.parent.callee.name=expect]`]( node ) { const styleName = node.parent.property; @@ -157,10 +164,14 @@ export const create = (context) => { const startOfStyleMemberExpression = node.object.range[1]; const endOfStyleMemberExpression = node.parent.parent.arguments[0].range[1]; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { + + let fix = null; + + if ( + typeof styleValue.value !== "number" && + !(styleValue.value instanceof RegExp) + ) { + fix = (fixer) => { return [ fixer.removeRange([ startOfStyleMemberExpression, @@ -169,10 +180,20 @@ export const create = (context) => { fixer.replaceText(matcher, "toHaveStyle"), fixer.replaceText( styleValue, - getReplacementStyleParam(styleName, styleValue) + typeof styleName.value === "number" + ? `{${getReplacementObjectProperty( + styleValue + )}: expect.anything()}` + : getReplacementStyleParam(styleName, styleValue) ), ]; - }, + }; + } + + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix, }); }, //expect(el.style["foo-bar"]).not.toBe("baz") @@ -185,10 +206,10 @@ export const create = (context) => { const endOfStyleMemberExpression = node.parent.parent.arguments[0].range[1]; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { + let fix = null; + + if (typeof styleName.value !== "number") { + fix = (fixer) => { return [ fixer.removeRange([ node.object.range[1], @@ -200,7 +221,13 @@ export const create = (context) => { getReplacementStyleParam(styleName, styleValue) ), ]; - }, + }; + } + + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix, }); }, //expect(foo.style).toHaveProperty("foo", "bar") @@ -225,9 +252,9 @@ export const create = (context) => { fixer.replaceText(matcher, "toHaveStyle"), fixer.replaceTextRange( [styleName.range[0], styleValue.range[1]], - `{${camelCase( - styleName.value - )}: ${context.getSourceCode().getText(styleValue)}}` + `{${camelCase(styleName.value)}: ${context + .getSourceCode() + .getText(styleValue)}}` ), ]; }, @@ -238,10 +265,8 @@ export const create = (context) => { [`MemberExpression[property.name=style][parent.parent.property.name=not][parent.parent.parent.property.name=toHaveProperty][parent.callee.name=expect]`]( node ) { - const [ - styleName, - styleValue, - ] = node.parent.parent.parent.parent.arguments; + const [styleName, styleValue] = + node.parent.parent.parent.parent.arguments; const matcher = node.parent.parent.parent.property; context.report({ @@ -259,9 +284,9 @@ export const create = (context) => { fixer.replaceText(matcher, "toHaveStyle"), fixer.replaceTextRange( [styleName.range[0], styleValue.range[1]], - `{${camelCase( - styleName.value - )}: ${context.getSourceCode().getText(styleValue)}}` + `{${camelCase(styleName.value)}: ${context + .getSourceCode() + .getText(styleValue)}}` ), ]; },