Skip to content

Commit 5a2d128

Browse files
committed
refactor(render-result-naming-convention): first approach for new helper
First implementation of isRenderUtil helper, and use it within this rule.
1 parent 8684ab8 commit 5a2d128

File tree

3 files changed

+52
-132
lines changed

3 files changed

+52
-132
lines changed

lib/detect-testing-library-utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@typescript-eslint/experimental-utils';
66
import {
77
getAssertNodeInfo,
8+
getIdentifierNode,
89
getImportModuleName,
910
ImportModuleNode,
1011
isImportDeclaration,
@@ -24,6 +25,7 @@ import {
2425
export type TestingLibrarySettings = {
2526
'testing-library/module'?: string;
2627
'testing-library/filename-pattern'?: string;
28+
'testing-library/custom-renders'?: string[];
2729
};
2830

2931
export type TestingLibraryContext<
@@ -60,6 +62,7 @@ export type DetectionHelpers = {
6062
isCustomQuery: (node: TSESTree.Identifier) => boolean;
6163
isAsyncUtil: (node: TSESTree.Identifier) => boolean;
6264
isFireEventMethod: (node: TSESTree.Identifier) => boolean;
65+
isRenderUtil: (node: TSESTree.Node) => boolean;
6366
isPresenceAssert: (node: TSESTree.MemberExpression) => boolean;
6467
isAbsenceAssert: (node: TSESTree.MemberExpression) => boolean;
6568
canReportErrors: () => boolean;
@@ -95,6 +98,7 @@ export function detectTestingLibraryUtils<
9598
const filenamePattern =
9699
context.settings['testing-library/filename-pattern'] ??
97100
DEFAULT_FILENAME_PATTERN;
101+
const customRenders = context.settings['testing-library/custom-renders'];
98102

99103
/**
100104
* Determines whether aggressive reporting is enabled or not.
@@ -105,6 +109,12 @@ export function detectTestingLibraryUtils<
105109
*/
106110
const isAggressiveReportingEnabled = () => !customModule;
107111

112+
/**
113+
* TODO
114+
*/
115+
const isAggressiveRenderEnabled = () =>
116+
!Array.isArray(customRenders) || customRenders.length === 0;
117+
108118
// Helpers for Testing Library detection.
109119
const getTestingLibraryImportNode: DetectionHelpers['getTestingLibraryImportNode'] = () => {
110120
return importedTestingLibraryNode;
@@ -256,6 +266,24 @@ export function detectTestingLibraryUtils<
256266
return regularCall || wildcardCall;
257267
};
258268

269+
/**
270+
* TODO
271+
*/
272+
const isRenderUtil: DetectionHelpers['isRenderUtil'] = (node) => {
273+
const identifier = getIdentifierNode(node);
274+
275+
if (!identifier) {
276+
return false;
277+
}
278+
279+
if (isAggressiveRenderEnabled()) {
280+
// TODO: should this be always true?
281+
return identifier.name.toLowerCase().startsWith('render');
282+
}
283+
284+
return ['render', ...customRenders].includes(identifier.name);
285+
};
286+
259287
/**
260288
* Determines whether a given MemberExpression node is a presence assert
261289
*
@@ -376,6 +404,7 @@ export function detectTestingLibraryUtils<
376404
isCustomQuery,
377405
isAsyncUtil,
378406
isFireEventMethod,
407+
isRenderUtil,
379408
isPresenceAssert,
380409
isAbsenceAssert,
381410
canReportErrors,

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

Lines changed: 13 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
1-
import {
2-
ESLintUtils,
3-
TSESTree,
4-
ASTUtils,
5-
} from '@typescript-eslint/experimental-utils';
6-
import { getDocsUrl, hasTestingLibraryImportModule } from '../utils';
7-
import {
8-
isCallExpression,
9-
isImportSpecifier,
10-
isMemberExpression,
11-
isObjectPattern,
12-
isRenderVariableDeclarator,
13-
} from '../node-utils';
1+
import { createTestingLibraryRule } from '../create-testing-library-rule';
2+
import { isObjectPattern } from '../node-utils';
3+
import { ASTUtils } from '@typescript-eslint/experimental-utils';
144

155
export const RULE_NAME = 'render-result-naming-convention';
166
export type MessageIds = 'renderResultNamingConvention';
177

18-
// TODO: remove renderFunctions option first, and then move it to ESLint settings
19-
type Options = [{ renderFunctions?: string[] }];
8+
type Options = [];
209

2110
const ALLOWED_VAR_NAMES = ['view', 'utils'];
2211
const ALLOWED_VAR_NAMES_TEXT = ALLOWED_VAR_NAMES.map(
2312
(name) => `\`${name}\``
2413
).join(', ');
2514

26-
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
15+
export default createTestingLibraryRule<Options, MessageIds>({
2716
name: RULE_NAME,
2817
meta: {
2918
type: 'suggestion',
@@ -33,109 +22,27 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
3322
recommended: false,
3423
},
3524
messages: {
36-
renderResultNamingConvention: `\`{{ varName }}\` is not a recommended name for \`render\` returned value. Instead, you should destructure it, or call it using one of the valid choices: ${ALLOWED_VAR_NAMES_TEXT}`,
25+
renderResultNamingConvention: `\`{{ renderResultName }}\` is not a recommended name for \`render\` returned value. Instead, you should destructure it, or name it using one of: ${ALLOWED_VAR_NAMES_TEXT}`,
3726
},
3827
fixable: null,
39-
schema: [
40-
{
41-
type: 'object',
42-
properties: {
43-
renderFunctions: {
44-
type: 'array',
45-
},
46-
},
47-
},
48-
],
28+
schema: [],
4929
},
50-
defaultOptions: [
51-
{
52-
renderFunctions: [],
53-
},
54-
],
55-
56-
create(context, [options]) {
57-
const { renderFunctions } = options;
58-
let renderAlias: string | undefined;
59-
let wildcardImportName: string | undefined;
30+
defaultOptions: [],
6031

32+
create(context, _, helpers) {
6133
return {
62-
// TODO: this can be removed
63-
// check named imports
64-
ImportDeclaration(node: TSESTree.ImportDeclaration) {
65-
if (!hasTestingLibraryImportModule(node)) {
34+
VariableDeclarator(node) {
35+
if (!helpers.isRenderUtil(node.init)) {
6636
return;
6737
}
68-
const renderImport = node.specifiers.find(
69-
(node) => isImportSpecifier(node) && node.imported.name === 'render'
70-
);
7138

72-
if (!renderImport) {
73-
return;
74-
}
75-
76-
renderAlias = renderImport.local.name;
77-
},
78-
// TODO: this can be removed
79-
// check wildcard imports
80-
'ImportDeclaration ImportNamespaceSpecifier'(
81-
node: TSESTree.ImportNamespaceSpecifier
82-
) {
83-
if (
84-
!hasTestingLibraryImportModule(
85-
node.parent as TSESTree.ImportDeclaration
86-
)
87-
) {
88-
return;
89-
}
90-
91-
wildcardImportName = node.local.name;
92-
},
93-
VariableDeclarator(node: TSESTree.VariableDeclarator) {
9439
// check if destructuring return value from render
9540
if (isObjectPattern(node.id)) {
9641
return;
9742
}
9843

99-
// TODO: call `helpers.isRender` with the node.init
100-
// this ini could be Identifier (render) or MemberExpression (rtl.render)
101-
const isValidRenderDeclarator = isRenderVariableDeclarator(node, [
102-
...renderFunctions,
103-
renderAlias,
104-
]);
105-
106-
// TODO: After this point, most of the checks should be removed
107-
const isValidWildcardImport = !!wildcardImportName;
108-
109-
// check if is a Testing Library related import
110-
if (!isValidRenderDeclarator && !isValidWildcardImport) {
111-
return;
112-
}
113-
114-
const renderFunctionName =
115-
isCallExpression(node.init) &&
116-
ASTUtils.isIdentifier(node.init.callee) &&
117-
node.init.callee.name;
118-
119-
const renderFunctionObjectName =
120-
isCallExpression(node.init) &&
121-
isMemberExpression(node.init.callee) &&
122-
ASTUtils.isIdentifier(node.init.callee.property) &&
123-
ASTUtils.isIdentifier(node.init.callee.object) &&
124-
node.init.callee.property.name === 'render' &&
125-
node.init.callee.object.name;
126-
127-
const isRenderAlias = !!renderAlias;
128-
const isCustomRender = renderFunctions.includes(renderFunctionName);
129-
const isWildCardRender =
130-
renderFunctionObjectName &&
131-
renderFunctionObjectName === wildcardImportName;
132-
133-
// check if is a qualified render function
134-
if (!isRenderAlias && !isCustomRender && !isWildCardRender) {
135-
return;
136-
}
137-
13844
const renderResultName = ASTUtils.isIdentifier(node.id) && node.id.name;
45+
13946
const isAllowedRenderResultName = ALLOWED_VAR_NAMES.includes(
14047
renderResultName
14148
);
@@ -149,7 +56,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
14956
node,
15057
messageId: 'renderResultNamingConvention',
15158
data: {
152-
varName: renderResultName,
59+
renderResultName,
15360
},
15461
});
15562
},

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

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,7 @@ ruleTester.run(RULE_NAME, rule, {
9393
const button = screen.getByText('some button');
9494
});
9595
`,
96-
options: [
97-
{
98-
renderFunctions: ['customRender'],
99-
},
100-
],
96+
settings: { 'testing-library/custom-renders': ['customRender'] },
10197
},
10298
{
10399
code: `
@@ -108,11 +104,7 @@ ruleTester.run(RULE_NAME, rule, {
108104
await view.findByRole('button');
109105
});
110106
`,
111-
options: [
112-
{
113-
renderFunctions: ['customRender'],
114-
},
115-
],
107+
settings: { 'testing-library/custom-renders': ['customRender'] },
116108
},
117109
{
118110
code: `
@@ -123,11 +115,7 @@ ruleTester.run(RULE_NAME, rule, {
123115
await utils.findByRole('button');
124116
});
125117
`,
126-
options: [
127-
{
128-
renderFunctions: ['customRender'],
129-
},
130-
],
118+
settings: { 'testing-library/custom-renders': ['customRender'] },
131119
},
132120
{
133121
code: `
@@ -203,7 +191,7 @@ ruleTester.run(RULE_NAME, rule, {
203191
{
204192
messageId: 'renderResultNamingConvention',
205193
data: {
206-
varName: 'wrapper',
194+
renderResultName: 'wrapper',
207195
},
208196
line: 5,
209197
column: 17,
@@ -223,7 +211,7 @@ ruleTester.run(RULE_NAME, rule, {
223211
{
224212
messageId: 'renderResultNamingConvention',
225213
data: {
226-
varName: 'wrapper',
214+
renderResultName: 'wrapper',
227215
},
228216
line: 5,
229217
column: 17,
@@ -243,7 +231,7 @@ ruleTester.run(RULE_NAME, rule, {
243231
{
244232
messageId: 'renderResultNamingConvention',
245233
data: {
246-
varName: 'component',
234+
renderResultName: 'component',
247235
},
248236
line: 5,
249237
column: 17,
@@ -280,7 +268,7 @@ ruleTester.run(RULE_NAME, rule, {
280268
{
281269
messageId: 'renderResultNamingConvention',
282270
data: {
283-
varName: 'wrapper',
271+
renderResultName: 'wrapper',
284272
},
285273
line: 5,
286274
column: 17,
@@ -307,7 +295,7 @@ ruleTester.run(RULE_NAME, rule, {
307295
{
308296
messageId: 'renderResultNamingConvention',
309297
data: {
310-
varName: 'wrapper',
298+
renderResultName: 'wrapper',
311299
},
312300
line: 6,
313301
column: 17,
@@ -323,16 +311,12 @@ ruleTester.run(RULE_NAME, rule, {
323311
const button = wrapper.getByText('some button');
324312
});
325313
`,
326-
options: [
327-
{
328-
renderFunctions: ['customRender'],
329-
},
330-
],
314+
settings: { 'testing-library/custom-renders': ['customRender'] },
331315
errors: [
332316
{
333317
messageId: 'renderResultNamingConvention',
334318
data: {
335-
varName: 'wrapper',
319+
renderResultName: 'wrapper',
336320
},
337321
line: 5,
338322
column: 17,

0 commit comments

Comments
 (0)