From 2e7e1b68760e2185a0f66c259732ba26dc4bedef Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Mon, 28 Jul 2025 15:32:47 +0200
Subject: [PATCH 1/2] docs: add andreww2012 as a contributor for code (#1050)
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 9 +++++++++
README.md | 3 +++
2 files changed, 12 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 52146b4b..3998e5d6 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -738,6 +738,15 @@
"code",
"test"
]
+ },
+ {
+ "login": "andreww2012",
+ "name": "Andrew Kazakov",
+ "avatar_url": "https://avatars.githubusercontent.com/u/6554045?v=4",
+ "profile": "https://github.com/andreww2012",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 50b8f708..5c6649ae 100644
--- a/README.md
+++ b/README.md
@@ -555,6 +555,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Yukihiro Hasegawa 💻 ⚠️ |
 Charley Pugmire 💻 ⚠️ |
+
+  Andrew Kazakov 💻 |
+
From 079043ba43827e24d7d7f3efe37e153ca01eebaa Mon Sep 17 00:00:00 2001
From: Yukihiro Hasegawa <49516827+y-hsgw@users.noreply.github.com>
Date: Mon, 11 Aug 2025 16:00:12 +0900
Subject: [PATCH 2/2] fix(no-node-access): support user-event instances
returned from custom render (#1048)
---
lib/rules/no-node-access.ts | 49 ++++++++++++++++++------
tests/lib/rules/no-node-access.test.ts | 53 +++++++++++++++++++++++++-
2 files changed, 89 insertions(+), 13 deletions(-)
diff --git a/lib/rules/no-node-access.ts b/lib/rules/no-node-access.ts
index faecf12e..b0362886 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';
@@ -89,24 +99,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 05d5754b..eb5906d3 100644
--- a/tests/lib/rules/no-node-access.test.ts
+++ b/tests/lib/rules/no-node-access.test.ts
@@ -181,6 +181,16 @@ ruleTester.run(RULE_NAME, rule, {
const buttonText = screen.getByText('submit');
const userAlias = userEvent.setup();
userAlias.click(buttonText);
+ `,
+ },
+ {
+ code: `
+ import userEvent from '@testing-library/user-event';
+ import { screen } from '${testingFramework}';
+ test('...', () => {
+ const buttonText = screen.getByText('submit');
+ (() => { click: userEvent.click(buttonText); })();
+ });
`,
},
{
@@ -241,7 +251,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 = {
@@ -251,6 +260,48 @@ 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"))
+ });
+ `,
+ },
+ {
+ settings: {
+ 'testing-library/utils-module': 'TestUtils',
+ 'testing-library/custom-renders': ['renderComponent'],
+ },
+ code: `
+ // case: custom module set but not imported using ${testingFramework} (aggressive reporting limited)
+ import { screen, renderComponent } from './TestUtils';
+ import MyComponent from './MyComponent'
+
+ test('...', async () => {
+ const result = renderComponent()
+ await result.user.click(screen.getByRole("button"))
+ });
`,
},
]