diff --git a/README.md b/README.md index 6e334723..9985eae9 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,7 @@ module.exports = [ | [no-render-in-lifecycle](docs/rules/no-render-in-lifecycle.md) | Disallow the use of `render` in testing frameworks setup functions | ![badge-angular](https://img.shields.io/badge/-Angular-black?style=flat-square&logo=angular&logoColor=white&labelColor=DD0031&color=black) ![badge-marko](https://img.shields.io/badge/-Marko-black?style=flat-square&logo=marko&logoColor=white&labelColor=2596BE&color=black) ![badge-react](https://img.shields.io/badge/-React-black?style=flat-square&logo=react&logoColor=white&labelColor=61DAFB&color=black) ![badge-svelte](https://img.shields.io/badge/-Svelte-black?style=flat-square&logo=svelte&logoColor=white&labelColor=FF3E00&color=black) ![badge-vue](https://img.shields.io/badge/-Vue-black?style=flat-square&logo=vue.js&logoColor=white&labelColor=4FC08D&color=black) | | | | [no-test-id-queries](docs/rules/no-test-id-queries.md) | Ensure no `data-testid` queries are used | | | | | [no-unnecessary-act](docs/rules/no-unnecessary-act.md) | Disallow wrapping Testing Library utils or empty callbacks in `act` | ![badge-marko](https://img.shields.io/badge/-Marko-black?style=flat-square&logo=marko&logoColor=white&labelColor=2596BE&color=black) ![badge-react](https://img.shields.io/badge/-React-black?style=flat-square&logo=react&logoColor=white&labelColor=61DAFB&color=black) | | | +| [no-unsettled-absence-query](docs/rules/no-unsettled-absence-query.md) | Disallow absence assertions on `queryBy*` before the component has settled | | | | | [no-wait-for-multiple-assertions](docs/rules/no-wait-for-multiple-assertions.md) | Disallow the use of multiple `expect` calls inside `waitFor` | ![badge-angular](https://img.shields.io/badge/-Angular-black?style=flat-square&logo=angular&logoColor=white&labelColor=DD0031&color=black) ![badge-dom](https://img.shields.io/badge/%F0%9F%90%99-DOM-black?style=flat-square) ![badge-marko](https://img.shields.io/badge/-Marko-black?style=flat-square&logo=marko&logoColor=white&labelColor=2596BE&color=black) ![badge-react](https://img.shields.io/badge/-React-black?style=flat-square&logo=react&logoColor=white&labelColor=61DAFB&color=black) ![badge-svelte](https://img.shields.io/badge/-Svelte-black?style=flat-square&logo=svelte&logoColor=white&labelColor=FF3E00&color=black) ![badge-vue](https://img.shields.io/badge/-Vue-black?style=flat-square&logo=vue.js&logoColor=white&labelColor=4FC08D&color=black) | | 🔧 | | [no-wait-for-side-effects](docs/rules/no-wait-for-side-effects.md) | Disallow the use of side effects in `waitFor` | ![badge-angular](https://img.shields.io/badge/-Angular-black?style=flat-square&logo=angular&logoColor=white&labelColor=DD0031&color=black) ![badge-dom](https://img.shields.io/badge/%F0%9F%90%99-DOM-black?style=flat-square) ![badge-marko](https://img.shields.io/badge/-Marko-black?style=flat-square&logo=marko&logoColor=white&labelColor=2596BE&color=black) ![badge-react](https://img.shields.io/badge/-React-black?style=flat-square&logo=react&logoColor=white&labelColor=61DAFB&color=black) ![badge-svelte](https://img.shields.io/badge/-Svelte-black?style=flat-square&logo=svelte&logoColor=white&labelColor=FF3E00&color=black) ![badge-vue](https://img.shields.io/badge/-Vue-black?style=flat-square&logo=vue.js&logoColor=white&labelColor=4FC08D&color=black) | | 🔧 | | [no-wait-for-snapshot](docs/rules/no-wait-for-snapshot.md) | Ensures no snapshot is generated inside of a `waitFor` call | ![badge-angular](https://img.shields.io/badge/-Angular-black?style=flat-square&logo=angular&logoColor=white&labelColor=DD0031&color=black) ![badge-dom](https://img.shields.io/badge/%F0%9F%90%99-DOM-black?style=flat-square) ![badge-marko](https://img.shields.io/badge/-Marko-black?style=flat-square&logo=marko&logoColor=white&labelColor=2596BE&color=black) ![badge-react](https://img.shields.io/badge/-React-black?style=flat-square&logo=react&logoColor=white&labelColor=61DAFB&color=black) ![badge-svelte](https://img.shields.io/badge/-Svelte-black?style=flat-square&logo=svelte&logoColor=white&labelColor=FF3E00&color=black) ![badge-vue](https://img.shields.io/badge/-Vue-black?style=flat-square&logo=vue.js&logoColor=white&labelColor=4FC08D&color=black) | | | diff --git a/docs/rules/no-unsettled-absence-query.md b/docs/rules/no-unsettled-absence-query.md new file mode 100644 index 00000000..a13e9afd --- /dev/null +++ b/docs/rules/no-unsettled-absence-query.md @@ -0,0 +1,86 @@ +# testing-library/no-unsettled-absence-query + +📝 Disallow absence assertions on `queryBy*` before the component has settled. + + + +Asserting absence with `queryBy*` + `.not.toBeInTheDocument()` (or `.not.toBeVisible()`) immediately after `render()`, before the component has settled, can produce a false positive. The element isn't there _yet_, not because it _won't_ be there. This is commonly referred to as **Testing Ghosts**. + +## Rule details + +This rule fires when an absence assertion on a `queryBy*` / `queryAllBy*` query appears before any **settling expression** in the test body. + +**What counts as "settled":** + +1. Any `await` expression on a preceding statement - covers `findBy*`, `waitFor`, `act`, or custom async helpers. +2. A `getBy*` / `getAllBy*` call on a preceding statement - proves the synchronous render produced expected output. + +**Additionally**, absence assertions inside a `waitFor` callback are always flagged, because `waitFor` retries until the assertion passes - an absence check passes on the first invocation before the component has settled. + +Examples of **incorrect** code for this rule: + +```js +// Absence assertion before component has settled +test('shows no error', () => { + render(); + expect(screen.queryByText('error')).not.toBeInTheDocument(); +}); + +// Absence assertion BEFORE the await - order matters +test('shows no error', async () => { + render(); + expect(screen.queryByText('error')).not.toBeInTheDocument(); + await screen.findByText('loaded'); +}); + +// queryAllBy variant +test('shows no alerts', () => { + render(); + expect(screen.queryAllByRole('alert')).not.toBeInTheDocument(); +}); + +// Absence assertion inside waitFor - passes on first retry, still a ghost +test('shows no error', async () => { + render(); + await waitFor(() => { + expect(screen.queryByText('error')).not.toBeInTheDocument(); + }); +}); +``` + +Examples of **correct** code for this rule: + +```js +// findBy* settles the component first +test('shows no error', async () => { + render(); + await screen.findByText('loaded'); + expect(screen.queryByText('error')).not.toBeInTheDocument(); +}); + +// waitFor settles the component first +test('shows no error', async () => { + render(); + await waitFor(() => expect(something).toBe(true)); + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); +}); + +// act settles the component first +test('shows no error', async () => { + await act(() => render()); + expect(screen.queryByText('error')).not.toBeInTheDocument(); +}); + +// getBy* proves sync render completed +test('shows no error', () => { + render(); + screen.getByText('visible heading'); + expect(screen.queryByText('error')).not.toBeInTheDocument(); +}); +``` + +## Further Reading + +- [Testing Library: Appearance and Disappearance guide](https://testing-library.com/docs/guide-disappearance/) +- [Kent C. Dodds: Common mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library) +- [Gerardo Perrucci: Stop Testing Ghosts](https://www.gperrucci.com/blog/react/assert-non-existence-react-testing-library) diff --git a/src/rules/index.ts b/src/rules/index.ts index 06a671c4..c17c4046 100644 --- a/src/rules/index.ts +++ b/src/rules/index.ts @@ -14,6 +14,7 @@ import noPromiseInFireEvent from './no-promise-in-fire-event'; import noRenderInLifecycle from './no-render-in-lifecycle'; import noTestIdQueries from './no-test-id-queries'; import noUnnecessaryAct from './no-unnecessary-act'; +import noUnsettledAbsenceQuery from './no-unsettled-absence-query'; import noWaitForMultipleAssertions from './no-wait-for-multiple-assertions'; import noWaitForSideEffects from './no-wait-for-side-effects'; import noWaitForSnapshot from './no-wait-for-snapshot'; @@ -45,6 +46,7 @@ export const rules = { 'no-render-in-lifecycle': noRenderInLifecycle, 'no-test-id-queries': noTestIdQueries, 'no-unnecessary-act': noUnnecessaryAct, + 'no-unsettled-absence-query': noUnsettledAbsenceQuery, 'no-wait-for-multiple-assertions': noWaitForMultipleAssertions, 'no-wait-for-side-effects': noWaitForSideEffects, 'no-wait-for-snapshot': noWaitForSnapshot, diff --git a/src/rules/no-unsettled-absence-query.ts b/src/rules/no-unsettled-absence-query.ts new file mode 100644 index 00000000..c658235a --- /dev/null +++ b/src/rules/no-unsettled-absence-query.ts @@ -0,0 +1,225 @@ +import { ASTUtils } from '@typescript-eslint/utils'; + +import { createTestingLibraryRule } from '../create-testing-library-rule'; +import { + findClosestCallNode, + getAssertNodeInfo, + getDeepestIdentifierNode, + isArrowFunctionExpression, + isBlockStatement, + isCallExpression, + isFunctionDeclaration, + isFunctionExpression, + isMemberExpression, +} from '../node-utils'; + +import type { TSESTree } from '@typescript-eslint/utils'; + +const RULE_NAME = 'no-unsettled-absence-query'; +export type MessageIds = 'noUnsettledAbsenceQuery'; +export type Options = []; + +const NEGATED_ABSENCE_MATCHERS = ['toBeVisible']; + +function isNestedFunction(node: TSESTree.Node): boolean { + return ( + isArrowFunctionExpression(node) || + isFunctionExpression(node) || + isFunctionDeclaration(node) + ); +} + +function containsNode( + node: TSESTree.Node, + predicate: (n: TSESTree.Node) => boolean +): boolean { + if (predicate(node)) { + return true; + } + + if (isNestedFunction(node)) { + return false; + } + + for (const key of Object.keys(node)) { + if (key === 'parent') continue; + const child = (node as unknown as Record)[key]; + if (child && typeof child === 'object') { + if (Array.isArray(child)) { + for (const item of child) { + if ( + item && + typeof item === 'object' && + 'type' in item && + containsNode(item as TSESTree.Node, predicate) + ) { + return true; + } + } + } else if ( + 'type' in child && + containsNode(child as TSESTree.Node, predicate) + ) { + return true; + } + } + } + return false; +} + +export default createTestingLibraryRule({ + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: + 'Disallow absence assertions on `queryBy*` before the component has settled', + recommendedConfig: { + dom: false, + angular: false, + react: false, + vue: false, + svelte: false, + marko: false, + }, + }, + messages: { + noUnsettledAbsenceQuery: + 'Absence assertion on `{{queryMethod}}` appears before the component has settled. ' + + 'The element may not have rendered yet, resulting in a false positive. ' + + 'Add an `await` expression (e.g. `findBy*`, `waitFor`, `act`) or a `getBy*` call before this assertion.', + }, + schema: [], + }, + defaultOptions: [], + + create(context, _, helpers) { + function isAbsenceAssertion(node: TSESTree.MemberExpression): boolean { + if (helpers.isAbsenceAssert(node)) { + return true; + } + + const { matcher, isNegated } = getAssertNodeInfo(node); + return ( + isNegated !== false && + matcher !== null && + NEGATED_ABSENCE_MATCHERS.includes(matcher) + ); + } + + function isInsideAsyncUtilCallback(node: TSESTree.Node): boolean { + let current: TSESTree.Node | undefined = node.parent; + + while (current) { + if ( + (isArrowFunctionExpression(current) || + isFunctionExpression(current)) && + isCallExpression(current.parent) + ) { + const calleeIdentifier = getDeepestIdentifierNode( + current.parent.callee + ); + if (calleeIdentifier && helpers.isAsyncUtil(calleeIdentifier)) { + return true; + } + } + current = current.parent; + } + return false; + } + + function findEnclosingFunctionBody( + node: TSESTree.Node + ): TSESTree.Statement[] | null { + let current: TSESTree.Node | undefined = node.parent; + + while (current) { + if ( + (isArrowFunctionExpression(current) || + isFunctionExpression(current)) && + isBlockStatement(current.body) + ) { + return current.body.body; + } + current = current.parent; + } + return null; + } + + function findAncestorStatement( + node: TSESTree.Node, + statements: TSESTree.Statement[] + ): TSESTree.Statement | null { + let current: TSESTree.Node = node; + while (current.parent) { + if (statements.includes(current as TSESTree.Statement)) { + return current as TSESTree.Statement; + } + current = current.parent; + } + return null; + } + + function hasSettlingExpression(statement: TSESTree.Statement): boolean { + const hasAwait = containsNode(statement, (n) => + ASTUtils.isAwaitExpression(n) + ); + const hasGetQuery = containsNode( + statement, + (n) => ASTUtils.isIdentifier(n) && helpers.isGetQueryVariant(n) + ); + return hasAwait || hasGetQuery; + } + + return { + 'CallExpression Identifier'(node: TSESTree.Identifier) { + if (!helpers.isQueryQueryVariant(node)) { + return; + } + + const expectCallNode = findClosestCallNode(node, 'expect'); + if ( + !expectCallNode?.parent || + !isMemberExpression(expectCallNode.parent) + ) { + return; + } + + if (!isAbsenceAssertion(expectCallNode.parent)) { + return; + } + + if (isInsideAsyncUtilCallback(node)) { + context.report({ + node, + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: node.name }, + }); + return; + } + + const functionBody = findEnclosingFunctionBody(node); + if (!functionBody) { + return; + } + + const containingStatement = findAncestorStatement(node, functionBody); + if (!containingStatement) { + return; + } + + const stmtIndex = functionBody.indexOf(containingStatement); + const precedingStatements = functionBody.slice(0, stmtIndex); + const hasSettled = precedingStatements.some(hasSettlingExpression); + + if (!hasSettled) { + context.report({ + node, + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: node.name }, + }); + } + }, + }; + }, +}); diff --git a/tests/rules/no-unsettled-absence-query.test.ts b/tests/rules/no-unsettled-absence-query.test.ts new file mode 100644 index 00000000..a4ba7a25 --- /dev/null +++ b/tests/rules/no-unsettled-absence-query.test.ts @@ -0,0 +1,482 @@ +import rule from '../../src/rules/no-unsettled-absence-query'; +import { createRuleTester } from '../test-utils'; + +import type { + MessageIds, + Options, +} from '../../src/rules/no-unsettled-absence-query'; +import type { + InvalidTestCase, + ValidTestCase, +} from '@typescript-eslint/rule-tester'; + +const ruleTester = createRuleTester(); + +type RuleValidTestCase = ValidTestCase; +type RuleInvalidTestCase = InvalidTestCase; + +const SUPPORTED_TESTING_FRAMEWORKS = [ + ['@testing-library/dom', 'DOM TL'], + ['@testing-library/react', 'React TL'], + ['@testing-library/vue', 'Vue TL'], + ['@testing-library/angular', 'Angular TL'], + ['@marko/testing-library', 'Marko TL'], +] as const; + +ruleTester.run('no-unsettled-absence-query', rule, { + valid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.map( + ([testingFramework, label]) => ({ + code: ` + import { render, screen } from '${testingFramework}' + + test('${label} - settled by findBy', async () => { + render() + await screen.findByText('loaded') + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + }) + ), + + { + code: ` + import { render, screen, waitFor } from '@testing-library/react' + + test('settled by waitFor', async () => { + render() + await waitFor(() => expect(something).toBe(true)) + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render, screen, act } from '@testing-library/react' + + test('settled by act wrapping render', async () => { + await act(() => render()) + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('settled by getByText', () => { + render() + screen.getByText('visible heading') + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('settled by getByRole inside expect', () => { + render() + expect(screen.getByRole('heading')).toBeVisible() + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('settled by getAllByText', () => { + render() + screen.getAllByText('item') + expect(screen.queryByRole('alert')).not.toBeVisible() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('.not.toBeVisible after findBy', async () => { + render() + await screen.findByText('loaded') + expect(screen.queryByRole('dialog')).not.toBeVisible() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('presence assertion is not flagged', () => { + render() + expect(screen.queryByText('exists')).toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('.not with unrelated matcher is not flagged', () => { + render() + expect(screen.queryByText('item')).not.toHaveClass('active') + }) + `, + }, + + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render, screen } from 'other-library' + + test('non-TL import is not reported', () => { + render() + expect(screen.queryByText('gone')).not.toBeInTheDocument() + }) + `, + }, + + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render, screen } from 'test-utils' + + test('custom module settled', async () => { + render() + await screen.findByText('loaded') + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render } from '@testing-library/react' + + test('destructured queryByText after settling', async () => { + const { queryByText, findByText } = render() + await findByText('loaded') + expect(queryByText('error')).not.toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('standalone queryBy without expect', () => { + render() + const el = screen.queryByText('error') + }) + `, + }, + + { + code: ` + import { screen } from '@testing-library/react' + + expect(screen.queryByText('error')).not.toBeInTheDocument() + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('settled inside function expression', async function() { + render() + await screen.findByText('loaded') + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('settled by await nested inside expect arguments', async () => { + render() + expect(await screen.findByText('loaded')).toBeInTheDocument() + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + }, + ], + + invalid: [ + ...SUPPORTED_TESTING_FRAMEWORKS.map( + ([testingFramework, label]) => ({ + code: ` + import { render, screen } from '${testingFramework}' + + test('${label} - absence before settling', () => { + render() + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 6, + column: 20, + }, + ], + }) + ), + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('.not.toBeVisible before settling', () => { + render() + expect(screen.queryByRole('dialog')).not.toBeVisible() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByRole' }, + line: 6, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('queryAllByText before settling', () => { + render() + expect(screen.queryAllByText('gone')).not.toBeInTheDocument() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryAllByText' }, + line: 6, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('absence before await - order matters', async () => { + render() + expect(screen.queryByText('error')).not.toBeInTheDocument() + await screen.findByText('loaded') + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 6, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('multiple absence assertions before settling', () => { + render() + expect(screen.queryByText('a')).not.toBeInTheDocument() + expect(screen.queryByText('b')).not.toBeInTheDocument() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 6, + column: 19, + }, + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 7, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen, waitFor } from '@testing-library/react' + + test('absence inside awaited waitFor', async () => { + render() + await waitFor(() => { + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 7, + column: 20, + }, + ], + }, + + { + code: ` + import { render, screen, waitFor } from '@testing-library/react' + + test('absence inside unawaited waitFor', () => { + render() + waitFor(() => { + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 7, + column: 20, + }, + ], + }, + + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render, screen } from 'test-utils' + + test('custom module unsettled', () => { + render() + expect(screen.queryByText('gone')).not.toBeInTheDocument() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 6, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('.toBeNull before settling', () => { + render() + expect(screen.queryByText('error')).toBeNull() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 6, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('.toBeFalsy before settling', () => { + render() + expect(screen.queryByText('error')).toBeFalsy() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 6, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen, waitFor } from '@testing-library/react' + + test('absence inside waitFor with function expression callback', async () => { + render() + await waitFor(function() { + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 7, + column: 20, + }, + ], + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('unsettled in function expression', function() { + render() + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 6, + column: 19, + }, + ], + }, + + { + code: ` + import { render, screen } from '@testing-library/react' + + test('await inside nested function does not settle outer scope', () => { + render() + const helper = async () => { await screen.findByText('loaded') } + expect(screen.queryByText('error')).not.toBeInTheDocument() + }) + `, + errors: [ + { + messageId: 'noUnsettledAbsenceQuery', + data: { queryMethod: 'queryByText' }, + line: 7, + column: 19, + }, + ], + }, + ], +});