Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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][] | |
Expand Down
30 changes: 28 additions & 2 deletions docs/rules/no-debug.md
Original file line number Diff line number Diff line change
@@ -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:

Expand All @@ -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)
Expand Down
19 changes: 14 additions & 5 deletions lib/create-testing-library-rule/detect-testing-library-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
ABSENCE_MATCHERS,
ALL_QUERIES_COMBINATIONS,
ASYNC_UTILS,
DEBUG_UTILS,
PRESENCE_MATCHERS,
} from '../utils';

Expand Down Expand Up @@ -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<typeof DEBUG_UTILS[number]>
) => boolean;
type IsPresenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
type IsAbsenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
type CanReportErrorsFn = () => boolean;
Expand Down Expand Up @@ -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) &&
Expand All @@ -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))
);
}
)
);
Expand Down
56 changes: 47 additions & 9 deletions lib/rules/no-debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof DEBUG_UTILS[number], boolean>
>;

export const RULE_NAME = 'no-debug';
export type MessageIds = 'noDebug';
type Options = [];
type Options = [{ utilsToCheckFor?: DebugUtilsToCheckFor }];

export default createTestingLibraryRule<Options, MessageIds>({
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,
Expand All @@ -32,16 +41,42 @@ export default createTestingLibraryRule<Options, MessageIds>({
messages: {
noDebug: 'Unexpected debug statement',
},
schema: [],
schema: [
{
type: 'object',
properties: {
utilsToCheckFor: {
type: 'object',
properties: DEBUG_UTILS.reduce<
Record<string, JSONSchema.JSONSchema7>
>(
(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);

Expand Down Expand Up @@ -84,7 +119,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
if (
isProperty(property) &&
ASTUtils.isIdentifier(property.key) &&
property.key.name === 'debug'
utilsToReport.includes(property.key.name)
) {
const identifierNode = getDeepestIdentifierNode(property.value);

Expand Down Expand Up @@ -119,14 +154,17 @@ export default createTestingLibraryRule<Options, MessageIds>({
return;
}

const isDebugUtil = helpers.isDebugUtil(callExpressionIdentifier);
const isDebugUtil = helpers.isDebugUtil(
callExpressionIdentifier,
utilsToReport as Array<typeof DEBUG_UTILS[number]>
);
const isDeclaredDebugVariable = suspiciousDebugVariableNames.includes(
callExpressionIdentifier.name
);
const isChainedReferenceDebug = suspiciousReferenceNodes.some(
(suspiciousReferenceIdentifier) => {
return (
callExpressionIdentifier.name === 'debug' &&
utilsToReport.includes(callExpressionIdentifier.name) &&
suspiciousReferenceIdentifier.name === referenceIdentifier.name
);
}
Expand Down
10 changes: 10 additions & 0 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -119,6 +128,7 @@ export {
ASYNC_QUERIES_COMBINATIONS,
ALL_QUERIES_COMBINATIONS,
ASYNC_UTILS,
DEBUG_UTILS,
EVENTS_SIMULATORS,
TESTING_FRAMEWORK_SETUP_HOOKS,
LIBRARY_MODULES,
Expand Down
97 changes: 97 additions & 0 deletions tests/lib/rules/no-debug.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')`,
},
Expand Down Expand Up @@ -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
Expand Down