diff --git a/docs/rules/prefer-presence-queries.md b/docs/rules/prefer-presence-queries.md index 6b773089..91ab2b66 100644 --- a/docs/rules/prefer-presence-queries.md +++ b/docs/rules/prefer-presence-queries.md @@ -13,7 +13,7 @@ The (DOM) Testing Library allows to query DOM elements using different types of This rule fires whenever: -- `queryBy*` or `queryAllBy*` are used to assert element **is** present with `.toBeInTheDocument()`, `toBeTruthy()` or `.toBeDefined()` matchers or negated matchers from case below. +- `queryBy*` or `queryAllBy*` are used to assert element **is** present with `.toBeInTheDocument()`, `toBeTruthy()` or `.toBeDefined()` matchers or negated matchers from case below, or when used inside a `within()` clause. - `getBy*` or `getAllBy*` are used to assert element **is not** present with `.toBeNull()` or `.toBeFalsy()` matchers or negated matchers from case above. Examples of **incorrect** code for this rule: @@ -28,6 +28,7 @@ test('some test', () => { expect(screen.queryByText('button')).not.toBeNull(); expect(screen.queryAllByText('button')[2]).not.toBeNull(); expect(screen.queryByText('button')).not.toBeFalsy(); + ...(within(screen.queryByText('button')))... // check element is NOT present with `getBy*` expect(screen.getByText('loading')).not.toBeInTheDocument(); @@ -50,6 +51,7 @@ test('some test', async () => { expect(screen.getByText('button')).not.toBeNull(); expect(screen.getAllByText('button')[7]).not.toBeNull(); expect(screen.getByText('button')).not.toBeFalsy(); + ...(within(screen.getByText('button')))... // check element is NOT present with `queryBy*` expect(screen.queryByText('loading')).not.toBeInTheDocument(); diff --git a/lib/rules/prefer-presence-queries.ts b/lib/rules/prefer-presence-queries.ts index 9ec96c38..dd816408 100644 --- a/lib/rules/prefer-presence-queries.ts +++ b/lib/rules/prefer-presence-queries.ts @@ -59,6 +59,7 @@ export default createTestingLibraryRule({ return { 'CallExpression Identifier'(node: TSESTree.Identifier) { const expectCallNode = findClosestCallNode(node, 'expect'); + const withinCallNode = findClosestCallNode(node, 'within'); if (!expectCallNode || !isMemberExpression(expectCallNode.parent)) { return; @@ -79,9 +80,18 @@ export default createTestingLibraryRule({ return; } - if (presence && isPresenceAssert && !isPresenceQuery) { + if ( + presence && + (withinCallNode || isPresenceAssert) && + !isPresenceQuery + ) { context.report({ node, messageId: 'wrongPresenceQuery' }); - } else if (absence && isAbsenceAssert && isPresenceQuery) { + } else if ( + !withinCallNode && + absence && + isAbsenceAssert && + isPresenceQuery + ) { context.report({ node, messageId: 'wrongAbsenceQuery' }); } }, diff --git a/tests/lib/rules/prefer-presence-queries.test.ts b/tests/lib/rules/prefer-presence-queries.test.ts index 2b5187f2..a5e8b3c9 100644 --- a/tests/lib/rules/prefer-presence-queries.test.ts +++ b/tests/lib/rules/prefer-presence-queries.test.ts @@ -837,6 +837,12 @@ ruleTester.run(RULE_NAME, rule, { // right after clicking submit button it disappears expect(submitButton).not.toBeInTheDocument() `, + `// checking absence on getBy* inside a within with queryBy* outside the within + expect(within(screen.getByRole("button")).queryByText("Hello")).not.toBeInTheDocument() + `, + `// checking presence on getBy* inside a within with getBy* outside the within + expect(within(screen.getByRole("button")).getByText("Hello")).toBeInTheDocument() + `, ], invalid: [ // cases: asserting absence incorrectly with `getBy*` queries @@ -1199,5 +1205,47 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [{ line: 4, column: 14, messageId: 'wrongAbsenceQuery' }], }, + { + code: ` + // case: asserting within check does still work with improper outer clause + expect(within(screen.getByRole("button")).getByText("Hello")).not.toBeInTheDocument()`, + errors: [{ line: 3, column: 46, messageId: 'wrongAbsenceQuery' }], + }, + { + code: ` + // case: asserting within check does still work with improper outer clause + expect(within(screen.getByRole("button")).queryByText("Hello")).toBeInTheDocument()`, + errors: [{ line: 3, column: 46, messageId: 'wrongPresenceQuery' }], + }, + { + code: ` + // case: asserting within check does still work with improper outer clause and improper inner clause + expect(within(screen.queryByRole("button")).getByText("Hello")).not.toBeInTheDocument()`, + errors: [ + { line: 3, column: 25, messageId: 'wrongPresenceQuery' }, + { line: 3, column: 48, messageId: 'wrongAbsenceQuery' }, + ], + }, + { + code: ` + // case: asserting within check does still work with proper outer clause and improper inner clause + expect(within(screen.queryByRole("button")).queryByText("Hello")).not.toBeInTheDocument()`, + errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }], + }, + { + code: ` + // case: asserting within check does still work with proper outer clause and improper inner clause + expect(within(screen.queryByRole("button")).getByText("Hello")).toBeInTheDocument()`, + errors: [{ line: 3, column: 25, messageId: 'wrongPresenceQuery' }], + }, + { + code: ` + // case: asserting within check does still work with improper outer clause and improper inner clause + expect(within(screen.queryByRole("button")).queryByText("Hello")).toBeInTheDocument()`, + errors: [ + { line: 3, column: 25, messageId: 'wrongPresenceQuery' }, + { line: 3, column: 48, messageId: 'wrongPresenceQuery' }, + ], + }, ], });