testing-library / eslint-plugin-testing-library

ESLint plugin to follow best practices and anticipate common mistakes when writing tests with Testing Library
https://npm.im/eslint-plugin-testing-library
MIT License
968 stars 135 forks source link

`no-unnecessary-act` false positive #820

Closed mikew closed 9 months ago

mikew commented 9 months ago

Have you read the Troubleshooting section?

Yes

Plugin version

v5.11.1

ESLint version

v8.50.0

Node.js version

v18.16.1

package manager and version

npm v9.5.1

Operating system

Linux

Bug description

I'm upgrading dependencies and am getting a lot of churn from testing-library. It's now complaining that the act below is unnecessary, but without it we get a giant warning:

stderr | src/exampleRedux/ExampleComponent.test.tsx > ExampleComponent > calls setIncrementBy when the input is changed
Warning: An update to ForwardRef(FormControl) inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
    at FormControl ([REDACTED]/starter-kit-react/node_modules/@mui/material/node/FormControl/FormControl.js:92:44)
    at [REDACTED]/starter-kit-react/node_modules/@emotion/react/dist/emotion-element-48d2c2e4.cjs.dev.js:62:23
    at TextField ([REDACTED]/starter-kit-react/node_modules/@mui/material/TextField/TextField.js:95:46)
    at div
    at div
    at [REDACTED]/starter-kit-react/node_modules/@emotion/react/dist/emotion-element-48d2c2e4.cjs.dev.js:62:23
    at Box ([REDACTED]/starter-kit-react/node_modules/@mui/system/createBox.js:31:41)
    at div
    at [REDACTED]/starter-kit-react/node_modules/@emotion/react/dist/emotion-element-48d2c2e4.cjs.dev.js:62:23
    at Box ([REDACTED]/starter-kit-react/node_modules/@mui/system/createBox.js:31:41)
    at div
    at [REDACTED]/starter-kit-react/node_modules/@emotion/react/dist/emotion-element-48d2c2e4.cjs.dev.js:62:23
    at Paper ([REDACTED]/starter-kit-react/node_modules/@mui/material/node/Paper/Paper.js:69:44)
    at div
    at [REDACTED]/starter-kit-react/node_modules/@emotion/react/dist/emotion-element-48d2c2e4.cjs.dev.js:62:23
    at Box ([REDACTED]/starter-kit-react/node_modules/@mui/system/createBox.js:31:41)
    at ExampleComponent ([REDACTED]/starter-kit-react/src/exampleRedux/ExampleComponent.tsx:20:39)
    at Router ([REDACTED]/starter-kit-react/node_modules/react-router/cjs/react-router.js:283:30)
    at MemoryRouter ([REDACTED]/starter-kit-react/node_modules/react-router/cjs/react-router.js:385:35)
    at Provider ([REDACTED]/starter-kit-react/node_modules/react-redux/lib/components/Provider.js:19:3)

Steps to reproduce

await act(async () => {
  screen.getByTestId('ExampleComponent--incrementByInput').focus()
  await userEvent.type(
    screen.getByTestId('ExampleComponent--incrementByInput'),
    '3',
    // This seems like an issue with user-event. Focusing an input should
    // select all the text.
    {
      initialSelectionStart: 0,
      initialSelectionEnd: 1,
    },
  )
})

Error output/screenshots

No response

ESLint configuration

{
  "env": {
    "browser": true,
    "jest": true,
    "jest/globals": true,
    "node": true,
    "es6": true,
    "commonjs": true
  },
  "globals": {},
  "parser": "[REDACTED]/eslint-config/node_modules/@typescript-eslint/parser/dist/index.js",
  "parserOptions": {
    "skipGraphQLConfig": true,
    "schema": "./src/exampleGraphql/api-schema.json",
    "operations": "src/**/*.{graphql,js,jsx,ts,tsx}",
    "ecmaVersion": 2018,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    },
    "warnOnUnsupportedTypeScriptVersion": true,
    "requireConfigFile": false,
    "babelOptions": {
      "presets": [
        "[REDACTED]/starter-kit-react/node_modules/babel-preset-react-app/prod.js"
      ]
    }
  },
  "plugins": [
    "@typescript-eslint",
    "testing-library",
    "jest",
    "react-hooks",
    "jsx-a11y",
    "import",
    "react"
  ],
  "rules": {
    "react/react-in-jsx-scope": [
      "off"
    ],
    "react/jsx-uses-react": [
      "off"
    ],
    "no-console": [
      "warn",
      {
        "allow": [
          "warn",
          "error",
          "info",
          "ignoredYellowBox"
        ]
      }
    ],
    "react-hooks/exhaustive-deps": [
      "warn"
    ],
    "react/forbid-foreign-prop-types": [
      "warn",
      {
        "allowInPropTypes": true
      }
    ],
    "react/jsx-no-comment-textnodes": [
      "warn"
    ],
    "react/jsx-no-duplicate-props": [
      1
    ],
    "react/jsx-no-target-blank": [
      "warn"
    ],
    "react/jsx-no-undef": [
      1
    ],
    "react/jsx-pascal-case": [
      "warn",
      {
        "allowAllCaps": true,
        "ignore": []
      }
    ],
    "react/no-danger-with-children": [
      "warn"
    ],
    "react/no-direct-mutation-state": [
      1
    ],
    "react/no-is-mounted": [
      "warn"
    ],
    "react/no-typos": [
      "error"
    ],
    "react/require-render-return": [
      "error"
    ],
    "react/style-prop-object": [
      "warn"
    ],
    "jsx-a11y/alt-text": [
      "warn"
    ],
    "jsx-a11y/anchor-has-content": [
      "warn"
    ],
    "jsx-a11y/anchor-is-valid": [
      "warn",
      {
        "aspects": [
          "noHref",
          "invalidHref"
        ]
      }
    ],
    "jsx-a11y/aria-activedescendant-has-tabindex": [
      "warn"
    ],
    "jsx-a11y/aria-props": [
      "warn"
    ],
    "jsx-a11y/aria-proptypes": [
      "warn"
    ],
    "jsx-a11y/aria-role": [
      "warn",
      {
        "ignoreNonDOM": true
      }
    ],
    "jsx-a11y/aria-unsupported-elements": [
      "warn"
    ],
    "jsx-a11y/heading-has-content": [
      "warn"
    ],
    "jsx-a11y/iframe-has-title": [
      "warn"
    ],
    "jsx-a11y/img-redundant-alt": [
      "warn"
    ],
    "jsx-a11y/no-access-key": [
      "warn"
    ],
    "jsx-a11y/no-distracting-elements": [
      "warn"
    ],
    "jsx-a11y/no-redundant-roles": [
      "warn"
    ],
    "jsx-a11y/role-has-required-aria-props": [
      "warn"
    ],
    "jsx-a11y/role-supports-aria-props": [
      "warn"
    ],
    "jsx-a11y/scope": [
      "warn"
    ],
    "react-hooks/rules-of-hooks": [
      "error"
    ],
    "react/forbid-prop-types": [
      0
    ],
    "react/jsx-boolean-value": [
      0
    ],
    "react/jsx-no-bind": [
      0
    ],
    "react/jsx-uses-vars": [
      1
    ],
    "react/no-array-index-key": [
      1
    ],
    "react/no-danger": [
      1
    ],
    "react/no-did-mount-set-state": [
      1
    ],
    "react/no-did-update-set-state": [
      1
    ],
    "react/no-multi-comp": [
      0
    ],
    "react/no-set-state": [
      0
    ],
    "react/no-unknown-property": [
      1
    ],
    "react/prefer-es6-class": [
      1
    ],
    "react/self-closing-comp": [
      1
    ],
    "react/sort-comp": [
      0
    ],
    "jest/no-conditional-expect": [
      "error"
    ],
    "jest/no-identical-title": [
      "error"
    ],
    "jest/no-interpolation-in-snapshots": [
      "error"
    ],
    "jest/no-jasmine-globals": [
      "error"
    ],
    "jest/no-jest-import": [
      "error"
    ],
    "jest/no-mocks-import": [
      "error"
    ],
    "jest/valid-describe-callback": [
      "error"
    ],
    "jest/valid-expect": [
      "error"
    ],
    "jest/valid-expect-in-promise": [
      "error"
    ],
    "jest/valid-title": [
      "warn"
    ],
    "testing-library/await-async-query": [
      "error"
    ],
    "testing-library/await-async-utils": [
      "error"
    ],
    "testing-library/no-await-sync-query": [
      "error"
    ],
    "testing-library/no-container": [
      "error"
    ],
    "testing-library/no-debugging-utils": [
      "error"
    ],
    "testing-library/no-dom-import": [
      "error",
      "react"
    ],
    "testing-library/no-node-access": [
      "error"
    ],
    "testing-library/no-promise-in-fire-event": [
      "error"
    ],
    "testing-library/no-render-in-setup": [
      "error"
    ],
    "testing-library/no-unnecessary-act": [
      "error"
    ],
    "testing-library/no-wait-for-empty-callback": [
      "error"
    ],
    "testing-library/no-wait-for-multiple-assertions": [
      "error"
    ],
    "testing-library/no-wait-for-side-effects": [
      "error"
    ],
    "testing-library/no-wait-for-snapshot": [
      "error"
    ],
    "testing-library/prefer-find-by": [
      "error"
    ],
    "testing-library/prefer-presence-queries": [
      "error"
    ],
    "testing-library/prefer-query-by-disappearance": [
      "error"
    ],
    "testing-library/prefer-screen-queries": [
      "error"
    ],
    "testing-library/render-result-naming-convention": [
      "error"
    ],
    "curly": [
      0
    ],
    "lines-around-comment": [
      0
    ],
    "max-len": [
      0
    ],
    "no-confusing-arrow": [
      0
    ],
    "no-mixed-operators": [
      0,
      {
        "groups": [
          [
            "&",
            "|",
            "^",
            "~",
            "<<",
            ">>",
            ">>>"
          ],
          [
            "==",
            "!=",
            "===",
            "!==",
            ">",
            ">=",
            "<",
            "<="
          ],
          [
            "&&",
            "||"
          ],
          [
            "in",
            "instanceof"
          ]
        ],
        "allowSamePrecedence": false
      }
    ],
    "no-tabs": [
      0
    ],
    "no-unexpected-multiline": [
      0
    ],
    "quotes": [
      0
    ],
    "@typescript-eslint/lines-around-comment": [
      0
    ],
    "@typescript-eslint/quotes": [
      0
    ],
    "babel/quotes": [
      0
    ],
    "vue/html-self-closing": [
      0
    ],
    "vue/max-len": [
      0
    ],
    "array-bracket-newline": [
      "off"
    ],
    "array-bracket-spacing": [
      "off"
    ],
    "array-element-newline": [
      "off"
    ],
    "arrow-parens": [
      "off"
    ],
    "arrow-spacing": [
      "off"
    ],
    "block-spacing": [
      "off"
    ],
    "brace-style": [
      "off"
    ],
    "comma-dangle": [
      "off"
    ],
    "comma-spacing": [
      "off"
    ],
    "comma-style": [
      "off"
    ],
    "computed-property-spacing": [
      "off"
    ],
    "dot-location": [
      "off",
      "property"
    ],
    "eol-last": [
      "off"
    ],
    "func-call-spacing": [
      "off"
    ],
    "function-call-argument-newline": [
      "off"
    ],
    "function-paren-newline": [
      "off"
    ],
    "generator-star-spacing": [
      "off"
    ],
    "implicit-arrow-linebreak": [
      "off"
    ],
    "indent": [
      "off"
    ],
    "jsx-quotes": [
      "off"
    ],
    "key-spacing": [
      "off"
    ],
    "keyword-spacing": [
      "off"
    ],
    "linebreak-style": [
      "off"
    ],
    "max-statements-per-line": [
      "off"
    ],
    "multiline-ternary": [
      "off"
    ],
    "newline-per-chained-call": [
      "off"
    ],
    "new-parens": [
      "off"
    ],
    "no-extra-parens": [
      "off"
    ],
    "no-extra-semi": [
      "off"
    ],
    "no-floating-decimal": [
      "off"
    ],
    "no-mixed-spaces-and-tabs": [
      "off"
    ],
    "no-multi-spaces": [
      "off"
    ],
    "no-multiple-empty-lines": [
      "off"
    ],
    "no-trailing-spaces": [
      "off"
    ],
    "no-whitespace-before-property": [
      "off"
    ],
    "nonblock-statement-body-position": [
      "off"
    ],
    "object-curly-newline": [
      "off"
    ],
    "object-curly-spacing": [
      "off"
    ],
    "object-property-newline": [
      "off"
    ],
    "one-var-declaration-per-line": [
      "off"
    ],
    "operator-linebreak": [
      "off"
    ],
    "padded-blocks": [
      "off"
    ],
    "quote-props": [
      "off"
    ],
    "rest-spread-spacing": [
      "off",
      "never"
    ],
    "semi": [
      "off"
    ],
    "semi-spacing": [
      "off"
    ],
    "semi-style": [
      "off"
    ],
    "space-before-blocks": [
      "off"
    ],
    "space-before-function-paren": [
      "off"
    ],
    "space-in-parens": [
      "off"
    ],
    "space-infix-ops": [
      "off"
    ],
    "space-unary-ops": [
      "off"
    ],
    "switch-colon-spacing": [
      "off"
    ],
    "template-curly-spacing": [
      "off"
    ],
    "template-tag-spacing": [
      "off"
    ],
    "unicode-bom": [
      "off",
      "never"
    ],
    "wrap-iife": [
      "off"
    ],
    "wrap-regex": [
      "off"
    ],
    "yield-star-spacing": [
      "off"
    ],
    "@babel/object-curly-spacing": [
      "off"
    ],
    "@babel/semi": [
      "off"
    ],
    "@typescript-eslint/block-spacing": [
      "off"
    ],
    "@typescript-eslint/brace-style": [
      "off"
    ],
    "@typescript-eslint/comma-dangle": [
      "off"
    ],
    "@typescript-eslint/comma-spacing": [
      "off"
    ],
    "@typescript-eslint/func-call-spacing": [
      "off"
    ],
    "@typescript-eslint/indent": [
      "off"
    ],
    "@typescript-eslint/key-spacing": [
      "off"
    ],
    "@typescript-eslint/keyword-spacing": [
      "off"
    ],
    "@typescript-eslint/member-delimiter-style": [
      "off"
    ],
    "@typescript-eslint/no-extra-parens": [
      "off"
    ],
    "@typescript-eslint/no-extra-semi": [
      "off"
    ],
    "@typescript-eslint/object-curly-spacing": [
      "off"
    ],
    "@typescript-eslint/semi": [
      "off"
    ],
    "@typescript-eslint/space-before-blocks": [
      "off"
    ],
    "@typescript-eslint/space-before-function-paren": [
      "off"
    ],
    "@typescript-eslint/space-infix-ops": [
      "off"
    ],
    "@typescript-eslint/type-annotation-spacing": [
      "off"
    ],
    "babel/object-curly-spacing": [
      "off"
    ],
    "babel/semi": [
      "off"
    ],
    "flowtype/boolean-style": [
      "off"
    ],
    "flowtype/delimiter-dangle": [
      "off"
    ],
    "flowtype/generic-spacing": [
      "off"
    ],
    "flowtype/object-type-curly-spacing": [
      "off"
    ],
    "flowtype/object-type-delimiter": [
      "off"
    ],
    "flowtype/quotes": [
      "off"
    ],
    "flowtype/semi": [
      "off"
    ],
    "flowtype/space-after-type-colon": [
      "off"
    ],
    "flowtype/space-before-generic-bracket": [
      "off"
    ],
    "flowtype/space-before-type-colon": [
      "off"
    ],
    "flowtype/union-intersection-spacing": [
      "off"
    ],
    "react/jsx-child-element-spacing": [
      "off"
    ],
    "react/jsx-closing-bracket-location": [
      "off"
    ],
    "react/jsx-closing-tag-location": [
      "off"
    ],
    "react/jsx-curly-newline": [
      "off"
    ],
    "react/jsx-curly-spacing": [
      "off"
    ],
    "react/jsx-equals-spacing": [
      "off"
    ],
    "react/jsx-first-prop-new-line": [
      "off"
    ],
    "react/jsx-indent": [
      "off"
    ],
    "react/jsx-indent-props": [
      "off"
    ],
    "react/jsx-max-props-per-line": [
      "off"
    ],
    "react/jsx-newline": [
      "off"
    ],
    "react/jsx-one-expression-per-line": [
      "off"
    ],
    "react/jsx-props-no-multi-spaces": [
      "off"
    ],
    "react/jsx-tag-spacing": [
      "off"
    ],
    "react/jsx-wrap-multilines": [
      "off"
    ],
    "standard/array-bracket-even-spacing": [
      "off"
    ],
    "standard/computed-property-even-spacing": [
      "off"
    ],
    "standard/object-curly-even-spacing": [
      "off"
    ],
    "unicorn/empty-brace-spaces": [
      "off"
    ],
    "unicorn/no-nested-ternary": [
      "off"
    ],
    "unicorn/number-literal-case": [
      "off"
    ],
    "vue/array-bracket-newline": [
      "off"
    ],
    "vue/array-bracket-spacing": [
      "off"
    ],
    "vue/array-element-newline": [
      "off"
    ],
    "vue/arrow-spacing": [
      "off"
    ],
    "vue/block-spacing": [
      "off"
    ],
    "vue/block-tag-newline": [
      "off"
    ],
    "vue/brace-style": [
      "off"
    ],
    "vue/comma-dangle": [
      "off"
    ],
    "vue/comma-spacing": [
      "off"
    ],
    "vue/comma-style": [
      "off"
    ],
    "vue/dot-location": [
      "off"
    ],
    "vue/func-call-spacing": [
      "off"
    ],
    "vue/html-closing-bracket-newline": [
      "off"
    ],
    "vue/html-closing-bracket-spacing": [
      "off"
    ],
    "vue/html-end-tags": [
      "off"
    ],
    "vue/html-indent": [
      "off"
    ],
    "vue/html-quotes": [
      "off"
    ],
    "vue/key-spacing": [
      "off"
    ],
    "vue/keyword-spacing": [
      "off"
    ],
    "vue/max-attributes-per-line": [
      "off"
    ],
    "vue/multiline-html-element-content-newline": [
      "off"
    ],
    "vue/multiline-ternary": [
      "off"
    ],
    "vue/mustache-interpolation-spacing": [
      "off"
    ],
    "vue/no-extra-parens": [
      "off"
    ],
    "vue/no-multi-spaces": [
      "off"
    ],
    "vue/no-spaces-around-equal-signs-in-attribute": [
      "off"
    ],
    "vue/object-curly-newline": [
      "off"
    ],
    "vue/object-curly-spacing": [
      "off"
    ],
    "vue/object-property-newline": [
      "off"
    ],
    "vue/operator-linebreak": [
      "off"
    ],
    "vue/quote-props": [
      "off"
    ],
    "vue/script-indent": [
      "off"
    ],
    "vue/singleline-html-element-content-newline": [
      "off"
    ],
    "vue/space-in-parens": [
      "off"
    ],
    "vue/space-infix-ops": [
      "off"
    ],
    "vue/space-unary-ops": [
      "off"
    ],
    "vue/template-curly-spacing": [
      "off"
    ],
    "generator-star": [
      "off"
    ],
    "indent-legacy": [
      "off"
    ],
    "no-arrow-condition": [
      "off"
    ],
    "no-comma-dangle": [
      "off"
    ],
    "no-reserved-keys": [
      "off"
    ],
    "no-space-before-semi": [
      "off"
    ],
    "no-spaced-func": [
      "off"
    ],
    "no-wrap-func": [
      "off"
    ],
    "space-after-function-name": [
      "off"
    ],
    "space-after-keywords": [
      "off"
    ],
    "space-before-function-parentheses": [
      "off"
    ],
    "space-before-keywords": [
      "off"
    ],
    "space-in-brackets": [
      "off"
    ],
    "space-return-throw-case": [
      "off"
    ],
    "space-unary-word-ops": [
      "off"
    ],
    "react/jsx-space-before-closing": [
      "off"
    ],
    "default-case": [
      "off",
      {
        "commentPattern": "^no default$"
      }
    ],
    "no-dupe-class-members": [
      "off"
    ],
    "no-undef": [
      "off"
    ],
    "@typescript-eslint/consistent-type-assertions": [
      "warn"
    ],
    "no-array-constructor": [
      "off"
    ],
    "@typescript-eslint/no-array-constructor": [
      "warn"
    ],
    "no-redeclare": [
      "off"
    ],
    "@typescript-eslint/no-redeclare": [
      "warn"
    ],
    "no-use-before-define": [
      "off"
    ],
    "@typescript-eslint/no-use-before-define": [
      "warn",
      {
        "functions": false,
        "classes": false,
        "variables": false,
        "typedefs": false
      }
    ],
    "no-unused-expressions": [
      "off",
      {
        "allowShortCircuit": true,
        "allowTernary": true,
        "allowTaggedTemplates": true,
        "enforceForJSX": false
      }
    ],
    "@typescript-eslint/no-unused-expressions": [
      "error",
      {
        "allowShortCircuit": true,
        "allowTernary": true,
        "allowTaggedTemplates": true,
        "enforceForJSX": false
      }
    ],
    "no-unused-vars": [
      "off",
      {
        "args": "none",
        "ignoreRestSiblings": true
      }
    ],
    "@typescript-eslint/no-unused-vars": [
      "warn",
      {
        "args": "none",
        "ignoreRestSiblings": true
      }
    ],
    "no-useless-constructor": [
      "off"
    ],
    "@typescript-eslint/no-useless-constructor": [
      "warn"
    ],
    "array-callback-return": [
      "warn"
    ],
    "eqeqeq": [
      "warn",
      "smart"
    ],
    "no-caller": [
      "warn"
    ],
    "no-cond-assign": [
      "warn",
      "except-parens"
    ],
    "no-const-assign": [
      "warn"
    ],
    "no-control-regex": [
      "warn"
    ],
    "no-delete-var": [
      "warn"
    ],
    "no-dupe-args": [
      "warn"
    ],
    "no-dupe-keys": [
      "warn"
    ],
    "no-duplicate-case": [
      "warn"
    ],
    "no-empty-character-class": [
      "warn"
    ],
    "no-empty-pattern": [
      "warn"
    ],
    "no-eval": [
      "warn"
    ],
    "no-ex-assign": [
      "warn"
    ],
    "no-extend-native": [
      "warn"
    ],
    "no-extra-bind": [
      "warn"
    ],
    "no-extra-label": [
      "warn"
    ],
    "no-fallthrough": [
      "warn"
    ],
    "no-func-assign": [
      "warn"
    ],
    "no-implied-eval": [
      "warn"
    ],
    "no-invalid-regexp": [
      "warn"
    ],
    "no-iterator": [
      "warn"
    ],
    "no-label-var": [
      "warn"
    ],
    "no-labels": [
      "warn",
      {
        "allowLoop": true,
        "allowSwitch": false
      }
    ],
    "no-lone-blocks": [
      "warn"
    ],
    "no-loop-func": [
      "warn"
    ],
    "no-multi-str": [
      "warn"
    ],
    "no-global-assign": [
      "warn"
    ],
    "no-unsafe-negation": [
      "warn"
    ],
    "no-new-func": [
      "warn"
    ],
    "no-new-object": [
      "warn"
    ],
    "no-new-symbol": [
      "warn"
    ],
    "no-new-wrappers": [
      "warn"
    ],
    "no-obj-calls": [
      "warn"
    ],
    "no-octal": [
      "warn"
    ],
    "no-octal-escape": [
      "warn"
    ],
    "no-regex-spaces": [
      "warn"
    ],
    "no-restricted-syntax": [
      "warn",
      "WithStatement"
    ],
    "no-script-url": [
      "warn"
    ],
    "no-self-assign": [
      "warn"
    ],
    "no-self-compare": [
      "warn"
    ],
    "no-sequences": [
      "warn"
    ],
    "no-shadow-restricted-names": [
      "warn"
    ],
    "no-sparse-arrays": [
      "warn"
    ],
    "no-template-curly-in-string": [
      "warn"
    ],
    "no-this-before-super": [
      "warn"
    ],
    "no-throw-literal": [
      "warn"
    ],
    "no-restricted-globals": [
      "error",
      "addEventListener",
      "blur",
      "close",
      "closed",
      "confirm",
      "defaultStatus",
      "defaultstatus",
      "event",
      "external",
      "find",
      "focus",
      "frameElement",
      "frames",
      "history",
      "innerHeight",
      "innerWidth",
      "length",
      "location",
      "locationbar",
      "menubar",
      "moveBy",
      "moveTo",
      "name",
      "onblur",
      "onerror",
      "onfocus",
      "onload",
      "onresize",
      "onunload",
      "open",
      "opener",
      "opera",
      "outerHeight",
      "outerWidth",
      "pageXOffset",
      "pageYOffset",
      "parent",
      "print",
      "removeEventListener",
      "resizeBy",
      "resizeTo",
      "screen",
      "screenLeft",
      "screenTop",
      "screenX",
      "screenY",
      "scroll",
      "scrollbars",
      "scrollBy",
      "scrollTo",
      "scrollX",
      "scrollY",
      "self",
      "status",
      "statusbar",
      "stop",
      "toolbar",
      "top"
    ],
    "no-unreachable": [
      "warn"
    ],
    "no-unused-labels": [
      "warn"
    ],
    "no-useless-computed-key": [
      "warn"
    ],
    "no-useless-concat": [
      "warn"
    ],
    "no-useless-escape": [
      "warn"
    ],
    "no-useless-rename": [
      "warn",
      {
        "ignoreDestructuring": false,
        "ignoreImport": false,
        "ignoreExport": false
      }
    ],
    "no-with": [
      "warn"
    ],
    "require-yield": [
      "warn"
    ],
    "strict": [
      "warn",
      "never"
    ],
    "use-isnan": [
      "warn"
    ],
    "valid-typeof": [
      "warn"
    ],
    "no-restricted-properties": [
      "error",
      {
        "object": "require",
        "property": "ensure",
        "message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
      },
      {
        "object": "System",
        "property": "import",
        "message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting"
      }
    ],
    "getter-return": [
      "warn"
    ],
    "import/first": [
      "error"
    ],
    "import/no-amd": [
      "error"
    ],
    "import/no-anonymous-default-export": [
      "warn"
    ],
    "import/no-webpack-loader-syntax": [
      "error"
    ],
    "no-var": [
      1
    ],
    "object-shorthand": [
      2,
      "always"
    ],
    "prefer-spread": [
      1
    ],
    "prefer-template": [
      1
    ],
    "no-duplicate-imports": [
      0
    ],
    "import/no-duplicates": [
      1
    ],
    "import/order": [
      1,
      {
        "alphabetize": {
          "order": "asc",
          "caseInsensitive": true,
          "orderImportKind": "ignore"
        },
        "newlines-between": "always",
        "groups": [
          "builtin",
          "external",
          "internal",
          "parent",
          [
            "sibling",
            "index"
          ]
        ],
        "distinctGroup": true,
        "warnOnUnassignedImports": false
      }
    ],
    "import/no-cycle": [
      2
    ],
    "constructor-super": [
      "error"
    ],
    "for-direction": [
      "error"
    ],
    "no-async-promise-executor": [
      "error"
    ],
    "no-case-declarations": [
      "error"
    ],
    "no-class-assign": [
      "error"
    ],
    "no-compare-neg-zero": [
      "error"
    ],
    "no-constant-condition": [
      "error"
    ],
    "no-debugger": [
      "error"
    ],
    "no-dupe-else-if": [
      "error"
    ],
    "no-empty": [
      "error"
    ],
    "no-extra-boolean-cast": [
      "error"
    ],
    "no-import-assign": [
      "error"
    ],
    "no-inner-declarations": [
      "error"
    ],
    "no-irregular-whitespace": [
      "error"
    ],
    "no-loss-of-precision": [
      "error"
    ],
    "no-misleading-character-class": [
      "error"
    ],
    "no-nonoctal-decimal-escape": [
      "error"
    ],
    "no-prototype-builtins": [
      "error"
    ],
    "no-setter-return": [
      "error"
    ],
    "no-unsafe-finally": [
      "error"
    ],
    "no-unsafe-optional-chaining": [
      "error"
    ],
    "no-useless-backreference": [
      "error"
    ],
    "no-useless-catch": [
      "error"
    ]
  },
  "settings": {
    "import/internal-regex": "^(@?src)/",
    "import/extensions": [
      ".js",
      ".jsx"
    ],
    "react": {
      "version": "detect"
    }
  },
  "ignorePatterns": []
}

Rule(s) affected

testing-library/no-unnecessary-act

Anything else?

The docs for focus / blur generate a warning for testing-library/prefer-screen-queries, also a TypeScript error because that function seems to want 2 arguments.

Screenshot 2023-09-28 at 12 47 34 PM

My reference to churn is due to the fact that this is happening in a starter kit for the company I work at. Every few months I go and upgrade the dependencies. The few tests that are in the repo are usually effected, even though there's no changes to the UI code. The last push I did apparently added act, due to warnings. Now testing-library is telling me to remove them again.

Do you want to submit a pull request to fix this bug?

Yes

mikew commented 9 months ago

I think I will just disable this rule, as it stands it feels pretty inconsistent, and the developer will most likely start with act, which doesn't break anything, and then be told to remove it, which might produce a warning. And I don't want to litter our code with eslint-disable-next-line comments.

Belco90 commented 9 months ago

Hi @mikew! I'm afraid the errors reported are not false positives but legit potential issues.

You can find a more detailed explanation in the links provided in the rule's doc. Basically, the problem is that Testing Library utils are already wrapped in act so needing to wrap their usage in act again is a sign of something is incorrect in your test. If you remove the act, you get warnings not because you actually need it, but because there is an async action your test is not aware of. You must await for something changing in your UI (e.g., a new element appears, the value of an existing element changes, an existing element disappears) after you perform the actions.

Based on the provided code snippet, it should be something like:

screen.getByTestId('ExampleComponent--incrementByInput').focus()

// Assuming you are using user-event v14, awaiting the userEvent.type is fine. Otherwise, it's not.
await userEvent.type(
  screen.getByTestId('ExampleComponent--incrementByInput'),
  '3',
  // This seems like an issue with user-event. Focusing an input should
  // select all the text.
  {
    initialSelectionStart: 0,
    initialSelectionEnd: 1,
  },
)

// I'm assuming the input has a new value now, so I'll wait for that
await waitFor(() => expect(screen.getByTestId('ExampleComponent--incrementByInput')).toHaveValue('3'))

This is definitely not a bug. However, if you are not in a position where you can fix the reported issues, feel free to just disable the rule. I encourage you to find what async behavior is changing the UI after those actions and wait for them instead, tho.

mikew commented 9 months ago

I appreciate the thorough response! This is a very basic test that assets something is changed through very synchronous redux, so probably little more than any context + updating interaction. I've gone ahead and disabled the rule, and this just helped me solidify my views that my team should keep using playwright / cyprus.