Skip to content

Commit b2579bb

Browse files
authored
feat(no-debug): support more debugging functions (#357)
* feat(no-debug): support `utilNames` option * fix(no-debug): only accept known debug utils * refactor: merge `isDebugUtil` & `isOneOfDebugUtils` utils * docs(no-debug): mention which utils are checked for by default * fix(no-debug): mark `utilNames` as optional * test(no-debug): add new case * feat(no-debug): accept `utilsToCheckFor` property option * feat: support more `DEBUG_UTILS` * docs(no-debug): update with details of new functionality * docs(no-debug): include list of debug utils and more complete example
1 parent 88416b2 commit b2579bb

File tree

6 files changed

+197
-17
lines changed

6 files changed

+197
-17
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ To enable this configuration use the `extends` property in your
187187
| [testing-library/no-await-sync-events](docs/rules/no-await-sync-events.md) | Disallow unnecessary `await` for sync events | | |
188188
| [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][] | |
189189
| [testing-library/no-container](docs/rules/no-container.md) | Disallow the use of `container` methods | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
190-
| [testing-library/no-debug](docs/rules/no-debug.md) | Disallow the use of `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
190+
| [testing-library/no-debug](docs/rules/no-debug.md) | Disallow the use of debugging utilities like `debug` | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
191191
| [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][] |
192192
| [testing-library/no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
193193
| [testing-library/no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | |

docs/rules/no-debug.md

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
# Disallow the use of `debug` (`testing-library/no-debug`)
1+
# Disallow the use of debugging utilities like `debug` (`testing-library/no-debug`)
22

33
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.
44

55
## Rule Details
66

7-
This rule aims to disallow the use of `debug` in your tests.
7+
This rule supports disallowing the following debugging utilities:
8+
9+
- `debug`
10+
- `logTestingPlaygroundURL`
11+
- `prettyDOM`
12+
- `logRoles`
13+
- `logDOM`
14+
- `prettyFormat`
15+
16+
By default, only `debug` and `logTestingPlaygroundURL` are disallowed.
817

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

@@ -28,6 +37,23 @@ const { screen } = require('@testing-library/react');
2837
screen.debug();
2938
```
3039

40+
You can control which debugging utils are checked for with the `utilsToCheckFor` option:
41+
42+
```json
43+
{
44+
"testing-library/no-debug": [
45+
"error",
46+
{
47+
"utilsToCheckFor": {
48+
"debug": false,
49+
"logRoles": true,
50+
"logDOM": true
51+
}
52+
}
53+
]
54+
}
55+
```
56+
3157
## Further Reading
3258

3359
- [debug API in React Testing Library](https://testing-library.com/docs/react-testing-library/api#debug)

lib/create-testing-library-rule/detect-testing-library-utils.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
ABSENCE_MATCHERS,
2626
ALL_QUERIES_COMBINATIONS,
2727
ASYNC_UTILS,
28+
DEBUG_UTILS,
2829
PRESENCE_MATCHERS,
2930
} from '../utils';
3031

@@ -79,7 +80,10 @@ type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean;
7980
type IsRenderVariableDeclaratorFn = (
8081
node: TSESTree.VariableDeclarator
8182
) => boolean;
82-
type IsDebugUtilFn = (identifierNode: TSESTree.Identifier) => boolean;
83+
type IsDebugUtilFn = (
84+
identifierNode: TSESTree.Identifier,
85+
validNames?: ReadonlyArray<typeof DEBUG_UTILS[number]>
86+
) => boolean;
8387
type IsPresenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
8488
type IsAbsenceAssertFn = (node: TSESTree.MemberExpression) => boolean;
8589
type CanReportErrorsFn = () => boolean;
@@ -605,7 +609,10 @@ export function detectTestingLibraryUtils<
605609
return isRenderUtil(initIdentifierNode);
606610
};
607611

608-
const isDebugUtil: IsDebugUtilFn = (identifierNode) => {
612+
const isDebugUtil: IsDebugUtilFn = (
613+
identifierNode,
614+
validNames = DEBUG_UTILS
615+
) => {
609616
const isBuiltInConsole =
610617
isMemberExpression(identifierNode.parent) &&
611618
ASTUtils.isIdentifier(identifierNode.parent.object) &&
@@ -616,9 +623,11 @@ export function detectTestingLibraryUtils<
616623
isPotentialTestingLibraryFunction(
617624
identifierNode,
618625
(identifierNodeName, originalNodeName) => {
619-
return [identifierNodeName, originalNodeName]
620-
.filter(Boolean)
621-
.includes('debug');
626+
return (
627+
(validNames as string[]).includes(identifierNodeName) ||
628+
(!!originalNodeName &&
629+
(validNames as string[]).includes(originalNodeName))
630+
);
622631
}
623632
)
624633
);

lib/rules/no-debug.ts

+47-9
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,28 @@ import {
88
isObjectPattern,
99
isProperty,
1010
} from '../node-utils';
11+
import { DEBUG_UTILS } from '../utils';
1112
import { createTestingLibraryRule } from '../create-testing-library-rule';
12-
import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils';
13+
import {
14+
ASTUtils,
15+
TSESTree,
16+
JSONSchema,
17+
} from '@typescript-eslint/experimental-utils';
18+
19+
type DebugUtilsToCheckFor = Partial<
20+
Record<typeof DEBUG_UTILS[number], boolean>
21+
>;
1322

1423
export const RULE_NAME = 'no-debug';
1524
export type MessageIds = 'noDebug';
16-
type Options = [];
25+
type Options = [{ utilsToCheckFor?: DebugUtilsToCheckFor }];
1726

1827
export default createTestingLibraryRule<Options, MessageIds>({
1928
name: RULE_NAME,
2029
meta: {
2130
type: 'problem',
2231
docs: {
23-
description: 'Disallow unnecessary debug usages in the tests',
32+
description: 'Disallow the use of debugging utilities like `debug`',
2433
category: 'Best Practices',
2534
recommendedConfig: {
2635
dom: false,
@@ -32,16 +41,42 @@ export default createTestingLibraryRule<Options, MessageIds>({
3241
messages: {
3342
noDebug: 'Unexpected debug statement',
3443
},
35-
schema: [],
44+
schema: [
45+
{
46+
type: 'object',
47+
properties: {
48+
utilsToCheckFor: {
49+
type: 'object',
50+
properties: DEBUG_UTILS.reduce<
51+
Record<string, JSONSchema.JSONSchema7>
52+
>(
53+
(obj, name) => ({
54+
[name]: { type: 'boolean' },
55+
...obj,
56+
}),
57+
{}
58+
),
59+
additionalProperties: false,
60+
},
61+
},
62+
additionalProperties: false,
63+
},
64+
],
3665
},
37-
defaultOptions: [],
66+
defaultOptions: [
67+
{ utilsToCheckFor: { debug: true, logTestingPlaygroundURL: true } },
68+
],
3869

39-
create(context, [], helpers) {
70+
create(context, [{ utilsToCheckFor = {} }], helpers) {
4071
const suspiciousDebugVariableNames: string[] = [];
4172
const suspiciousReferenceNodes: TSESTree.Identifier[] = [];
4273
const renderWrapperNames: string[] = [];
4374
const builtInConsoleNodes: TSESTree.VariableDeclarator[] = [];
4475

76+
const utilsToReport = Object.entries(utilsToCheckFor)
77+
.filter(([, shouldCheckFor]) => shouldCheckFor)
78+
.map(([name]) => name);
79+
4580
function detectRenderWrapper(node: TSESTree.Identifier): void {
4681
const innerFunction = getInnermostReturningFunction(context, node);
4782

@@ -84,7 +119,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
84119
if (
85120
isProperty(property) &&
86121
ASTUtils.isIdentifier(property.key) &&
87-
property.key.name === 'debug'
122+
utilsToReport.includes(property.key.name)
88123
) {
89124
const identifierNode = getDeepestIdentifierNode(property.value);
90125

@@ -119,14 +154,17 @@ export default createTestingLibraryRule<Options, MessageIds>({
119154
return;
120155
}
121156

122-
const isDebugUtil = helpers.isDebugUtil(callExpressionIdentifier);
157+
const isDebugUtil = helpers.isDebugUtil(
158+
callExpressionIdentifier,
159+
utilsToReport as Array<typeof DEBUG_UTILS[number]>
160+
);
123161
const isDeclaredDebugVariable = suspiciousDebugVariableNames.includes(
124162
callExpressionIdentifier.name
125163
);
126164
const isChainedReferenceDebug = suspiciousReferenceNodes.some(
127165
(suspiciousReferenceIdentifier) => {
128166
return (
129-
callExpressionIdentifier.name === 'debug' &&
167+
utilsToReport.includes(callExpressionIdentifier.name) &&
130168
suspiciousReferenceIdentifier.name === referenceIdentifier.name
131169
);
132170
}

lib/utils/index.ts

+10
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ const ASYNC_UTILS = [
6666
'waitForDomChange',
6767
] as const;
6868

69+
const DEBUG_UTILS = [
70+
'debug',
71+
'logTestingPlaygroundURL',
72+
'prettyDOM',
73+
'logRoles',
74+
'logDOM',
75+
'prettyFormat',
76+
] as const;
77+
6978
const EVENTS_SIMULATORS = ['fireEvent', 'userEvent'] as const;
7079

7180
const TESTING_FRAMEWORK_SETUP_HOOKS = ['beforeEach', 'beforeAll'];
@@ -119,6 +128,7 @@ export {
119128
ASYNC_QUERIES_COMBINATIONS,
120129
ALL_QUERIES_COMBINATIONS,
121130
ASYNC_UTILS,
131+
DEBUG_UTILS,
122132
EVENTS_SIMULATORS,
123133
TESTING_FRAMEWORK_SETUP_HOOKS,
124134
LIBRARY_MODULES,

tests/lib/rules/no-debug.test.ts

+97
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ ruleTester.run(RULE_NAME, rule, {
8787
screen.debug
8888
`,
8989
},
90+
{
91+
code: `
92+
import { screen } from '@testing-library/dom'
93+
screen.logTestingPlaygroundURL()
94+
`,
95+
options: [{ utilsToCheckFor: { logTestingPlaygroundURL: false } }],
96+
},
97+
{
98+
code: `
99+
import { screen } from '@testing-library/dom'
100+
screen.logTestingPlaygroundURL()
101+
`,
102+
options: [{ utilsToCheckFor: undefined }],
103+
},
90104
{
91105
code: `const { queries } = require('@testing-library/dom')`,
92106
},
@@ -419,6 +433,89 @@ ruleTester.run(RULE_NAME, rule, {
419433
},
420434
],
421435
},
436+
{
437+
code: `
438+
import { screen } from '@testing-library/dom'
439+
screen.logTestingPlaygroundURL()
440+
`,
441+
options: [{ utilsToCheckFor: { logTestingPlaygroundURL: true } }],
442+
errors: [
443+
{
444+
line: 3,
445+
column: 16,
446+
messageId: 'noDebug',
447+
},
448+
],
449+
},
450+
{
451+
code: `
452+
import { logRoles } from '@testing-library/dom'
453+
logRoles(document.createElement('nav'))
454+
`,
455+
options: [{ utilsToCheckFor: { logRoles: true } }],
456+
errors: [
457+
{
458+
line: 3,
459+
column: 9,
460+
messageId: 'noDebug',
461+
},
462+
],
463+
},
464+
{
465+
code: `
466+
import { screen } from '@testing-library/dom'
467+
screen.logTestingPlaygroundURL()
468+
`,
469+
options: [{ utilsToCheckFor: { logRoles: true } }],
470+
errors: [
471+
{
472+
line: 3,
473+
column: 16,
474+
messageId: 'noDebug',
475+
},
476+
],
477+
},
478+
{
479+
code: `
480+
import { screen } from '@testing-library/dom'
481+
screen.logTestingPlaygroundURL()
482+
`,
483+
options: [{ utilsToCheckFor: { debug: false } }],
484+
errors: [
485+
{
486+
line: 3,
487+
column: 16,
488+
messageId: 'noDebug',
489+
},
490+
],
491+
},
492+
{
493+
code: `
494+
import { screen } from '@testing-library/dom'
495+
screen.logTestingPlaygroundURL()
496+
`,
497+
options: [{ utilsToCheckFor: {} }],
498+
errors: [
499+
{
500+
line: 3,
501+
column: 16,
502+
messageId: 'noDebug',
503+
},
504+
],
505+
},
506+
{
507+
code: `
508+
import { screen } from '@testing-library/dom'
509+
screen.logTestingPlaygroundURL()
510+
`,
511+
errors: [
512+
{
513+
line: 3,
514+
column: 16,
515+
messageId: 'noDebug',
516+
},
517+
],
518+
},
422519
{
423520
settings: { 'testing-library/utils-module': 'test-utils' },
424521
code: `// aggressive reporting disabled

0 commit comments

Comments
 (0)