From d8c5ee1ffc6e5dc23936acecf44c384cb30589cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 30 Jun 2020 22:14:02 +0200 Subject: [PATCH 01/18] feat: rule skeleton --- lib/index.ts | 5 +++ lib/rules/render-result-naming-convention.ts | 32 ++++++++++++++++++++ tests/__snapshots__/index.test.ts.snap | 3 ++ 3 files changed, 40 insertions(+) create mode 100644 lib/rules/render-result-naming-convention.ts diff --git a/lib/index.ts b/lib/index.ts index 81390a91..ad9bfca7 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -17,6 +17,7 @@ import preferUserEvent from './rules/prefer-user-event'; import preferWaitFor from './rules/prefer-wait-for'; import noMultipleAssertionsWaitFor from './rules/no-multiple-assertions-wait-for' import preferFindBy from './rules/prefer-find-by'; +import renderResultNamingConvention from './rules/render-result-naming-convention'; const rules = { 'await-async-query': awaitAsyncQuery, @@ -38,6 +39,7 @@ const rules = { 'prefer-screen-queries': preferScreenQueries, 'prefer-user-event': preferUserEvent, 'prefer-wait-for': preferWaitFor, + 'render-result-naming-convention': renderResultNamingConvention, }; const domRules = { @@ -57,6 +59,7 @@ const angularRules = { 'testing-library/no-debug': 'warn', 'testing-library/no-dom-import': ['error', 'angular'], 'testing-library/no-node-access': 'error', + 'testing-library/render-result-naming-convention': 'error', }; const reactRules = { @@ -65,6 +68,7 @@ const reactRules = { 'testing-library/no-debug': 'warn', 'testing-library/no-dom-import': ['error', 'react'], 'testing-library/no-node-access': 'error', + 'testing-library/render-result-naming-convention': 'error', }; const vueRules = { @@ -74,6 +78,7 @@ const vueRules = { 'testing-library/no-debug': 'warn', 'testing-library/no-dom-import': ['error', 'vue'], 'testing-library/no-node-access': 'error', + 'testing-library/render-result-naming-convention': 'error', }; export = { diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts new file mode 100644 index 00000000..daff9271 --- /dev/null +++ b/lib/rules/render-result-naming-convention.ts @@ -0,0 +1,32 @@ +import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; +import { getDocsUrl } from '../utils'; + +export const RULE_NAME = 'render-result-naming-convention'; +type MessageIds = 'renderResultNamingConvention'; +type Options = []; + +const ALLOWED_VAR_NAMES = ['view', 'utils']; + +export default ESLintUtils.RuleCreator(getDocsUrl)({ + name: RULE_NAME, + meta: { + type: 'suggestion', + docs: { + description: 'TODO', + category: 'Best Practices', + recommended: false, + }, + messages: { + 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.map( + name => `\`${name}\`` + ).join(', ')}`, + }, + fixable: null, + schema: [], + }, + defaultOptions: [], + + create() { + return {}; + }, +}); diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index da608e52..1f425687 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -21,6 +21,7 @@ Object { "testing-library/prefer-find-by": "error", "testing-library/prefer-screen-queries": "error", "testing-library/prefer-user-event": "warn", + "testing-library/render-result-naming-convention": "error", }, } `; @@ -64,6 +65,7 @@ Object { "testing-library/prefer-find-by": "error", "testing-library/prefer-screen-queries": "error", "testing-library/prefer-user-event": "warn", + "testing-library/render-result-naming-convention": "error", }, } `; @@ -90,6 +92,7 @@ Object { "testing-library/prefer-find-by": "error", "testing-library/prefer-screen-queries": "error", "testing-library/prefer-user-event": "warn", + "testing-library/render-result-naming-convention": "error", }, } `; From fe4be397386e8127fe0c69865529544f04f72261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 30 Jun 2020 22:25:56 +0200 Subject: [PATCH 02/18] test: first round --- docs/rules/render-result-naming-convention.md | 1 + .../render-result-naming-convention.test.ts | 202 ++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 docs/rules/render-result-naming-convention.md create mode 100644 tests/lib/rules/render-result-naming-convention.test.ts diff --git a/docs/rules/render-result-naming-convention.md b/docs/rules/render-result-naming-convention.md new file mode 100644 index 00000000..1333ed77 --- /dev/null +++ b/docs/rules/render-result-naming-convention.md @@ -0,0 +1 @@ +TODO diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts new file mode 100644 index 00000000..d2c50933 --- /dev/null +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -0,0 +1,202 @@ +import { createRuleTester } from '../test-utils'; +import rule, { + RULE_NAME, +} from '../../../lib/rules/render-result-naming-convention'; + +const ruleTester = createRuleTester({ + ecmaFeatures: { + jsx: true, + }, +}); + +ruleTester.run(RULE_NAME, rule, { + valid: [ + { + code: ` + import { render } from '@testing-library/react'; + + test('should not report straight destructured render result', () => { + const { rerender, getByText } = render(); + const button = getByText('some button'); + }); + `, + }, + { + code: ` + import { render } from '@testing-library/react'; + + test('should not report straight render result called "utils"', async () => { + const utils = render(); + await utils.findByRole('button'); + }); + `, + }, + { + code: ` + import { render } from '@testing-library/react'; + + test('should not report straight render result called "view"', async () => { + const view = render(); + await view.findByRole('button'); + }); + `, + }, + { + code: ` + import { render } from '@testing-library/react'; + + const setup = () => render(); + + test('should not report destructured render result from wrapping function', () => { + const { rerender, getByText } = setup(); + const button = getByText('some button'); + }); + `, + }, + { + code: ` + import { render } from '@testing-library/react'; + + const setup = () => render(); + + test('should not report render result called "utils" from wrapping function', async () => { + const utils = setup(); + await utils.findByRole('button'); + }); + `, + }, + { + code: ` + import { render } from '@testing-library/react'; + + const setup = () => render(); + + test('should not report render result called "view" from wrapping function', async () => { + const view = setup(); + await view.findByRole('button'); + }); + `, + }, + { + code: ` + import { render } from '@foo/bar'; + + test('should not report from render not related to testing library', () => { + const wrapper = render(); + const button = wrapper.getByText('some button'); + }); + `, + }, + { + code: ` + import { render } from '@foo/bar'; + + test('should not report from render not imported from testing library', () => { + const wrapper = render(); + const button = wrapper.getByText('some button'); + }); + `, + }, + { + code: ` + function render() { + return 'whatever'; + } + + test('should not report from custom render not related to testing library', () => { + const wrapper = render(); + const button = wrapper.getByText('some button'); + }); + `, + }, + ], + invalid: [ + { + code: ` + import { render } from '@testing-library/react'; + + test('should report straight render result called "wrapper"', async () => { + const wrapper = render(); + await wrapper.findByRole('button'); + }); + `, + errors: [ + { + messageId: 'renderResultNamingConvention', + line: 4, + column: 17, + }, + ], + }, + { + code: ` + import { render } from '@testing-library/react'; + + test('should report straight render result called "component"', async () => { + const component = render(); + await component.findByRole('button'); + }); + `, + errors: [ + { + messageId: 'renderResultNamingConvention', + line: 4, + column: 17, + }, + ], + }, + { + code: ` + import { render } from '@testing-library/react'; + + test('should report straight render result called "notValidName"', async () => { + const notValidName = render(); + await notValidName.findByRole('button'); + }); + `, + errors: [ + { + messageId: 'renderResultNamingConvention', + line: 4, + column: 17, + }, + ], + }, + { + code: ` + import { render as testingLibraryRender } from '@testing-library/react'; + + test('should report renamed render result called "wrapper"', async () => { + const wrapper = testingLibraryRender(); + await wrapper.findByRole('button'); + }); + `, + errors: [ + { + messageId: 'renderResultNamingConvention', + line: 4, + column: 17, + }, + ], + }, + { + code: ` + import { render } from '@testing-library/react'; + + const setup = () => render(); + + test('should report render result called "wrapper" from wrapping function', async () => { + const wrapper = setup(); + await wrapper.findByRole('button'); + }); + `, + errors: [ + { + messageId: 'renderResultNamingConvention', + line: 4, + column: 17, + }, + ], + }, + ], +}); From 1ed0defd5d9e12c63cdf84adc86598b70585372f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 1 Jul 2020 21:40:54 +0200 Subject: [PATCH 03/18] feat: rule implementation round 1 --- lib/index.ts | 6 +- lib/rules/render-result-naming-convention.ts | 54 +++++++++++++++--- .../render-result-naming-convention.test.ts | 55 +++++++++++++++---- 3 files changed, 94 insertions(+), 21 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index ad9bfca7..d9aa98c7 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -15,9 +15,9 @@ import preferPresenceQueries from './rules/prefer-presence-queries'; import preferScreenQueries from './rules/prefer-screen-queries'; import preferUserEvent from './rules/prefer-user-event'; import preferWaitFor from './rules/prefer-wait-for'; -import noMultipleAssertionsWaitFor from './rules/no-multiple-assertions-wait-for' +import noMultipleAssertionsWaitFor from './rules/no-multiple-assertions-wait-for'; import preferFindBy from './rules/prefer-find-by'; -import renderResultNamingConvention from './rules/render-result-naming-convention'; +import invalidRenderResultName from './rules/render-result-naming-convention'; const rules = { 'await-async-query': awaitAsyncQuery, @@ -39,7 +39,7 @@ const rules = { 'prefer-screen-queries': preferScreenQueries, 'prefer-user-event': preferUserEvent, 'prefer-wait-for': preferWaitFor, - 'render-result-naming-convention': renderResultNamingConvention, + 'render-result-naming-convention': invalidRenderResultName, }; const domRules = { diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index daff9271..811d4fda 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -1,13 +1,16 @@ import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; import { getDocsUrl } from '../utils'; +import { + isIdentifier, + isObjectPattern, + isRenderVariableDeclarator, +} from '../node-utils'; export const RULE_NAME = 'render-result-naming-convention'; -type MessageIds = 'renderResultNamingConvention'; -type Options = []; const ALLOWED_VAR_NAMES = ['view', 'utils']; -export default ESLintUtils.RuleCreator(getDocsUrl)({ +export default ESLintUtils.RuleCreator(getDocsUrl)({ name: RULE_NAME, meta: { type: 'suggestion', @@ -17,16 +20,51 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ recommended: false, }, messages: { - 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.map( + invalidRenderResultName: `\`{{ 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.map( name => `\`${name}\`` ).join(', ')}`, }, fixable: null, - schema: [], + schema: [ + { + type: 'object', + properties: { + renderFunctions: { + type: 'array', + }, + }, + }, + ], }, - defaultOptions: [], + defaultOptions: [ + { + renderFunctions: [], + }, + ], + + create(context, [options]) { + const { renderFunctions } = options; + let renderResultName = null; + + return { + VariableDeclarator(node) { + if ( + isRenderVariableDeclarator(node, renderFunctions) && + !isObjectPattern(node.id) + ) { + renderResultName = isIdentifier(node.id) && node.id.name; - create() { - return {}; + if (!ALLOWED_VAR_NAMES.includes(renderResultName)) { + context.report({ + node, + messageId: 'invalidRenderResultName', + data: { + varName: renderResultName, + }, + }); + } + } + }, + }; }, }); diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index d2c50933..a6d51ab9 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -122,8 +122,11 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: 'renderResultNamingConvention', - line: 4, + messageId: 'invalidRenderResultName', + data: { + varName: 'wrapper', + }, + line: 5, column: 17, }, ], @@ -139,8 +142,11 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: 'renderResultNamingConvention', - line: 4, + messageId: 'invalidRenderResultName', + data: { + varName: 'component', + }, + line: 5, column: 17, }, ], @@ -156,8 +162,8 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: 'renderResultNamingConvention', - line: 4, + messageId: 'invalidRenderResultName', + line: 5, column: 17, }, ], @@ -173,8 +179,11 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: 'renderResultNamingConvention', - line: 4, + messageId: 'invalidRenderResultName', + data: { + varName: 'wrapper', + }, + line: 5, column: 17, }, ], @@ -192,8 +201,34 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: 'renderResultNamingConvention', - line: 4, + messageId: 'invalidRenderResultName', + data: { + varName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + }, + { + code: ` + test('should report from custom render function ', () => { + const wrapper = renderWithSomethingElse(); + const button = wrapper.getByText('some button'); + }); + `, + options: [ + { + renderFunctions: ['renderWithSomethingElse'], + }, + ], + errors: [ + { + messageId: 'invalidRenderResultName', + data: { + varName: 'wrapper', + }, + line: 2, column: 17, }, ], From ce61d56811d0590f51c098b6328765a8734c005a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 15 Jul 2020 18:27:23 +0200 Subject: [PATCH 04/18] refactor: move hasTestingLibraryImportModule --- lib/rules/render-result-naming-convention.ts | 7 ++++--- lib/utils.ts | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index 811d4fda..1bf2f87e 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -9,6 +9,9 @@ import { export const RULE_NAME = 'render-result-naming-convention'; const ALLOWED_VAR_NAMES = ['view', 'utils']; +const ALLOWED_VAR_NAMES_TEXT = ALLOWED_VAR_NAMES.map( + name => '`' + name + '`' +).join(', '); export default ESLintUtils.RuleCreator(getDocsUrl)({ name: RULE_NAME, @@ -20,9 +23,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ recommended: false, }, messages: { - invalidRenderResultName: `\`{{ 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.map( - name => `\`${name}\`` - ).join(', ')}`, + invalidRenderResultName: `\`{{ 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}`, }, fixable: null, schema: [ diff --git a/lib/utils.ts b/lib/utils.ts index be015f6b..7232f306 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -25,8 +25,8 @@ const LIBRARY_MODULES = [ ]; const hasTestingLibraryImportModule = (node: TSESTree.ImportDeclaration) => { - return LIBRARY_MODULES.includes(node.source.value.toString()) -} + return LIBRARY_MODULES.includes(node.source.value.toString()); +}; const SYNC_QUERIES_VARIANTS = ['getBy', 'getAllBy', 'queryBy', 'queryAllBy']; const ASYNC_QUERIES_VARIANTS = ['findBy', 'findAllBy']; From 3e540748122ed41f758008fa1cdc8b8f29459544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 21 Jul 2020 14:01:32 +0200 Subject: [PATCH 05/18] test: fix invalid lines --- tests/lib/rules/render-result-naming-convention.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index a6d51ab9..9b482ece 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -205,7 +205,7 @@ ruleTester.run(RULE_NAME, rule, { data: { varName: 'wrapper', }, - line: 5, + line: 7, column: 17, }, ], @@ -228,7 +228,7 @@ ruleTester.run(RULE_NAME, rule, { data: { varName: 'wrapper', }, - line: 2, + line: 3, column: 17, }, ], From d55ed507e4880d608357b167ae9818d902ddbcbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 21 Jul 2020 14:22:57 +0200 Subject: [PATCH 06/18] feat: check imported module --- lib/rules/render-result-naming-convention.ts | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index 1bf2f87e..f123e51c 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -1,7 +1,8 @@ import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; -import { getDocsUrl } from '../utils'; +import { getDocsUrl, hasTestingLibraryImportModule } from '../utils'; import { isIdentifier, + isImportSpecifier, isObjectPattern, isRenderVariableDeclarator, } from '../node-utils'; @@ -45,17 +46,36 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ create(context, [options]) { const { renderFunctions } = options; - let renderResultName = null; + let renderResultName: string | null = null; + let renderAlias: string | undefined; return { + ImportDeclaration(node: TSESTree.ImportDeclaration) { + if (!hasTestingLibraryImportModule(node)) { + return; + } + const renderImport = node.specifiers.find( + node => isImportSpecifier(node) && node.imported.name === 'render' + ); + + if (!renderImport) { + return; + } + + renderAlias = renderImport.local.name; + }, VariableDeclarator(node) { if ( isRenderVariableDeclarator(node, renderFunctions) && !isObjectPattern(node.id) ) { renderResultName = isIdentifier(node.id) && node.id.name; + const isTestingLibraryRenderAlias = !!renderAlias; + const isAllowedRenderResultName = ALLOWED_VAR_NAMES.includes( + renderResultName + ); - if (!ALLOWED_VAR_NAMES.includes(renderResultName)) { + if (isTestingLibraryRenderAlias && !isAllowedRenderResultName) { context.report({ node, messageId: 'invalidRenderResultName', From c56b7dcb9135f446544d3f3c927ef670fd20fb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 21 Jul 2020 14:46:33 +0200 Subject: [PATCH 07/18] feat: check imported render renamed --- lib/rules/render-result-naming-convention.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index f123e51c..692566c5 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -65,10 +65,12 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ renderAlias = renderImport.local.name; }, VariableDeclarator(node) { - if ( - isRenderVariableDeclarator(node, renderFunctions) && - !isObjectPattern(node.id) - ) { + const isValidRenderDeclarator = isRenderVariableDeclarator(node, [ + ...renderFunctions, + renderAlias, + ]); + + if (isValidRenderDeclarator && !isObjectPattern(node.id)) { renderResultName = isIdentifier(node.id) && node.id.name; const isTestingLibraryRenderAlias = !!renderAlias; const isAllowedRenderResultName = ALLOWED_VAR_NAMES.includes( From 4837c3d9c7db9d700d0565ac474896ff5bbf676e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 21 Jul 2020 18:03:26 +0200 Subject: [PATCH 08/18] feat: check custom render --- lib/rules/render-result-naming-convention.ts | 12 ++++++++++-- .../rules/render-result-naming-convention.test.ts | 8 +++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index 692566c5..e2930612 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -1,6 +1,7 @@ import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; import { getDocsUrl, hasTestingLibraryImportModule } from '../utils'; import { + isCallExpression, isIdentifier, isImportSpecifier, isObjectPattern, @@ -72,12 +73,19 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ if (isValidRenderDeclarator && !isObjectPattern(node.id)) { renderResultName = isIdentifier(node.id) && node.id.name; - const isTestingLibraryRenderAlias = !!renderAlias; + + const renderFunctionName = + isCallExpression(node.init) && + isIdentifier(node.init.callee) && + node.init.callee.name; + + const isTestingLibraryRender = + !!renderAlias || renderFunctions.includes(renderFunctionName); const isAllowedRenderResultName = ALLOWED_VAR_NAMES.includes( renderResultName ); - if (isTestingLibraryRenderAlias && !isAllowedRenderResultName) { + if (isTestingLibraryRender && !isAllowedRenderResultName) { context.report({ node, messageId: 'invalidRenderResultName', diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index 9b482ece..edc1b583 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -212,14 +212,16 @@ ruleTester.run(RULE_NAME, rule, { }, { code: ` + import { customRender } from 'test-utils'; + test('should report from custom render function ', () => { - const wrapper = renderWithSomethingElse(); + const wrapper = customRender(); const button = wrapper.getByText('some button'); }); `, options: [ { - renderFunctions: ['renderWithSomethingElse'], + renderFunctions: ['customRender'], }, ], errors: [ @@ -228,7 +230,7 @@ ruleTester.run(RULE_NAME, rule, { data: { varName: 'wrapper', }, - line: 3, + line: 5, column: 17, }, ], From a2ec0a2083a1f33ff4b3806a2f2ef1f6bf8294d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 21 Jul 2020 18:25:34 +0200 Subject: [PATCH 09/18] test: add more valid tests for custom render functions --- .../render-result-naming-convention.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index edc1b583..59b58a3a 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -77,6 +77,51 @@ ruleTester.run(RULE_NAME, rule, { }); `, }, + { + code: ` + import { customRender } from 'test-utils'; + + test('should not report straight destructured render result from custom render', () => { + const { rerender, getByText } = customRender(); + const button = getByText('some button'); + }); + `, + options: [ + { + renderFunctions: ['customRender'], + }, + ], + }, + { + code: ` + import { customRender } from 'test-utils'; + + test('should not report render result called "view" from custom render', async () => { + const view = customRender(); + await view.findByRole('button'); + }); + `, + options: [ + { + renderFunctions: ['customRender'], + }, + ], + }, + { + code: ` + import { customRender } from 'test-utils'; + + test('should not report render result called "utils" from custom render', async () => { + const utils = customRender(); + await utils.findByRole('button'); + }); + `, + options: [ + { + renderFunctions: ['customRender'], + }, + ], + }, { code: ` import { render } from '@foo/bar'; From 0ac133c4eb5662882bc02c1787a31fe0243cd7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 21 Jul 2020 20:24:41 +0200 Subject: [PATCH 10/18] test: update tests for render wrapper functions --- .../render-result-naming-convention.test.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index 59b58a3a..5099b825 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -154,6 +154,23 @@ ruleTester.run(RULE_NAME, rule, { }); `, }, + { + code: ` + import { render } from '@testing-library/react'; + + const setup = () => { + // this one must have a valid name + const view = render(); + return view; + }; + + test('should not report render result called "view" from wrapping function', async () => { + // this isn't a render technically so it can be called "wrapper" + const wrapper = setup(); + await wrapper.findByRole('button'); + }); + `, + }, ], invalid: [ { @@ -237,9 +254,14 @@ ruleTester.run(RULE_NAME, rule, { code: ` import { render } from '@testing-library/react'; - const setup = () => render(); + const setup = () => { + // this one must have a valid name + const wrapper = render(); + return wrapper; + }; test('should report render result called "wrapper" from wrapping function', async () => { + // this isn't a render technically so it can be called "wrapper" const wrapper = setup(); await wrapper.findByRole('button'); }); @@ -250,7 +272,7 @@ ruleTester.run(RULE_NAME, rule, { data: { varName: 'wrapper', }, - line: 7, + line: 6, column: 17, }, ], From a205e4d688f62534e4e1d68efc859b2ee3636c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 21 Jul 2020 20:51:40 +0200 Subject: [PATCH 11/18] docs: add rule docs --- README.md | 41 +++++----- docs/rules/render-result-naming-convention.md | 78 ++++++++++++++++++- 2 files changed, 98 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 67f525f9..7995b942 100644 --- a/README.md +++ b/README.md @@ -125,27 +125,28 @@ To enable this configuration use the `extends` property in your ## Supported Rules -| Rule | Description | Configurations | Fixable | -| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------ | -| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [await-async-utils](docs/rules/await-async-utils.md) | Enforce async utils to be awaited properly | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | | -| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensure `data-testid` values match a provided regex. | | | -| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] | -| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | -| [no-multiple-assertions-wait-for](docs/rules/no-multiple-assertions-wait-for.md) | Disallow the use of multiple expect inside `waitFor` | | | -| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | | | -| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | | -| [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | | -| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `findBy*` methods instead of the `waitFor` + `getBy` queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] | -| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | | +| Rule | Description | Configurations | Fixable | +| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------ | +| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [await-async-utils](docs/rules/await-async-utils.md) | Enforce async utils to be awaited properly | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | | +| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensure `data-testid` values match a provided regex. | | | +| [no-await-sync-query](docs/rules/no-await-sync-query.md) | Disallow unnecessary `await` for sync queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] | +| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | +| [no-multiple-assertions-wait-for](docs/rules/no-multiple-assertions-wait-for.md) | Disallow the use of multiple expect inside `waitFor` | | | +| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | | +| [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | | +| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `findBy*` methods instead of the `waitFor` + `getBy` queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] | +| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | | | [prefer-user-event](docs/rules/prefer-user-event.md) | Suggest using `userEvent` library instead of `fireEvent` for simulating user interaction | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using screen while using queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | ![fixable-badge][] | +| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using screen while using queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | ![fixable-badge][] | +| [render-result-naming-convention](docs/rules/render-result-naming-convention.md) | Enforce a valid naming for return value from `render` | ![angular-badge][] ![react-badge][] ![vue-badge][] | | [build-badge]: https://img.shields.io/travis/testing-library/eslint-plugin-testing-library?style=flat-square [build-url]: https://travis-ci.org/testing-library/eslint-plugin-testing-library diff --git a/docs/rules/render-result-naming-convention.md b/docs/rules/render-result-naming-convention.md index 1333ed77..998117ac 100644 --- a/docs/rules/render-result-naming-convention.md +++ b/docs/rules/render-result-naming-convention.md @@ -1 +1,77 @@ -TODO +# Enforce a valid naming for return value from `render` (render-result-naming-convention) + +> The name `wrapper` is old cruft from `enzyme` and we don't need that here. The return value from `render` is not "wrapping" anything. It's simply a collection of utilities that you should actually not often need anyway. + +## Rule Details + +This rule aims to ensure the return value from `render` is named properly. + +Ideally, you should destructure what you need from `render`. In case you need to save the collection of utils returned in a variable, its name should be on of `view` or `utils`, as `render` is not wrapping anything: it's just returning a collection of utilities. Every other name for that variable will be considered invalid. + +To sum up these rules, the allowed naming convention for return value from `render` is: + +- destructuring +- `view` +- `utils` + +Examples of **incorrect** code for this rule: + +```javascript +import { render } from '@testing-library/framework'; + +// ... + +// return value from `render` shouldn't be kept in a var called "wrapper" +const wrapper = render(); +``` + +```javascript +import { render } from '@testing-library/framework'; + +// ... + +// return value from `render` shouldn't be kept in a var called "component" +const component = render(); +``` + +```javascript +import { render } from '@testing-library/framework'; + +// ... + +// to sum up: return value from `render` shouldn't be kept in a var called other than "view" or "utils" +const somethingElse = render(); +``` + +Examples of **correct** code for this rule: + +```javascript +import { render } from '@testing-library/framework'; + +// ... + +// destructuring return value from `render` is correct +const { unmount, rerender } = render(); +``` + +```javascript +import { render } from '@testing-library/framework'; + +// ... + +// keeping return value from `render` in a var called "view" is correct +const view = render(); +``` + +```javascript +import { render } from '@testing-library/framework'; + +// ... + +// keeping return value from `render` in a var called "utils" is correct +const utils = render(); +``` + +## Further Reading + +- [Common Mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#using-wrapper-as-the-variable-name-for-the-return-value-from-render) From f4524102f0dfadf1319c7dce523e80d46dc8520f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 22 Jul 2020 11:43:12 +0200 Subject: [PATCH 12/18] test: increase coverage up to 100% --- tests/lib/rules/render-result-naming-convention.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index 5099b825..d101fbaf 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -79,11 +79,12 @@ ruleTester.run(RULE_NAME, rule, { }, { code: ` + import { screen } from '@testing-library/react'; import { customRender } from 'test-utils'; test('should not report straight destructured render result from custom render', () => { - const { rerender, getByText } = customRender(); - const button = getByText('some button'); + const { unmount } = customRender(); + const button = screen.getByText('some button'); }); `, options: [ From c326afbe8b7d8f92da3bdf446311cfaa24cf3e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 22 Jul 2020 12:01:52 +0200 Subject: [PATCH 13/18] fix: add rule meta description --- lib/rules/render-result-naming-convention.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index e2930612..27b28633 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -20,7 +20,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ meta: { type: 'suggestion', docs: { - description: 'TODO', + description: 'Enforce a valid naming for return value from `render`', category: 'Best Practices', recommended: false, }, From 2fde16db565b4f47f1cc29de4fcfe8de63112360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 22 Jul 2020 12:13:06 +0200 Subject: [PATCH 14/18] docs: update rule details to mention screen object --- docs/rules/render-result-naming-convention.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/render-result-naming-convention.md b/docs/rules/render-result-naming-convention.md index 998117ac..d2a64819 100644 --- a/docs/rules/render-result-naming-convention.md +++ b/docs/rules/render-result-naming-convention.md @@ -6,7 +6,7 @@ This rule aims to ensure the return value from `render` is named properly. -Ideally, you should destructure what you need from `render`. In case you need to save the collection of utils returned in a variable, its name should be on of `view` or `utils`, as `render` is not wrapping anything: it's just returning a collection of utilities. Every other name for that variable will be considered invalid. +Ideally, you should destructure the minimum utils that you need from `render`, combined with using queries from [`screen` object](https://github.com/testing-library/eslint-plugin-testing-library/blob/master/docs/rules/prefer-screen-queries.md). In case you need to save the collection of utils returned in a variable, its name should be either `view` or `utils`, as `render` is not wrapping anything: it's just returning a collection of utilities. Every other name for that variable will be considered invalid. To sum up these rules, the allowed naming convention for return value from `render` is: From 112d5659370ad3afda40c76171a6065c41ab4c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 22 Jul 2020 13:19:17 +0200 Subject: [PATCH 15/18] refactor: return as soon as conditions are not met --- lib/rules/render-result-naming-convention.ts | 49 +++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index 27b28633..1991f384 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -47,10 +47,11 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ create(context, [options]) { const { renderFunctions } = options; - let renderResultName: string | null = null; let renderAlias: string | undefined; + let wildcardImportName: string | undefined; return { + // check named imports ImportDeclaration(node: TSESTree.ImportDeclaration) { if (!hasTestingLibraryImportModule(node)) { return; @@ -65,36 +66,40 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ renderAlias = renderImport.local.name; }, - VariableDeclarator(node) { + VariableDeclarator(node: TSESTree.VariableDeclarator) { const isValidRenderDeclarator = isRenderVariableDeclarator(node, [ ...renderFunctions, renderAlias, ]); - if (isValidRenderDeclarator && !isObjectPattern(node.id)) { - renderResultName = isIdentifier(node.id) && node.id.name; + if (!isValidRenderDeclarator || isObjectPattern(node.id)) { + return; + } - const renderFunctionName = - isCallExpression(node.init) && - isIdentifier(node.init.callee) && - node.init.callee.name; + const renderResultName = isIdentifier(node.id) && node.id.name; - const isTestingLibraryRender = - !!renderAlias || renderFunctions.includes(renderFunctionName); - const isAllowedRenderResultName = ALLOWED_VAR_NAMES.includes( - renderResultName - ); + const renderFunctionName = + isCallExpression(node.init) && + isIdentifier(node.init.callee) && + node.init.callee.name; + + const isTestingLibraryRender = + !!renderAlias || renderFunctions.includes(renderFunctionName); + const isAllowedRenderResultName = ALLOWED_VAR_NAMES.includes( + renderResultName + ); - if (isTestingLibraryRender && !isAllowedRenderResultName) { - context.report({ - node, - messageId: 'invalidRenderResultName', - data: { - varName: renderResultName, - }, - }); - } + if (!isTestingLibraryRender || isAllowedRenderResultName) { + return; } + + context.report({ + node, + messageId: 'invalidRenderResultName', + data: { + varName: renderResultName, + }, + }); }, }; }, From ce7a207d7adb590ae30df2eb238eeeb21d587e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 22 Jul 2020 15:53:09 +0200 Subject: [PATCH 16/18] feat: check wildcard imports --- lib/rules/render-result-naming-convention.ts | 51 ++++++++++++++++--- .../render-result-naming-convention.test.ts | 40 +++++++++++++++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/lib/rules/render-result-naming-convention.ts b/lib/rules/render-result-naming-convention.ts index 1991f384..5d63011f 100644 --- a/lib/rules/render-result-naming-convention.ts +++ b/lib/rules/render-result-naming-convention.ts @@ -4,6 +4,7 @@ import { isCallExpression, isIdentifier, isImportSpecifier, + isMemberExpression, isObjectPattern, isRenderVariableDeclarator, } from '../node-utils'; @@ -66,30 +67,68 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ renderAlias = renderImport.local.name; }, + // check wildcard imports + 'ImportDeclaration ImportNamespaceSpecifier'( + node: TSESTree.ImportNamespaceSpecifier + ) { + if ( + !hasTestingLibraryImportModule( + node.parent as TSESTree.ImportDeclaration + ) + ) { + return; + } + + wildcardImportName = node.local.name; + }, VariableDeclarator(node: TSESTree.VariableDeclarator) { + // check if destructuring return value from render + if (isObjectPattern(node.id)) { + return; + } + const isValidRenderDeclarator = isRenderVariableDeclarator(node, [ ...renderFunctions, renderAlias, ]); + const isValidWildcardImport = !!wildcardImportName; - if (!isValidRenderDeclarator || isObjectPattern(node.id)) { + // check if is a Testing Library related import + if (!isValidRenderDeclarator && !isValidWildcardImport) { return; } - const renderResultName = isIdentifier(node.id) && node.id.name; - const renderFunctionName = isCallExpression(node.init) && isIdentifier(node.init.callee) && node.init.callee.name; - const isTestingLibraryRender = - !!renderAlias || renderFunctions.includes(renderFunctionName); + const renderFunctionObjectName = + isCallExpression(node.init) && + isMemberExpression(node.init.callee) && + isIdentifier(node.init.callee.property) && + isIdentifier(node.init.callee.object) && + node.init.callee.property.name === 'render' && + node.init.callee.object.name; + + const isRenderAlias = !!renderAlias; + const isCustomRender = renderFunctions.includes(renderFunctionName); + const isWildCardRender = + renderFunctionObjectName && + renderFunctionObjectName === wildcardImportName; + + // check if is a qualified render function + if (!isRenderAlias && !isCustomRender && !isWildCardRender) { + return; + } + + const renderResultName = isIdentifier(node.id) && node.id.name; const isAllowedRenderResultName = ALLOWED_VAR_NAMES.includes( renderResultName ); - if (!isTestingLibraryRender || isAllowedRenderResultName) { + // check if return value var name is allowed + if (isAllowedRenderResultName) { return; } diff --git a/tests/lib/rules/render-result-naming-convention.test.ts b/tests/lib/rules/render-result-naming-convention.test.ts index d101fbaf..f21959ff 100644 --- a/tests/lib/rules/render-result-naming-convention.test.ts +++ b/tests/lib/rules/render-result-naming-convention.test.ts @@ -21,6 +21,16 @@ ruleTester.run(RULE_NAME, rule, { }); `, }, + { + code: ` + import * as RTL from '@testing-library/react'; + + test('should not report straight destructured render result from wildcard import', () => { + const { rerender, getByText } = RTL.render(); + const button = getByText('some button'); + }); + `, + }, { code: ` import { render } from '@testing-library/react'; @@ -143,6 +153,16 @@ ruleTester.run(RULE_NAME, rule, { }); `, }, + { + code: ` + import * as RTL from '@foo/bar'; + + test('should not report from wildcard render not imported from testing library', () => { + const wrapper = RTL.render(); + const button = wrapper.getByText('some button'); + }); + `, + }, { code: ` function render() { @@ -194,6 +214,26 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + { + code: ` + import * as RTL from '@testing-library/react'; + + test('should report straight render result called "wrapper" from wildcard import', () => { + const wrapper = RTL.render(); + const button = wrapper.getByText('some button'); + }); + `, + errors: [ + { + messageId: 'invalidRenderResultName', + data: { + varName: 'wrapper', + }, + line: 5, + column: 17, + }, + ], + }, { code: ` import { render } from '@testing-library/react'; From 92f769e3b88536920d7a44ac63308f2eb8a13d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Mon, 27 Jul 2020 10:42:58 +0200 Subject: [PATCH 17/18] refactor: rename default import --- lib/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index d9aa98c7..d8a64006 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -17,7 +17,7 @@ import preferUserEvent from './rules/prefer-user-event'; import preferWaitFor from './rules/prefer-wait-for'; import noMultipleAssertionsWaitFor from './rules/no-multiple-assertions-wait-for'; import preferFindBy from './rules/prefer-find-by'; -import invalidRenderResultName from './rules/render-result-naming-convention'; +import renderResultNamingConvention from './rules/render-result-naming-convention'; const rules = { 'await-async-query': awaitAsyncQuery, @@ -39,7 +39,7 @@ const rules = { 'prefer-screen-queries': preferScreenQueries, 'prefer-user-event': preferUserEvent, 'prefer-wait-for': preferWaitFor, - 'render-result-naming-convention': invalidRenderResultName, + 'render-result-naming-convention': renderResultNamingConvention, }; const domRules = { From 294645f3d62b6db3aeed86d8246295a8cfdca038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Mon, 27 Jul 2020 10:46:45 +0200 Subject: [PATCH 18/18] docs: include render result link --- docs/rules/render-result-naming-convention.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rules/render-result-naming-convention.md b/docs/rules/render-result-naming-convention.md index d2a64819..ffd6ec8f 100644 --- a/docs/rules/render-result-naming-convention.md +++ b/docs/rules/render-result-naming-convention.md @@ -75,3 +75,4 @@ const utils = render(); ## Further Reading - [Common Mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#using-wrapper-as-the-variable-name-for-the-return-value-from-render) +- [`render` Result](https://testing-library.com/docs/react-testing-library/api#render-result)