diff --git a/lib/rules/no-node-access.ts b/lib/rules/no-node-access.ts index 0027fcd3..0099ee25 100644 --- a/lib/rules/no-node-access.ts +++ b/lib/rules/no-node-access.ts @@ -1,10 +1,20 @@ +import { + DefinitionType, + type ScopeVariable, +} from '@typescript-eslint/scope-manager'; import { TSESTree, ASTUtils } from '@typescript-eslint/utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; -import { isCallExpression, isMemberExpression } from '../node-utils'; +import { + getDeepestIdentifierNode, + getPropertyIdentifierNode, + isCallExpression, + isMemberExpression, +} from '../node-utils'; import { ALL_RETURNING_NODES, EVENT_HANDLER_METHODS, + getScope, resolveToTestingLibraryFn, } from '../utils'; @@ -88,24 +98,39 @@ export default createTestingLibraryRule({ } } + function detectTestingLibraryFn( + node: TSESTree.CallExpression, + variable: ScopeVariable | null + ) { + if (variable && variable.defs.length > 0) { + const def = variable.defs[0]; + if ( + def.type === DefinitionType.Variable && + isCallExpression(def.node.init) + ) { + return resolveToTestingLibraryFn(def.node.init, context); + } + } + + return resolveToTestingLibraryFn(node, context); + } + return { CallExpression(node: TSESTree.CallExpression) { - const { callee } = node; - const property = isMemberExpression(callee) ? callee.property : null; - const object = isMemberExpression(callee) ? callee.object : null; - - const propertyName = ASTUtils.isIdentifier(property) - ? property.name - : null; - const objectName = ASTUtils.isIdentifier(object) ? object.name : null; + const property = getDeepestIdentifierNode(node); + const identifier = getPropertyIdentifierNode(node); const isEventHandlerMethod = EVENT_HANDLER_METHODS.some( - (method) => method === propertyName + (method) => method === property?.name ); const hasUserEventInstanceName = userEventInstanceNames.has( - objectName ?? '' + identifier?.name ?? '' ); - const testingLibraryFn = resolveToTestingLibraryFn(node, context); + + const variable = identifier + ? ASTUtils.findVariable(getScope(context, node), identifier) + : null; + const testingLibraryFn = detectTestingLibraryFn(node, variable); if ( !testingLibraryFn && diff --git a/tests/lib/rules/no-node-access.test.ts b/tests/lib/rules/no-node-access.test.ts index 8e9ec456..f745ea11 100644 --- a/tests/lib/rules/no-node-access.test.ts +++ b/tests/lib/rules/no-node-access.test.ts @@ -231,7 +231,6 @@ ruleTester.run(RULE_NAME, rule, { }, { code: ` - // case: custom module set but not imported using ${testingFramework} (aggressive reporting limited) import { screen } from '${testingFramework}'; const ui = { @@ -241,6 +240,32 @@ ruleTester.run(RULE_NAME, rule, { const select = ui.select.get(); expect(select).toHaveClass(selectClasses.select); }); + `, + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + // case: custom module set but not imported using ${testingFramework} (aggressive reporting limited) + import { screen, render } from 'test-utils'; + import MyComponent from './MyComponent' + + test('...', async () => { + const { user } = render() + await user.click(screen.getByRole("button")) + }); + `, + }, + { + settings: { 'testing-library/utils-module': 'test-utils' }, + code: ` + // case: custom module set but not imported using ${testingFramework} (aggressive reporting limited) + import { screen, render } from 'test-utils'; + import MyComponent from './MyComponent' + + test('...', async () => { + const result = render() + await result.user.click(screen.getByRole("button")) + }); `, }, ]