Skip to content

Commit b29277f

Browse files
authored
fix: Aggressive Reporting support when resolving Testing Library utils (#1043)
Fixes #1038
1 parent 247b205 commit b29277f

File tree

7 files changed

+192
-29
lines changed

7 files changed

+192
-29
lines changed

lib/create-testing-library-rule/detect-testing-library-utils.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ import {
2323
ASYNC_UTILS,
2424
DEBUG_UTILS,
2525
PRESENCE_MATCHERS,
26+
USER_EVENT_MODULE,
2627
} from '../utils';
28+
import {
29+
isCustomTestingLibraryModule,
30+
isOfficialTestingLibraryModule,
31+
isTestingLibraryModule,
32+
} from '../utils/is-testing-library-module';
2733

2834
const SETTING_OPTION_OFF = 'off';
2935

@@ -133,7 +139,6 @@ export interface DetectionHelpers {
133139
isNodeComingFromTestingLibrary: IsNodeComingFromTestingLibraryFn;
134140
}
135141

136-
const USER_EVENT_PACKAGE = '@testing-library/user-event';
137142
const REACT_DOM_TEST_UTILS_PACKAGE = 'react-dom/test-utils';
138143
const FIRE_EVENT_NAME = 'fireEvent';
139144
const CREATE_EVENT_NAME = 'createEvent';
@@ -960,12 +965,11 @@ export function detectTestingLibraryUtils<
960965
}
961966

962967
const hasImportElementMatch = hasImportMatch(importNode, identifierName);
963-
const hasImportModuleMatch =
964-
/testing-library/g.test(importDeclarationName) ||
965-
(typeof customModuleSetting === 'string' &&
966-
importDeclarationName.endsWith(customModuleSetting));
967968

968-
return hasImportElementMatch && hasImportModuleMatch;
969+
return (
970+
hasImportElementMatch &&
971+
isTestingLibraryModule(importDeclarationName, customModuleSetting)
972+
);
969973
};
970974

971975
const helpers: DetectionHelpers = {
@@ -1017,17 +1021,16 @@ export function detectTestingLibraryUtils<
10171021
}
10181022
// check only if testing library import not found yet so we avoid
10191023
// to override importedTestingLibraryNodes after it's found
1020-
if (/testing-library/g.test(node.source.value)) {
1024+
if (isOfficialTestingLibraryModule(node.source.value)) {
10211025
importedTestingLibraryNodes.push(node);
10221026
}
10231027

10241028
// check only if custom module import not found yet so we avoid
10251029
// to override importedCustomModuleNode after it's found
10261030
const customModule = getCustomModule();
10271031
if (
1028-
customModule &&
10291032
!importedCustomModuleNode &&
1030-
node.source.value.endsWith(customModule)
1033+
isCustomTestingLibraryModule(node.source.value, customModule)
10311034
) {
10321035
importedCustomModuleNode = node;
10331036
}
@@ -1036,7 +1039,7 @@ export function detectTestingLibraryUtils<
10361039
// to override importedUserEventLibraryNode after it's found
10371040
if (
10381041
!importedUserEventLibraryNode &&
1039-
node.source.value === USER_EVENT_PACKAGE
1042+
node.source.value === USER_EVENT_MODULE
10401043
) {
10411044
importedUserEventLibraryNode = node;
10421045
}
@@ -1063,7 +1066,7 @@ export function detectTestingLibraryUtils<
10631066
(arg) =>
10641067
isLiteral(arg) &&
10651068
typeof arg.value === 'string' &&
1066-
/testing-library/g.test(arg.value)
1069+
isOfficialTestingLibraryModule(arg.value)
10671070
)
10681071
) {
10691072
importedTestingLibraryNodes.push(callExpression);
@@ -1074,10 +1077,9 @@ export function detectTestingLibraryUtils<
10741077
!importedCustomModuleNode &&
10751078
args.some(
10761079
(arg) =>
1077-
customModule &&
10781080
isLiteral(arg) &&
10791081
typeof arg.value === 'string' &&
1080-
arg.value.endsWith(customModule)
1082+
isCustomTestingLibraryModule(arg.value, customModule)
10811083
)
10821084
) {
10831085
importedCustomModuleNode = callExpression;
@@ -1089,7 +1091,7 @@ export function detectTestingLibraryUtils<
10891091
(arg) =>
10901092
isLiteral(arg) &&
10911093
typeof arg.value === 'string' &&
1092-
arg.value === USER_EVENT_PACKAGE
1094+
arg.value === USER_EVENT_MODULE
10931095
)
10941096
) {
10951097
importedUserEventLibraryNode = callExpression;

lib/utils/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ const LIBRARY_MODULES = [
3333

3434
const USER_EVENT_MODULE = '@testing-library/user-event';
3535

36+
const OLD_LIBRARY_MODULES = [
37+
'dom-testing-library',
38+
'vue-testing-library',
39+
'react-testing-library',
40+
] as const;
41+
3642
const SYNC_QUERIES_VARIANTS = [
3743
'getBy',
3844
'getAllBy',
@@ -154,4 +160,5 @@ export {
154160
ABSENCE_MATCHERS,
155161
EVENT_HANDLER_METHODS,
156162
USER_EVENT_MODULE,
163+
OLD_LIBRARY_MODULES,
157164
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { TestingLibrarySettings } from '../create-testing-library-rule/detect-testing-library-utils';
2+
3+
import { LIBRARY_MODULES, OLD_LIBRARY_MODULES, USER_EVENT_MODULE } from '.';
4+
5+
export const isOfficialTestingLibraryModule = (importSourceName: string) =>
6+
[...OLD_LIBRARY_MODULES, ...LIBRARY_MODULES, USER_EVENT_MODULE].includes(
7+
importSourceName
8+
);
9+
10+
export const isCustomTestingLibraryModule = (
11+
importSourceName: string,
12+
customModuleSetting: TestingLibrarySettings['testing-library/utils-module']
13+
) =>
14+
typeof customModuleSetting === 'string' &&
15+
importSourceName.endsWith(customModuleSetting);
16+
17+
export const isTestingLibraryModule = (
18+
importSourceName: string,
19+
customModuleSetting?: TestingLibrarySettings['testing-library/utils-module']
20+
) =>
21+
isOfficialTestingLibraryModule(importSourceName) ||
22+
isCustomTestingLibraryModule(importSourceName, customModuleSetting);

lib/utils/resolve-to-testing-library-fn.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
isSupportedAccessor,
2525
} from '../node-utils/accessors';
2626

27-
import { LIBRARY_MODULES, USER_EVENT_MODULE } from '.';
27+
import { isTestingLibraryModule } from './is-testing-library-module';
2828

2929
interface ImportDetails {
3030
source: string;
@@ -171,11 +171,8 @@ export const resolveToTestingLibraryFn = <
171171
}
172172

173173
const customModuleSetting = context.settings['testing-library/utils-module'];
174-
if (
175-
[...LIBRARY_MODULES, USER_EVENT_MODULE, customModuleSetting].some(
176-
(module) => module === maybeImport.source
177-
)
178-
) {
174+
175+
if (isTestingLibraryModule(maybeImport.source, customModuleSetting)) {
179176
return {
180177
original: maybeImport.imported,
181178
local: maybeImport.local,

tests/lib/rules/no-node-access.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ ruleTester.run(RULE_NAME, rule, {
227227
228228
const buttonText = screen.getByText('submit');
229229
fe.click(buttonText);
230+
`,
231+
},
232+
{
233+
settings: { 'testing-library/utils-module': 'test-utils' },
234+
code: `
235+
// case: custom module set but not imported using ${testingFramework} (aggressive reporting limited)
236+
import { screen, fireEvent } from '../test-utils';
237+
238+
const buttonText = screen.getByText('submit');
239+
fireEvent.click(buttonText);
230240
`,
231241
},
232242
{
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import {
2+
isCustomTestingLibraryModule,
3+
isOfficialTestingLibraryModule,
4+
isTestingLibraryModule,
5+
} from '../../../lib/utils/is-testing-library-module';
6+
7+
const OLD_LIBRARY_MODULES = [
8+
'dom-testing-library',
9+
'vue-testing-library',
10+
'react-testing-library',
11+
] as const;
12+
13+
const LIBRARY_MODULES = [
14+
'@testing-library/dom',
15+
'@testing-library/angular',
16+
'@testing-library/react',
17+
'@testing-library/preact',
18+
'@testing-library/vue',
19+
'@testing-library/svelte',
20+
'@marko/testing-library',
21+
] as const;
22+
23+
const USER_EVENT_MODULE = '@testing-library/user-event';
24+
25+
describe('isOfficialTestingLibraryModule', () => {
26+
it.each([...OLD_LIBRARY_MODULES, ...LIBRARY_MODULES, USER_EVENT_MODULE])(
27+
'returns true when arg is "%s"',
28+
(importSourceName) => {
29+
const result = isOfficialTestingLibraryModule(importSourceName);
30+
31+
expect(result).toBe(true);
32+
}
33+
);
34+
35+
it.each(['custom-modules', 'hoge-testing-library', '@testing-library/hoge'])(
36+
'returns false when arg is "%s"',
37+
(importSourceName) => {
38+
const result = isOfficialTestingLibraryModule(importSourceName);
39+
40+
expect(result).toBe(false);
41+
}
42+
);
43+
});
44+
45+
describe('isCustomTestingLibraryModule', () => {
46+
it.each(['test-utils', '../test-utils', '@/test-utils'])(
47+
'returns true when arg is "%s"',
48+
(importSourceName) => {
49+
const result = isCustomTestingLibraryModule(
50+
importSourceName,
51+
'test-utils'
52+
);
53+
54+
expect(result).toBe(true);
55+
}
56+
);
57+
58+
it.each([
59+
'custom-modules',
60+
'react-testing-library',
61+
'@testing-library/react',
62+
'test-util',
63+
'test-utils-module',
64+
])('returns false when arg is "%s"', (importSourceName) => {
65+
const result = isCustomTestingLibraryModule(importSourceName, 'test-utils');
66+
67+
expect(result).toBe(false);
68+
});
69+
});
70+
71+
describe('isTestingLibraryModule', () => {
72+
it.each([
73+
...OLD_LIBRARY_MODULES,
74+
...LIBRARY_MODULES,
75+
USER_EVENT_MODULE,
76+
'test-utils',
77+
'../test-utils',
78+
'@/test-utils',
79+
])('returns true when arg is "%s"', (importSourceName) => {
80+
const result = isTestingLibraryModule(importSourceName, 'test-utils');
81+
82+
expect(result).toBe(true);
83+
});
84+
85+
it.each([
86+
'custom-modules',
87+
'hoge-testing-library',
88+
'@testing-library/hoge',
89+
'test-util',
90+
'test-utils-module',
91+
])('returns false when arg is "%s"', (importSourceName) => {
92+
const result = isTestingLibraryModule(importSourceName, 'test-utils');
93+
94+
expect(result).toBe(false);
95+
});
96+
});

tests/lib/utils/resolve-to-testing-library-fn.test.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,6 @@ ruleTester.run('esm', rule, {
8080
userEvent.default.setup()
8181
`,
8282
},
83-
{
84-
// Verifies that a local './test-utils' import doesn't match the configured 'test-utils' utils module
85-
settings: { 'testing-library/utils-module': 'test-utils' },
86-
code: `
87-
import { userEvent } from './test-utils';
88-
89-
userEvent.setup()
90-
`,
91-
},
9283
...LIBRARY_MODULES.map((module) => ({
9384
code: `
9485
import * as testingLibrary from '${module}';
@@ -172,6 +163,44 @@ ruleTester.run('esm', rule, {
172163
},
173164
],
174165
},
166+
{
167+
settings: { 'testing-library/utils-module': 'test-utils' },
168+
code: `
169+
import userEvent from '../test-utils';
170+
171+
userEvent.setup()
172+
`,
173+
errors: [
174+
{
175+
messageId: 'details',
176+
data: {
177+
data: {
178+
original: null,
179+
local: 'userEvent',
180+
},
181+
},
182+
},
183+
],
184+
},
185+
{
186+
settings: { 'testing-library/utils-module': 'test-utils' },
187+
code: `
188+
import userEvent from '@/test-utils';
189+
190+
userEvent.setup()
191+
`,
192+
errors: [
193+
{
194+
messageId: 'details',
195+
data: {
196+
data: {
197+
original: null,
198+
local: 'userEvent',
199+
},
200+
},
201+
},
202+
],
203+
},
175204
{
176205
settings: {
177206
'testing-library/custom-renders': ['customRender', 'renderWithRedux'],

0 commit comments

Comments
 (0)