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
8 changes: 8 additions & 0 deletions docs/rules/no-debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

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.
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 `utilNames` option:

```
"testing-library/no-debug": ["error", {"utilNames": ["debug"}],
```

## 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
54 changes: 46 additions & 8 deletions lib/rules/no-debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ 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,
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