Skip to content

Commit 461809f

Browse files
committed
Merge remote-tracking branch 'origin/v4' into v4
2 parents 2588602 + 65028a5 commit 461809f

8 files changed

+231
-94
lines changed

docs/rules/no-container.md

-6
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ render(<Example />);
3232
screen.getByRole('button', { name: /click me/i });
3333
```
3434

35-
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.
36-
37-
```
38-
"testing-library/no-container": ["error", {"renderFunctions":["renderWithRedux", "renderWithRouter"]}],
39-
```
40-
4135
## Further Reading
4236

4337
- [about the `container` element](https://testing-library.com/docs/react-testing-library/api#container-1)

lib/detect-testing-library-utils.ts

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@typescript-eslint/experimental-utils';
66
import {
77
getAssertNodeInfo,
8+
getDeepestIdentifierNode,
89
getImportModuleName,
910
getPropertyIdentifierNode,
1011
getReferenceNode,
@@ -69,6 +70,9 @@ type IsAsyncUtilFn = (
6970
) => boolean;
7071
type IsFireEventMethodFn = (node: TSESTree.Identifier) => boolean;
7172
type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean;
73+
type IsRenderVariableDeclaratorFn = (
74+
node: TSESTree.VariableDeclarator
75+
) => boolean;
7276
type IsDebugUtilFn = (node: TSESTree.Identifier) => boolean;
7377
type IsPresenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
7478
type IsAbsenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
@@ -97,6 +101,7 @@ export interface DetectionHelpers {
97101
isAsyncUtil: IsAsyncUtilFn;
98102
isFireEventMethod: IsFireEventMethodFn;
99103
isRenderUtil: IsRenderUtilFn;
104+
isRenderVariableDeclarator: IsRenderVariableDeclaratorFn;
100105
isDebugUtil: IsDebugUtilFn;
101106
isPresenceAssert: IsPresenceAssertFn;
102107
isAbsenceAssert: IsAbsenceAssertFn;
@@ -149,6 +154,10 @@ export function detectTestingLibraryUtils<
149154
originalNodeName?: string
150155
) => boolean
151156
): boolean {
157+
if (!node) {
158+
return false;
159+
}
160+
152161
const referenceNode = getReferenceNode(node);
153162
const referenceNodeIdentifier = getPropertyIdentifierNode(referenceNode);
154163
const importedUtilSpecifier = getImportedUtilSpecifier(
@@ -408,6 +417,12 @@ export function detectTestingLibraryUtils<
408417
);
409418
};
410419

420+
const isRenderVariableDeclarator: IsRenderVariableDeclaratorFn = (node) => {
421+
const initIdentifierNode = getDeepestIdentifierNode(node.init);
422+
423+
return isRenderUtil(initIdentifierNode);
424+
};
425+
411426
const isDebugUtil: IsDebugUtilFn = (node) => {
412427
return isTestingLibraryUtil(
413428
node,
@@ -562,6 +577,7 @@ export function detectTestingLibraryUtils<
562577
isAsyncUtil,
563578
isFireEventMethod,
564579
isRenderUtil,
580+
isRenderVariableDeclarator,
565581
isDebugUtil,
566582
isPresenceAssert,
567583
isAbsenceAssert,

lib/node-utils.ts

-25
Original file line numberDiff line numberDiff line change
@@ -477,31 +477,6 @@ export function isRenderFunction(
477477
});
478478
}
479479

480-
// TODO: should be removed after v4 is finished
481-
export function isRenderVariableDeclarator(
482-
node: TSESTree.VariableDeclarator,
483-
renderFunctions: string[]
484-
): boolean {
485-
if (node.init) {
486-
if (ASTUtils.isAwaitExpression(node.init)) {
487-
return (
488-
node.init.argument &&
489-
isRenderFunction(
490-
node.init.argument as TSESTree.CallExpression,
491-
renderFunctions
492-
)
493-
);
494-
} else {
495-
return (
496-
isCallExpression(node.init) &&
497-
isRenderFunction(node.init, renderFunctions)
498-
);
499-
}
500-
}
501-
502-
return false;
503-
}
504-
505480
// TODO: extract into types file?
506481
export type ImportModuleNode =
507482
| TSESTree.ImportDeclaration

lib/rules/no-container.ts

+77-57
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1+
import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils';
12
import {
2-
ESLintUtils,
3-
TSESTree,
4-
ASTUtils,
5-
} from '@typescript-eslint/experimental-utils';
6-
import { getDocsUrl } from '../utils';
7-
import {
3+
getDeepestIdentifierNode,
4+
getFunctionName,
5+
getInnermostReturningFunction,
86
isMemberExpression,
97
isObjectPattern,
108
isProperty,
11-
isRenderVariableDeclarator,
129
} from '../node-utils';
10+
import { createTestingLibraryRule } from '../create-testing-library-rule';
1311

1412
export const RULE_NAME = 'no-container';
1513
export type MessageIds = 'noContainer';
16-
type Options = [{ renderFunctions?: string[] }];
14+
type Options = [];
1715

18-
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
16+
export default createTestingLibraryRule<Options, MessageIds>({
1917
name: RULE_NAME,
2018
meta: {
2119
type: 'problem',
@@ -29,48 +27,52 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
2927
'Avoid using container methods. Prefer using the methods from Testing Library, such as "getByRole()"',
3028
},
3129
fixable: null,
32-
schema: [
33-
{
34-
type: 'object',
35-
properties: {
36-
renderFunctions: {
37-
type: 'array',
38-
},
39-
},
40-
},
41-
],
30+
schema: [],
4231
},
43-
defaultOptions: [
44-
{
45-
renderFunctions: [],
46-
},
47-
],
32+
defaultOptions: [],
4833

49-
create(context, [options]) {
50-
const { renderFunctions } = options;
34+
create(context, [], helpers) {
5135
const destructuredContainerPropNames: string[] = [];
52-
let renderWrapperName: string = null;
36+
const renderWrapperNames: string[] = [];
37+
let renderResultVarName: string = null;
5338
let containerName: string = null;
5439
let containerCallsMethod = false;
5540

41+
function detectRenderWrapper(node: TSESTree.Identifier): void {
42+
const innerFunction = getInnermostReturningFunction(context, node);
43+
44+
if (innerFunction) {
45+
renderWrapperNames.push(getFunctionName(innerFunction));
46+
}
47+
}
48+
5649
function showErrorIfChainedContainerMethod(
5750
innerNode: TSESTree.MemberExpression
5851
) {
5952
if (isMemberExpression(innerNode)) {
6053
if (ASTUtils.isIdentifier(innerNode.object)) {
6154
const isContainerName = innerNode.object.name === containerName;
62-
const isRenderWrapper = innerNode.object.name === renderWrapperName;
6355

56+
if (isContainerName) {
57+
context.report({
58+
node: innerNode,
59+
messageId: 'noContainer',
60+
});
61+
return;
62+
}
63+
64+
const isRenderWrapper = innerNode.object.name === renderResultVarName;
6465
containerCallsMethod =
6566
ASTUtils.isIdentifier(innerNode.property) &&
6667
innerNode.property.name === 'container' &&
6768
isRenderWrapper;
6869

69-
if (isContainerName || containerCallsMethod) {
70+
if (containerCallsMethod) {
7071
context.report({
71-
node: innerNode,
72+
node: innerNode.property,
7273
messageId: 'noContainer',
7374
});
75+
return;
7476
}
7577
}
7678
showErrorIfChainedContainerMethod(
@@ -80,35 +82,12 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
8082
}
8183

8284
return {
83-
VariableDeclarator(node) {
84-
if (isRenderVariableDeclarator(node, ['render', ...renderFunctions])) {
85-
if (isObjectPattern(node.id)) {
86-
const containerIndex = node.id.properties.findIndex(
87-
(property) =>
88-
isProperty(property) &&
89-
ASTUtils.isIdentifier(property.key) &&
90-
property.key.name === 'container'
91-
);
92-
const nodeValue =
93-
containerIndex !== -1 && node.id.properties[containerIndex].value;
94-
if (ASTUtils.isIdentifier(nodeValue)) {
95-
containerName = nodeValue.name;
96-
} else {
97-
isObjectPattern(nodeValue) &&
98-
nodeValue.properties.forEach(
99-
(property) =>
100-
isProperty(property) &&
101-
ASTUtils.isIdentifier(property.key) &&
102-
destructuredContainerPropNames.push(property.key.name)
103-
);
104-
}
105-
} else {
106-
renderWrapperName = ASTUtils.isIdentifier(node.id) && node.id.name;
107-
}
85+
CallExpression(node) {
86+
const callExpressionIdentifier = getDeepestIdentifierNode(node);
87+
if (helpers.isRenderUtil(callExpressionIdentifier)) {
88+
detectRenderWrapper(callExpressionIdentifier);
10889
}
109-
},
11090

111-
CallExpression(node) {
11291
if (isMemberExpression(node.callee)) {
11392
showErrorIfChainedContainerMethod(node.callee);
11493
} else {
@@ -120,6 +99,47 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
12099
});
121100
}
122101
},
102+
103+
VariableDeclarator(node) {
104+
const initIdentifierNode = getDeepestIdentifierNode(node.init);
105+
106+
const isRenderWrapperVariableDeclarator = initIdentifierNode
107+
? renderWrapperNames.includes(initIdentifierNode.name)
108+
: false;
109+
110+
if (
111+
!helpers.isRenderVariableDeclarator(node) &&
112+
!isRenderWrapperVariableDeclarator
113+
) {
114+
return;
115+
}
116+
117+
if (isObjectPattern(node.id)) {
118+
const containerIndex = node.id.properties.findIndex(
119+
(property) =>
120+
isProperty(property) &&
121+
ASTUtils.isIdentifier(property.key) &&
122+
property.key.name === 'container'
123+
);
124+
125+
const nodeValue =
126+
containerIndex !== -1 && node.id.properties[containerIndex].value;
127+
128+
if (ASTUtils.isIdentifier(nodeValue)) {
129+
containerName = nodeValue.name;
130+
} else {
131+
isObjectPattern(nodeValue) &&
132+
nodeValue.properties.forEach(
133+
(property) =>
134+
isProperty(property) &&
135+
ASTUtils.isIdentifier(property.key) &&
136+
destructuredContainerPropNames.push(property.key.name)
137+
);
138+
}
139+
} else {
140+
renderResultVarName = ASTUtils.isIdentifier(node.id) && node.id.name;
141+
}
142+
},
123143
};
124144
},
125145
});

lib/rules/no-debug.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
22
getDeepestIdentifierNode,
3+
getFunctionName,
4+
getInnermostReturningFunction,
35
getPropertyIdentifierNode,
46
getReferenceNode,
57
isObjectPattern,
@@ -41,12 +43,28 @@ export default createTestingLibraryRule<Options, MessageIds>({
4143
create(context, [], helpers) {
4244
const suspiciousDebugVariableNames: string[] = [];
4345
const suspiciousReferenceNodes: TSESTree.Identifier[] = [];
46+
const renderWrapperNames: string[] = [];
47+
48+
function detectRenderWrapper(node: TSESTree.Identifier): void {
49+
const innerFunction = getInnermostReturningFunction(context, node);
50+
51+
if (innerFunction) {
52+
renderWrapperNames.push(getFunctionName(innerFunction));
53+
}
54+
}
4455

4556
return {
4657
VariableDeclarator(node) {
4758
const initIdentifierNode = getDeepestIdentifierNode(node.init);
4859

49-
if (!helpers.isRenderUtil(initIdentifierNode)) {
60+
const isRenderWrapperVariableDeclarator = initIdentifierNode
61+
? renderWrapperNames.includes(initIdentifierNode.name)
62+
: false;
63+
64+
if (
65+
!helpers.isRenderVariableDeclarator(node) &&
66+
!isRenderWrapperVariableDeclarator
67+
) {
5068
return;
5169
}
5270

@@ -74,6 +92,10 @@ export default createTestingLibraryRule<Options, MessageIds>({
7492
},
7593
CallExpression(node) {
7694
const callExpressionIdentifier = getDeepestIdentifierNode(node);
95+
if (helpers.isRenderUtil(callExpressionIdentifier)) {
96+
detectRenderWrapper(callExpressionIdentifier);
97+
}
98+
7799
const referenceNode = getReferenceNode(node);
78100
const referenceIdentifier = getPropertyIdentifierNode(referenceNode);
79101

lib/rules/render-result-naming-convention.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
5959
}
6060

6161
if (
62-
!helpers.isRenderUtil(initIdentifierNode) &&
62+
!helpers.isRenderVariableDeclarator(node) &&
6363
!renderWrapperNames.includes(initIdentifierNode.name)
6464
) {
6565
return;

0 commit comments

Comments
 (0)