From 0175206d0c5990800c46a82dad8145f560937f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 20 Mar 2021 18:11:26 +0100 Subject: [PATCH 1/7] docs(no-container): remove custom render reference --- docs/rules/no-container.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/rules/no-container.md b/docs/rules/no-container.md index 658879e2..2089fad5 100644 --- a/docs/rules/no-container.md +++ b/docs/rules/no-container.md @@ -32,12 +32,6 @@ render(); screen.getByRole('button', { name: /click me/i }); ``` -If you use [custom render functions](https://testing-library.com/docs/example-react-redux) then you can set a config option in your `.eslintrc` to look for these. - -``` -"testing-library/no-container": ["error", {"renderFunctions":["renderWithRedux", "renderWithRouter"]}], -``` - ## Further Reading - [about the `container` element](https://testing-library.com/docs/react-testing-library/api#container-1) From bf115039b2a1377c91dbb923f0e429c0f12a8786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 20 Mar 2021 18:24:57 +0100 Subject: [PATCH 2/7] test(no-container): improve errors asserts --- tests/lib/rules/no-container.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/lib/rules/no-container.test.ts b/tests/lib/rules/no-container.test.ts index 4823e9a6..1918f8ef 100644 --- a/tests/lib/rules/no-container.test.ts +++ b/tests/lib/rules/no-container.test.ts @@ -57,6 +57,8 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { + line: 3, + column: 24, messageId: 'noContainer', }, ], @@ -68,6 +70,8 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { + line: 3, + column: 9, messageId: 'noContainer', }, ], @@ -79,6 +83,8 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { + line: 3, + column: 9, messageId: 'noContainer', }, ], @@ -90,6 +96,8 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { + line: 3, + column: 24, messageId: 'noContainer', }, ], @@ -101,6 +109,8 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { + line: 3, + column: 9, messageId: 'noContainer', }, ], @@ -117,6 +127,8 @@ ruleTester.run(RULE_NAME, rule, { ], errors: [ { + line: 3, + column: 9, messageId: 'noContainer', }, ], From 585b0e418eab2d8c44719b5b9620131160fe432c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 20 Mar 2021 18:37:10 +0100 Subject: [PATCH 3/7] refactor(no-container): use new rule creator --- lib/rules/no-container.ts | 82 ++++++++++++---------------- tests/lib/rules/no-container.test.ts | 8 +-- 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/lib/rules/no-container.ts b/lib/rules/no-container.ts index 7970ec9c..74f98f99 100644 --- a/lib/rules/no-container.ts +++ b/lib/rules/no-container.ts @@ -1,21 +1,17 @@ +import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils'; import { - ESLintUtils, - TSESTree, - ASTUtils, -} from '@typescript-eslint/experimental-utils'; -import { getDocsUrl } from '../utils'; -import { + getDeepestIdentifierNode, isMemberExpression, isObjectPattern, isProperty, - isRenderVariableDeclarator, } from '../node-utils'; +import { createTestingLibraryRule } from '../create-testing-library-rule'; export const RULE_NAME = 'no-container'; export type MessageIds = 'noContainer'; -type Options = [{ renderFunctions?: string[] }]; +type Options = []; -export default ESLintUtils.RuleCreator(getDocsUrl)({ +export default createTestingLibraryRule({ name: RULE_NAME, meta: { type: 'problem', @@ -29,25 +25,11 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ 'Avoid using container methods. Prefer using the methods from Testing Library, such as "getByRole()"', }, fixable: null, - schema: [ - { - type: 'object', - properties: { - renderFunctions: { - type: 'array', - }, - }, - }, - ], + schema: [], }, - defaultOptions: [ - { - renderFunctions: [], - }, - ], + defaultOptions: [], - create(context, [options]) { - const { renderFunctions } = options; + create(context, [], helpers) { const destructuredContainerPropNames: string[] = []; let renderWrapperName: string = null; let containerName: string = null; @@ -81,30 +63,34 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ return { VariableDeclarator(node) { - if (isRenderVariableDeclarator(node, ['render', ...renderFunctions])) { - if (isObjectPattern(node.id)) { - const containerIndex = node.id.properties.findIndex( - (property) => - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - property.key.name === 'container' - ); - const nodeValue = - containerIndex !== -1 && node.id.properties[containerIndex].value; - if (ASTUtils.isIdentifier(nodeValue)) { - containerName = nodeValue.name; - } else { - isObjectPattern(nodeValue) && - nodeValue.properties.forEach( - (property) => - isProperty(property) && - ASTUtils.isIdentifier(property.key) && - destructuredContainerPropNames.push(property.key.name) - ); - } + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + if (!helpers.isRenderUtil(initIdentifierNode)) { + return; + } + + if (isObjectPattern(node.id)) { + const containerIndex = node.id.properties.findIndex( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + property.key.name === 'container' + ); + const nodeValue = + containerIndex !== -1 && node.id.properties[containerIndex].value; + if (ASTUtils.isIdentifier(nodeValue)) { + containerName = nodeValue.name; } else { - renderWrapperName = ASTUtils.isIdentifier(node.id) && node.id.name; + isObjectPattern(nodeValue) && + nodeValue.properties.forEach( + (property) => + isProperty(property) && + ASTUtils.isIdentifier(property.key) && + destructuredContainerPropNames.push(property.key.name) + ); } + } else { + renderWrapperName = ASTUtils.isIdentifier(node.id) && node.id.name; } }, diff --git a/tests/lib/rules/no-container.test.ts b/tests/lib/rules/no-container.test.ts index 1918f8ef..3161cbcd 100644 --- a/tests/lib/rules/no-container.test.ts +++ b/tests/lib/rules/no-container.test.ts @@ -116,15 +116,13 @@ ruleTester.run(RULE_NAME, rule, { ], }, { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, code: ` const { container } = renderWithRedux(); container.querySelector(); `, - options: [ - { - renderFunctions: ['renderWithRedux'], - }, - ], errors: [ { line: 3, From 81cfde859e4ead8c8a68ba07766062c294ceed9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 20 Mar 2021 18:48:13 +0100 Subject: [PATCH 4/7] refactor(no-container): extract isRenderVariableDeclarator helper --- lib/detect-testing-library-utils.ts | 12 ++++++++++ lib/node-utils.ts | 25 -------------------- lib/rules/no-container.ts | 13 ++++------ lib/rules/no-debug.ts | 4 +--- lib/rules/render-result-naming-convention.ts | 2 +- 5 files changed, 18 insertions(+), 38 deletions(-) diff --git a/lib/detect-testing-library-utils.ts b/lib/detect-testing-library-utils.ts index 316accf7..f2746fd5 100644 --- a/lib/detect-testing-library-utils.ts +++ b/lib/detect-testing-library-utils.ts @@ -5,6 +5,7 @@ import { } from '@typescript-eslint/experimental-utils'; import { getAssertNodeInfo, + getDeepestIdentifierNode, getImportModuleName, getPropertyIdentifierNode, getReferenceNode, @@ -69,6 +70,9 @@ type IsAsyncUtilFn = ( ) => boolean; type IsFireEventMethodFn = (node: TSESTree.Identifier) => boolean; type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean; +type IsRenderVariableDeclaratorFn = ( + node: TSESTree.VariableDeclarator +) => boolean; type IsDebugUtilFn = (node: TSESTree.Identifier) => boolean; type IsPresenceAssertFn = (node: TSESTree.MemberExpression) => boolean; type IsAbsenceAssertFn = (node: TSESTree.MemberExpression) => boolean; @@ -97,6 +101,7 @@ export interface DetectionHelpers { isAsyncUtil: IsAsyncUtilFn; isFireEventMethod: IsFireEventMethodFn; isRenderUtil: IsRenderUtilFn; + isRenderVariableDeclarator: IsRenderVariableDeclaratorFn; isDebugUtil: IsDebugUtilFn; isPresenceAssert: IsPresenceAssertFn; isAbsenceAssert: IsAbsenceAssertFn; @@ -408,6 +413,12 @@ export function detectTestingLibraryUtils< ); }; + const isRenderVariableDeclarator: IsRenderVariableDeclaratorFn = (node) => { + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + return isRenderUtil(initIdentifierNode); + }; + const isDebugUtil: IsDebugUtilFn = (node) => { return isTestingLibraryUtil( node, @@ -562,6 +573,7 @@ export function detectTestingLibraryUtils< isAsyncUtil, isFireEventMethod, isRenderUtil, + isRenderVariableDeclarator, isDebugUtil, isPresenceAssert, isAbsenceAssert, diff --git a/lib/node-utils.ts b/lib/node-utils.ts index c6a76253..4cda2a2e 100644 --- a/lib/node-utils.ts +++ b/lib/node-utils.ts @@ -477,31 +477,6 @@ export function isRenderFunction( }); } -// TODO: should be removed after v4 is finished -export function isRenderVariableDeclarator( - node: TSESTree.VariableDeclarator, - renderFunctions: string[] -): boolean { - if (node.init) { - if (ASTUtils.isAwaitExpression(node.init)) { - return ( - node.init.argument && - isRenderFunction( - node.init.argument as TSESTree.CallExpression, - renderFunctions - ) - ); - } else { - return ( - isCallExpression(node.init) && - isRenderFunction(node.init, renderFunctions) - ); - } - } - - return false; -} - // TODO: extract into types file? export type ImportModuleNode = | TSESTree.ImportDeclaration diff --git a/lib/rules/no-container.ts b/lib/rules/no-container.ts index 74f98f99..382f04ec 100644 --- a/lib/rules/no-container.ts +++ b/lib/rules/no-container.ts @@ -1,10 +1,5 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils'; -import { - getDeepestIdentifierNode, - isMemberExpression, - isObjectPattern, - isProperty, -} from '../node-utils'; +import { isMemberExpression, isObjectPattern, isProperty } from '../node-utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; export const RULE_NAME = 'no-container'; @@ -63,9 +58,7 @@ export default createTestingLibraryRule({ return { VariableDeclarator(node) { - const initIdentifierNode = getDeepestIdentifierNode(node.init); - - if (!helpers.isRenderUtil(initIdentifierNode)) { + if (!helpers.isRenderVariableDeclarator(node)) { return; } @@ -76,8 +69,10 @@ export default createTestingLibraryRule({ ASTUtils.isIdentifier(property.key) && property.key.name === 'container' ); + const nodeValue = containerIndex !== -1 && node.id.properties[containerIndex].value; + if (ASTUtils.isIdentifier(nodeValue)) { containerName = nodeValue.name; } else { diff --git a/lib/rules/no-debug.ts b/lib/rules/no-debug.ts index f03f0a33..4e443d80 100644 --- a/lib/rules/no-debug.ts +++ b/lib/rules/no-debug.ts @@ -44,9 +44,7 @@ export default createTestingLibraryRule({ return { VariableDeclarator(node) { - const initIdentifierNode = getDeepestIdentifierNode(node.init); - - if (!helpers.isRenderUtil(initIdentifierNode)) { + if (!helpers.isRenderVariableDeclarator(node)) { return; } diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index b322a89a..9fb7adbb 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -59,7 +59,7 @@ export default createTestingLibraryRule({ } if ( - !helpers.isRenderUtil(initIdentifierNode) && + !helpers.isRenderVariableDeclarator(node) && !renderWrapperNames.includes(initIdentifierNode.name) ) { return; From ddef4db51d0b4446f4bd784a9b20628cc57eb74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 20 Mar 2021 18:57:45 +0100 Subject: [PATCH 5/7] refactor(no-container): improve node reported location --- lib/rules/no-container.ts | 15 ++++++++++++--- tests/lib/rules/no-container.test.ts | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-container.ts b/lib/rules/no-container.ts index 382f04ec..0aa99f50 100644 --- a/lib/rules/no-container.ts +++ b/lib/rules/no-container.ts @@ -36,18 +36,27 @@ export default createTestingLibraryRule({ if (isMemberExpression(innerNode)) { if (ASTUtils.isIdentifier(innerNode.object)) { const isContainerName = innerNode.object.name === containerName; - const isRenderWrapper = innerNode.object.name === renderWrapperName; + if (isContainerName) { + context.report({ + node: innerNode, + messageId: 'noContainer', + }); + return; + } + + const isRenderWrapper = innerNode.object.name === renderWrapperName; containerCallsMethod = ASTUtils.isIdentifier(innerNode.property) && innerNode.property.name === 'container' && isRenderWrapper; - if (isContainerName || containerCallsMethod) { + if (containerCallsMethod) { context.report({ - node: innerNode, + node: innerNode.property, messageId: 'noContainer', }); + return; } } showErrorIfChainedContainerMethod( diff --git a/tests/lib/rules/no-container.test.ts b/tests/lib/rules/no-container.test.ts index 3161cbcd..a2d2bc4d 100644 --- a/tests/lib/rules/no-container.test.ts +++ b/tests/lib/rules/no-container.test.ts @@ -97,7 +97,7 @@ ruleTester.run(RULE_NAME, rule, { errors: [ { line: 3, - column: 24, + column: 29, messageId: 'noContainer', }, ], From a8602219988508bc7bcc788e61cdee3a17527a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 20 Mar 2021 19:19:16 +0100 Subject: [PATCH 6/7] refactor(no-container): detect nodes coming from render wrapper --- lib/detect-testing-library-utils.ts | 4 ++ lib/rules/no-container.ts | 66 ++++++++++++++++------ tests/lib/rules/no-container.test.ts | 82 ++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/lib/detect-testing-library-utils.ts b/lib/detect-testing-library-utils.ts index f2746fd5..503729c1 100644 --- a/lib/detect-testing-library-utils.ts +++ b/lib/detect-testing-library-utils.ts @@ -154,6 +154,10 @@ export function detectTestingLibraryUtils< originalNodeName?: string ) => boolean ): boolean { + if (!node) { + return false; + } + const referenceNode = getReferenceNode(node); const referenceNodeIdentifier = getPropertyIdentifierNode(referenceNode); const importedUtilSpecifier = getImportedUtilSpecifier( diff --git a/lib/rules/no-container.ts b/lib/rules/no-container.ts index 0aa99f50..d50923c2 100644 --- a/lib/rules/no-container.ts +++ b/lib/rules/no-container.ts @@ -1,5 +1,12 @@ import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils'; -import { isMemberExpression, isObjectPattern, isProperty } from '../node-utils'; +import { + getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, + isMemberExpression, + isObjectPattern, + isProperty, +} from '../node-utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; export const RULE_NAME = 'no-container'; @@ -26,10 +33,19 @@ export default createTestingLibraryRule({ create(context, [], helpers) { const destructuredContainerPropNames: string[] = []; - let renderWrapperName: string = null; + const renderWrapperNames: string[] = []; + let renderResultVarName: string = null; let containerName: string = null; let containerCallsMethod = false; + function detectRenderWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + renderWrapperNames.push(getFunctionName(innerFunction)); + } + } + function showErrorIfChainedContainerMethod( innerNode: TSESTree.MemberExpression ) { @@ -45,7 +61,7 @@ export default createTestingLibraryRule({ return; } - const isRenderWrapper = innerNode.object.name === renderWrapperName; + const isRenderWrapper = innerNode.object.name === renderResultVarName; containerCallsMethod = ASTUtils.isIdentifier(innerNode.property) && innerNode.property.name === 'container' && @@ -66,8 +82,35 @@ export default createTestingLibraryRule({ } return { + CallExpression(node) { + const callExpressionIdentifier = getDeepestIdentifierNode(node); + if (helpers.isRenderUtil(callExpressionIdentifier)) { + detectRenderWrapper(callExpressionIdentifier); + } + + if (isMemberExpression(node.callee)) { + showErrorIfChainedContainerMethod(node.callee); + } else { + ASTUtils.isIdentifier(node.callee) && + destructuredContainerPropNames.includes(node.callee.name) && + context.report({ + node, + messageId: 'noContainer', + }); + } + }, + VariableDeclarator(node) { - if (!helpers.isRenderVariableDeclarator(node)) { + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + const isRenderWrapperVariableDeclarator = initIdentifierNode + ? renderWrapperNames.includes(initIdentifierNode.name) + : false; + + if ( + !helpers.isRenderVariableDeclarator(node) && + !isRenderWrapperVariableDeclarator + ) { return; } @@ -94,20 +137,7 @@ export default createTestingLibraryRule({ ); } } else { - renderWrapperName = ASTUtils.isIdentifier(node.id) && node.id.name; - } - }, - - CallExpression(node) { - if (isMemberExpression(node.callee)) { - showErrorIfChainedContainerMethod(node.callee); - } else { - ASTUtils.isIdentifier(node.callee) && - destructuredContainerPropNames.includes(node.callee.name) && - context.report({ - node, - messageId: 'noContainer', - }); + renderResultVarName = ASTUtils.isIdentifier(node.id) && node.id.name; } }, }; diff --git a/tests/lib/rules/no-container.test.ts b/tests/lib/rules/no-container.test.ts index a2d2bc4d..0c15d5f4 100644 --- a/tests/lib/rules/no-container.test.ts +++ b/tests/lib/rules/no-container.test.ts @@ -48,6 +48,25 @@ ruleTester.run(RULE_NAME, rule, { expect(firstChild).toBeDefined(); `, }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render as renamed } from '@testing-library/react' + import { render } from 'somewhere-else' + const { container } = render(); + const button = container.querySelector('.btn-primary'); + `, + }, + { + settings: { + 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], + }, + code: ` + import { otherRender } from 'somewhere-else' + const { container } = otherRender(); + const button = container.querySelector('.btn-primary'); + `, + }, ], invalid: [ { @@ -63,6 +82,54 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render } from 'test-utils' + const { container } = render(); + const button = container.querySelector('.btn-primary'); + `, + errors: [ + { + line: 4, + column: 24, + messageId: 'noContainer', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render as testingRender } from '@testing-library/react' + const { container: renamed } = testingRender(); + const button = renamed.querySelector('.btn-primary'); + `, + errors: [ + { + line: 4, + column: 24, + messageId: 'noContainer', + }, + ], + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render } from '@testing-library/react' + + const setup = () => render() + + const { container } = setup() + const button = container.querySelector('.btn-primary'); + `, + errors: [ + { + line: 7, + column: 24, + messageId: 'noContainer', + }, + ], + }, { code: ` const { container } = render(); @@ -115,6 +182,21 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render } from '@testing-library/react' + const { container: { querySelector } } = render(); + querySelector('foo'); + `, + errors: [ + { + line: 4, + column: 9, + messageId: 'noContainer', + }, + ], + }, { settings: { 'testing-library/custom-renders': ['customRender', 'renderWithRedux'], From 574d0621d034d339079c6415fccfb0aee6cbcc4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltr=C3=A1n=20Alarc=C3=B3n?= Date: Sat, 20 Mar 2021 19:42:02 +0100 Subject: [PATCH 7/7] refactor(no-debug): detect nodes coming from render wrapper --- lib/rules/no-debug.ts | 26 +++++++++++++++++++++++++- tests/lib/rules/no-debug.test.ts | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-debug.ts b/lib/rules/no-debug.ts index 4e443d80..3f5bb64e 100644 --- a/lib/rules/no-debug.ts +++ b/lib/rules/no-debug.ts @@ -1,5 +1,7 @@ import { getDeepestIdentifierNode, + getFunctionName, + getInnermostReturningFunction, getPropertyIdentifierNode, getReferenceNode, isObjectPattern, @@ -41,10 +43,28 @@ export default createTestingLibraryRule({ create(context, [], helpers) { const suspiciousDebugVariableNames: string[] = []; const suspiciousReferenceNodes: TSESTree.Identifier[] = []; + const renderWrapperNames: string[] = []; + + function detectRenderWrapper(node: TSESTree.Identifier): void { + const innerFunction = getInnermostReturningFunction(context, node); + + if (innerFunction) { + renderWrapperNames.push(getFunctionName(innerFunction)); + } + } return { VariableDeclarator(node) { - if (!helpers.isRenderVariableDeclarator(node)) { + const initIdentifierNode = getDeepestIdentifierNode(node.init); + + const isRenderWrapperVariableDeclarator = initIdentifierNode + ? renderWrapperNames.includes(initIdentifierNode.name) + : false; + + if ( + !helpers.isRenderVariableDeclarator(node) && + !isRenderWrapperVariableDeclarator + ) { return; } @@ -72,6 +92,10 @@ export default createTestingLibraryRule({ }, CallExpression(node) { const callExpressionIdentifier = getDeepestIdentifierNode(node); + if (helpers.isRenderUtil(callExpressionIdentifier)) { + detectRenderWrapper(callExpressionIdentifier); + } + const referenceNode = getReferenceNode(node); const referenceIdentifier = getPropertyIdentifierNode(referenceNode); diff --git a/tests/lib/rules/no-debug.test.ts b/tests/lib/rules/no-debug.test.ts index 2de323df..d2e3f589 100644 --- a/tests/lib/rules/no-debug.test.ts +++ b/tests/lib/rules/no-debug.test.ts @@ -211,6 +211,24 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + import { render } from 'test-utils' + + const setup = () => render() + + const utils = setup() + utils.debug() + `, + errors: [ + { + line: 7, + column: 15, + messageId: 'noDebug', + }, + ], + }, { settings: { 'testing-library/utils-module': 'test-utils' }, code: `// aggressive reporting disabled