diff --git a/README.md b/README.md index f438182c..6ea2bcf9 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ To enable this configuration use the `extends` property in your | [testing-library/no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | | | [testing-library/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][] | | | [testing-library/no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![angular-badge][] ![react-badge][] ![vue-badge][] | | -| [testing-library/no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [testing-library/no-debug](docs/rules/no-debug.md) | Disallow the use of debugging utilities like `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | | | [testing-library/no-dom-import](docs/rules/no-dom-import.md) | Disallow importing from DOM Testing Library | ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] | | [testing-library/no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | | [testing-library/no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | | diff --git a/docs/rules/no-debug.md b/docs/rules/no-debug.md index 1b3167fd..9ae030dd 100644 --- a/docs/rules/no-debug.md +++ b/docs/rules/no-debug.md @@ -1,10 +1,19 @@ -# Disallow the use of `debug` (`testing-library/no-debug`) +# Disallow the use of debugging utilities like `debug` (`testing-library/no-debug`) Just like `console.log` statements pollutes the browser's output, debug statements also pollutes the tests if one of your teammates forgot to remove it. `debug` statements should be used when you actually want to debug your tests but should not be pushed to the codebase. ## Rule Details -This rule aims to disallow the use of `debug` in your tests. +This rule supports disallowing the following debugging utilities: + +- `debug` +- `logTestingPlaygroundURL` +- `prettyDOM` +- `logRoles` +- `logDOM` +- `prettyFormat` + +By default, only `debug` and `logTestingPlaygroundURL` are disallowed. Examples of **incorrect** code for this rule: @@ -28,6 +37,23 @@ const { screen } = require('@testing-library/react'); screen.debug(); ``` +You can control which debugging utils are checked for with the `utilsToCheckFor` option: + +```json +{ + "testing-library/no-debug": [ + "error", + { + "utilsToCheckFor": { + "debug": false, + "logRoles": true, + "logDOM": true + } + } + ] +} +``` + ## Further Reading - [debug API in React Testing Library](https://testing-library.com/docs/react-testing-library/api#debug) diff --git a/lib/create-testing-library-rule/detect-testing-library-utils.ts b/lib/create-testing-library-rule/detect-testing-library-utils.ts index 3d6662be..ec729111 100644 --- a/lib/create-testing-library-rule/detect-testing-library-utils.ts +++ b/lib/create-testing-library-rule/detect-testing-library-utils.ts @@ -25,6 +25,7 @@ import { ABSENCE_MATCHERS, ALL_QUERIES_COMBINATIONS, ASYNC_UTILS, + DEBUG_UTILS, PRESENCE_MATCHERS, } from '../utils'; @@ -79,7 +80,10 @@ type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean; type IsRenderVariableDeclaratorFn = ( node: TSESTree.VariableDeclarator ) => boolean; -type IsDebugUtilFn = (identifierNode: TSESTree.Identifier) => boolean; +type IsDebugUtilFn = ( + identifierNode: TSESTree.Identifier, + validNames?: ReadonlyArray +) => boolean; type IsPresenceAssertFn = (node: TSESTree.MemberExpression) => boolean; type IsAbsenceAssertFn = (node: TSESTree.MemberExpression) => boolean; type CanReportErrorsFn = () => boolean; @@ -595,7 +599,10 @@ export function detectTestingLibraryUtils< return isRenderUtil(initIdentifierNode); }; - const isDebugUtil: IsDebugUtilFn = (identifierNode) => { + const isDebugUtil: IsDebugUtilFn = ( + identifierNode, + validNames = DEBUG_UTILS + ) => { const isBuiltInConsole = isMemberExpression(identifierNode.parent) && ASTUtils.isIdentifier(identifierNode.parent.object) && @@ -606,9 +613,11 @@ export function detectTestingLibraryUtils< isTestingLibraryUtil( identifierNode, (identifierNodeName, originalNodeName) => { - return [identifierNodeName, originalNodeName] - .filter(Boolean) - .includes('debug'); + return ( + (validNames as string[]).includes(identifierNodeName) || + (!!originalNodeName && + (validNames as string[]).includes(originalNodeName)) + ); } ) ); diff --git a/lib/rules/no-debug.ts b/lib/rules/no-debug.ts index d1a492cf..39d2829a 100644 --- a/lib/rules/no-debug.ts +++ b/lib/rules/no-debug.ts @@ -8,19 +8,28 @@ import { isObjectPattern, isProperty, } from '../node-utils'; +import { DEBUG_UTILS } from '../utils'; import { createTestingLibraryRule } from '../create-testing-library-rule'; -import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils'; +import { + ASTUtils, + TSESTree, + JSONSchema, +} from '@typescript-eslint/experimental-utils'; + +type DebugUtilsToCheckFor = Partial< + Record +>; export const RULE_NAME = 'no-debug'; export type MessageIds = 'noDebug'; -type Options = []; +type Options = [{ utilsToCheckFor?: DebugUtilsToCheckFor }]; export default createTestingLibraryRule({ name: RULE_NAME, meta: { type: 'problem', docs: { - description: 'Disallow unnecessary debug usages in the tests', + description: 'Disallow the use of debugging utilities like `debug`', category: 'Best Practices', recommendedConfig: { dom: false, @@ -32,16 +41,42 @@ export default createTestingLibraryRule({ messages: { noDebug: 'Unexpected debug statement', }, - schema: [], + schema: [ + { + type: 'object', + properties: { + utilsToCheckFor: { + type: 'object', + properties: DEBUG_UTILS.reduce< + Record + >( + (obj, name) => ({ + [name]: { type: 'boolean' }, + ...obj, + }), + {} + ), + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], }, - defaultOptions: [], + defaultOptions: [ + { utilsToCheckFor: { debug: true, logTestingPlaygroundURL: true } }, + ], - create(context, [], helpers) { + create(context, [{ utilsToCheckFor = {} }], helpers) { const suspiciousDebugVariableNames: string[] = []; const suspiciousReferenceNodes: TSESTree.Identifier[] = []; const renderWrapperNames: string[] = []; const builtInConsoleNodes: TSESTree.VariableDeclarator[] = []; + const utilsToReport = Object.entries(utilsToCheckFor) + .filter(([, shouldCheckFor]) => shouldCheckFor) + .map(([name]) => name); + function detectRenderWrapper(node: TSESTree.Identifier): void { const innerFunction = getInnermostReturningFunction(context, node); @@ -84,7 +119,7 @@ export default createTestingLibraryRule({ if ( isProperty(property) && ASTUtils.isIdentifier(property.key) && - property.key.name === 'debug' + utilsToReport.includes(property.key.name) ) { const identifierNode = getDeepestIdentifierNode(property.value); @@ -119,14 +154,17 @@ export default createTestingLibraryRule({ return; } - const isDebugUtil = helpers.isDebugUtil(callExpressionIdentifier); + const isDebugUtil = helpers.isDebugUtil( + callExpressionIdentifier, + utilsToReport as Array + ); const isDeclaredDebugVariable = suspiciousDebugVariableNames.includes( callExpressionIdentifier.name ); const isChainedReferenceDebug = suspiciousReferenceNodes.some( (suspiciousReferenceIdentifier) => { return ( - callExpressionIdentifier.name === 'debug' && + utilsToReport.includes(callExpressionIdentifier.name) && suspiciousReferenceIdentifier.name === referenceIdentifier.name ); } diff --git a/lib/utils/index.ts b/lib/utils/index.ts index 106d5f36..55997e11 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -66,6 +66,15 @@ const ASYNC_UTILS = [ 'waitForDomChange', ] as const; +const DEBUG_UTILS = [ + 'debug', + 'logTestingPlaygroundURL', + 'prettyDOM', + 'logRoles', + 'logDOM', + 'prettyFormat', +] as const; + const EVENTS_SIMULATORS = ['fireEvent', 'userEvent'] as const; const TESTING_FRAMEWORK_SETUP_HOOKS = ['beforeEach', 'beforeAll']; @@ -119,6 +128,7 @@ export { ASYNC_QUERIES_COMBINATIONS, ALL_QUERIES_COMBINATIONS, ASYNC_UTILS, + DEBUG_UTILS, EVENTS_SIMULATORS, TESTING_FRAMEWORK_SETUP_HOOKS, LIBRARY_MODULES, diff --git a/tests/lib/rules/no-debug.test.ts b/tests/lib/rules/no-debug.test.ts index 7606b915..81507a38 100644 --- a/tests/lib/rules/no-debug.test.ts +++ b/tests/lib/rules/no-debug.test.ts @@ -87,6 +87,20 @@ ruleTester.run(RULE_NAME, rule, { screen.debug `, }, + { + code: ` + import { screen } from '@testing-library/dom' + screen.logTestingPlaygroundURL() + `, + options: [{ utilsToCheckFor: { logTestingPlaygroundURL: false } }], + }, + { + code: ` + import { screen } from '@testing-library/dom' + screen.logTestingPlaygroundURL() + `, + options: [{ utilsToCheckFor: undefined }], + }, { code: `const { queries } = require('@testing-library/dom')`, }, @@ -419,6 +433,89 @@ ruleTester.run(RULE_NAME, rule, { }, ], }, + { + code: ` + import { screen } from '@testing-library/dom' + screen.logTestingPlaygroundURL() + `, + options: [{ utilsToCheckFor: { logTestingPlaygroundURL: true } }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` + import { logRoles } from '@testing-library/dom' + logRoles(document.createElement('nav')) + `, + options: [{ utilsToCheckFor: { logRoles: true } }], + errors: [ + { + line: 3, + column: 9, + messageId: 'noDebug', + }, + ], + }, + { + code: ` + import { screen } from '@testing-library/dom' + screen.logTestingPlaygroundURL() + `, + options: [{ utilsToCheckFor: { logRoles: true } }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` + import { screen } from '@testing-library/dom' + screen.logTestingPlaygroundURL() + `, + options: [{ utilsToCheckFor: { debug: false } }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` + import { screen } from '@testing-library/dom' + screen.logTestingPlaygroundURL() + `, + options: [{ utilsToCheckFor: {} }], + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, + { + code: ` + import { screen } from '@testing-library/dom' + screen.logTestingPlaygroundURL() + `, + errors: [ + { + line: 3, + column: 16, + messageId: 'noDebug', + }, + ], + }, { settings: { 'testing-library/utils-module': 'test-utils' }, code: `// aggressive reporting disabled