Skip to content

Commit 6c8f14a

Browse files
authored
fix: check namespaced imports (#102)
* fix(no-debug): check namespaced imports * fix(prefer-wait-for): check namespaced imports Closes #101
1 parent 1420fd0 commit 6c8f14a

File tree

4 files changed

+151
-21
lines changed

4 files changed

+151
-21
lines changed

lib/rules/no-debug.js

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ module.exports = {
4545
}
4646

4747
let hasImportedScreen = false;
48+
let wildcardImportName = null;
4849

4950
return {
5051
VariableDeclarator(node) {
@@ -85,19 +86,22 @@ module.exports = {
8586
hasImportedScreen = true;
8687
}
8788
},
88-
ImportDeclaration(node) {
89-
const screenModuleName = LIBRARY_MODULES_WITH_SCREEN.find(
90-
module => module === node.source.value
91-
);
89+
// checks if import has shape:
90+
// import { screen } from '@testing-library/dom';
91+
'ImportDeclaration ImportSpecifier'(node) {
92+
const importDeclarationNode = node.parent;
9293

93-
if (
94-
screenModuleName &&
95-
node.specifiers.some(
96-
specifier => specifier.imported.name === 'screen'
97-
)
98-
) {
99-
hasImportedScreen = true;
100-
}
94+
if (!hasTestingLibraryImportModule(importDeclarationNode)) return;
95+
96+
hasImportedScreen = node.imported.name === 'screen';
97+
},
98+
// checks if import has shape:
99+
// import * as dtl from '@testing-library/dom';
100+
'ImportDeclaration ImportNamespaceSpecifier'(node) {
101+
const importDeclarationNode = node.parent;
102+
if (!hasTestingLibraryImportModule(importDeclarationNode)) return;
103+
104+
wildcardImportName = node.local && node.local.name;
101105
},
102106
[`CallExpression > Identifier[name="debug"]`](node) {
103107
if (hasDestructuredDebugStatement) {
@@ -108,11 +112,31 @@ module.exports = {
108112
}
109113
},
110114
[`CallExpression > MemberExpression > Identifier[name="debug"]`](node) {
111-
if (
115+
/*
116+
check if `debug` used following the pattern:
117+
118+
import { screen } from '@testing-library/dom';
119+
...
120+
screen.debug();
121+
*/
122+
const isScreenDebugUsed =
112123
hasImportedScreen &&
113124
node.parent &&
114-
node.parent.object.name === 'screen'
115-
) {
125+
node.parent.object.name === 'screen';
126+
127+
/*
128+
check if `debug` used following the pattern:
129+
130+
import * as dtl from '@testing-library/dom';
131+
...
132+
dtl.debug();
133+
*/
134+
const isNamespaceDebugUsed =
135+
wildcardImportName &&
136+
node.parent &&
137+
node.parent.object.name === wildcardImportName;
138+
139+
if (isScreenDebugUsed || isNamespaceDebugUsed) {
116140
context.report({
117141
node,
118142
messageId: 'noDebug',
@@ -164,3 +188,9 @@ function isRenderVariableDeclarator(node, renderFunctions) {
164188

165189
return false;
166190
}
191+
192+
function hasTestingLibraryImportModule(importDeclarationNode) {
193+
return LIBRARY_MODULES_WITH_SCREEN.some(
194+
module => module === importDeclarationNode.source.value
195+
);
196+
}

lib/rules/prefer-wait-for.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ module.exports = {
6161
methodName: node.name,
6262
},
6363
fix(fixer) {
64-
const { parent } = node;
65-
const [arg] = parent.arguments;
64+
const callExpressionNode = findClosestCallExpressionNode(node);
65+
const memberExpressionNode =
66+
node.parent.type === 'MemberExpression' && node.parent;
67+
const [arg] = callExpressionNode.arguments;
6668
const fixers = [];
6769

6870
if (arg) {
@@ -79,7 +81,16 @@ module.exports = {
7981
} else {
8082
// if wait method been fixed didn't have any callback
8183
// then we replace the method name and include an empty callback.
82-
fixers.push(fixer.replaceText(parent, 'waitFor(() => {})'));
84+
let methodReplacement = 'waitFor(() => {})';
85+
86+
// if wait method used like `foo.wait()` then we need to keep the
87+
// member expression to get `foo.waitFor(() => {})`
88+
if (memberExpressionNode) {
89+
methodReplacement = `${memberExpressionNode.object.name}.${methodReplacement}`;
90+
}
91+
const newText = methodReplacement;
92+
93+
fixers.push(fixer.replaceText(callExpressionNode, newText));
8394
}
8495

8596
return fixers;
@@ -90,8 +101,11 @@ module.exports = {
90101
return {
91102
'ImportDeclaration[source.value=/testing-library/]'(node) {
92103
const importedNames = node.specifiers
93-
.map(specifier => specifier.imported && specifier.imported.name)
94-
.filter(Boolean);
104+
.filter(
105+
specifier =>
106+
specifier.type === 'ImportSpecifier' && specifier.imported
107+
)
108+
.map(specifier => specifier.imported.name);
95109

96110
if (
97111
importedNames.some(importedName =>
@@ -101,7 +115,7 @@ module.exports = {
101115
importNodes.push(node);
102116
}
103117
},
104-
'CallExpression > Identifier[name=/^(wait|waitForElement|waitForDomChange)$/]'(
118+
'CallExpression Identifier[name=/^(wait|waitForElement|waitForDomChange)$/]'(
105119
node
106120
) {
107121
waitNodes.push(node);
@@ -118,3 +132,11 @@ module.exports = {
118132
};
119133
},
120134
};
135+
136+
function findClosestCallExpressionNode(node) {
137+
if (node.type === 'CallExpression') {
138+
return node;
139+
}
140+
141+
return findClosestCallExpressionNode(node.parent);
142+
}

tests/lib/rules/no-debug.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ ruleTester.run('no-debug', rule, {
7575
{
7676
code: `const { queries } = require('@testing-library/dom')`,
7777
},
78+
{
79+
code: `import * as dtl from '@testing-library/dom';
80+
const foo = dtl.debug;
81+
`,
82+
},
83+
{
84+
code: `
85+
import * as foo from '@somewhere/else';
86+
foo.debug();
87+
`,
88+
},
7889
{
7990
code: `import { queries } from '@testing-library/dom'`,
8091
},
@@ -204,5 +215,18 @@ ruleTester.run('no-debug', rule, {
204215
},
205216
],
206217
},
218+
{
219+
code: `
220+
import * as dtl from '@testing-library/dom';
221+
dtl.debug();
222+
`,
223+
errors: [
224+
{
225+
messageId: 'noDebug',
226+
line: 3,
227+
column: 13,
228+
},
229+
],
230+
},
207231
],
208232
});

tests/lib/rules/prefer-wait-for.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ ruleTester.run('prefer-wait-for', rule, {
2222
await waitForElementToBeRemoved(() => {});
2323
}`,
2424
},
25+
{
26+
code: `import * as testingLibrary from '@testing-library/foo';
27+
28+
async () => {
29+
await testingLibrary.waitForElementToBeRemoved(() => {});
30+
}`,
31+
},
2532
{
2633
code: `import { render } from '@testing-library/foo';
2734
import { waitForSomethingElse } from 'other-module';
@@ -30,6 +37,13 @@ ruleTester.run('prefer-wait-for', rule, {
3037
await waitForSomethingElse(() => {});
3138
}`,
3239
},
40+
{
41+
code: `import * as testingLibrary from '@testing-library/foo';
42+
43+
async () => {
44+
await testingLibrary.waitFor(() => {}, { timeout: 500 });
45+
}`,
46+
},
3347
],
3448

3549
invalid: [
@@ -57,6 +71,46 @@ ruleTester.run('prefer-wait-for', rule, {
5771
await waitFor(() => {});
5872
}`,
5973
},
74+
// namespaced wait should be fixed but not its import
75+
{
76+
code: `import * as testingLibrary from '@testing-library/foo';
77+
78+
async () => {
79+
await testingLibrary.wait();
80+
}`,
81+
errors: [
82+
{
83+
messageId: 'preferWaitForMethod',
84+
line: 4,
85+
column: 30,
86+
},
87+
],
88+
output: `import * as testingLibrary from '@testing-library/foo';
89+
90+
async () => {
91+
await testingLibrary.waitFor(() => {});
92+
}`,
93+
},
94+
// namespaced waitForDomChange should be fixed but not its import
95+
{
96+
code: `import * as testingLibrary from '@testing-library/foo';
97+
98+
async () => {
99+
await testingLibrary.waitForDomChange({ timeout: 500 });
100+
}`,
101+
errors: [
102+
{
103+
messageId: 'preferWaitForMethod',
104+
line: 4,
105+
column: 30,
106+
},
107+
],
108+
output: `import * as testingLibrary from '@testing-library/foo';
109+
110+
async () => {
111+
await testingLibrary.waitFor(() => {}, { timeout: 500 });
112+
}`,
113+
},
60114
{
61115
// this import doesn't have trailing semicolon but fixer adds it
62116
code: `import { render, wait } from '@testing-library/foo'

0 commit comments

Comments
 (0)