Skip to content

feat(no-debug): support utilNames option #357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 13, 2021
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
12 changes: 10 additions & 2 deletions docs/rules/no-debug.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# 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.

By default, this rule disallows the `debug` and `logTestingPlaygroundURL` utils.

## Rule Details

This rule aims to disallow the use of `debug` in your tests.
This rule aims to disallow the use of debugging utilities like `debug` in your tests.

Examples of **incorrect** code for this rule:

Expand All @@ -28,6 +30,12 @@ const { screen } = require('@testing-library/react');
screen.debug();
```

If you want to allow the use of some debugging functions, you can configure what names this rule checks for with the `utilsToCheckFor` option:

```
"testing-library/no-debug": ["error", { "utilsToCheckFor": { "debug": false } },
```

## 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