Skip to content

Commit 2ac6b81

Browse files
committed
Merge v4 with develop
2 parents 91200b7 + 609c6db commit 2ac6b81

25 files changed

+1062
-363
lines changed

.all-contributorsrc

+31
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,37 @@
306306
"doc",
307307
"test"
308308
]
309+
},
310+
{
311+
"login": "codecog",
312+
"name": "Josh Kelly",
313+
"avatar_url": "https://avatars0.githubusercontent.com/u/5106076?v=4",
314+
"profile": "https://github.com/codecog",
315+
"contributions": [
316+
"code"
317+
]
318+
},
319+
{
320+
"login": "alessbell",
321+
"name": "Alessia Bellisario",
322+
"avatar_url": "https://avatars0.githubusercontent.com/u/5139846?v=4",
323+
"profile": "http://aless.co",
324+
"contributions": [
325+
"code",
326+
"test",
327+
"doc"
328+
]
329+
},
330+
{
331+
"login": "skovy",
332+
"name": "Spencer Miskoviak",
333+
"avatar_url": "https://avatars1.githubusercontent.com/u/5247455?v=4",
334+
"profile": "https://skovy.dev",
335+
"contributions": [
336+
"code",
337+
"test",
338+
"doc"
339+
]
309340
}
310341
],
311342
"contributorsPerLine": 7,

CONTRIBUTING.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ Based on [ESLint's Rule Naming Conventions](https://eslint.org/docs/developer-gu
5757
In the [same way as ESLint](https://eslint.org/docs/developer-guide/working-with-rules),
5858
each rule has three files named with its identifier (e.g. `no-debug`):
5959
60-
- in the `lib/rules` directory: a source file (e.g. `no-debug.js`)
61-
- in the `tests/lib/rules` directory: a test file (e.g. `no-debug.js`)
60+
- in the `lib/rules` directory: a source file (e.g. `no-debug.ts`)
61+
- in the `tests/lib/rules` directory: a test file (e.g. `no-debug.ts`)
6262
- in the `docs/rules` directory: a Markdown documentation file (e.g. `no-debug.md`)
6363
6464
Additionally, you need to do a couple of extra things:
6565
66-
- Import the new rule in `lib/index.js` and include it
66+
- Import the new rule in `lib/index.ts` and include it
6767
in `rules` constant (there is a test which will make sure you did
68-
this). Remember to include your rule under corresponding `config` if necessary
68+
this). Remember to include your rule under corresponding `config` if necessary
6969
(a snapshot test will check this too, but you can update it just running
7070
`npm run test:update`).
7171
- Include your rule in the "Supported Rules" table within the [README.md](./README.md).
@@ -75,8 +75,8 @@ Additionally, you need to do a couple of extra things:
7575
7676
A couple of things you need to remember when editing already existing rules:
7777
78-
- If renaming a rule, make sure to update all points mentioned in
79-
"Adding new rules" section.
78+
- If renaming a rule, make sure to update all points mentioned in the
79+
"[Adding new rules"](#adding-new-rules) section.
8080
- Try to add tests to cover the changes introduced, no matter if that's
8181
a bug fix or a new feature.
8282

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
2626

27-
[![All Contributors](https://img.shields.io/badge/all_contributors-28-orange.svg?style=flat-square)](#contributors-)
27+
[![All Contributors](https://img.shields.io/badge/all_contributors-31-orange.svg?style=flat-square)](#contributors-)
2828

2929
<!-- ALL-CONTRIBUTORS-BADGE:END -->
3030

@@ -138,6 +138,7 @@ To enable this configuration use the `extends` property in your
138138
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
139139
| [no-multiple-assertions-wait-for](docs/rules/no-multiple-assertions-wait-for.md) | Disallow the use of multiple expect inside `waitFor` | | |
140140
| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
141+
| [no-render-in-setup](docs/rules/no-render-in-setup.md) | Disallow the use of `render` in setup functions | | |
141142
| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | |
142143
| [no-side-effects-wait-for](docs/rules/no-side-effects-wait-for.md) | Disallow the use of side effects inside `waitFor` | | |
143144
| [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
@@ -213,6 +214,11 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
213214
<td align="center"><a href="https://www.linkedin.com/in/mateusfelix/"><img src="https://avatars2.githubusercontent.com/u/4968788?v=4" width="100px;" alt=""/><br /><sub><b>Mateus Felix</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=thebinaryfelix" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=thebinaryfelix" title="Tests">⚠️</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=thebinaryfelix" title="Documentation">📖</a></td>
214215
<td align="center"><a href="https://github.com/renatoagds"><img src="https://avatars2.githubusercontent.com/u/1663717?v=4" width="100px;" alt=""/><br /><sub><b>Renato Augusto Gama dos Santos</b></sub></a><br /><a href="#ideas-renatoagds" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=renatoagds" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=renatoagds" title="Documentation">📖</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=renatoagds" title="Tests">⚠️</a></td>
215216
</tr>
217+
<tr>
218+
<td align="center"><a href="https://github.com/codecog"><img src="https://avatars0.githubusercontent.com/u/5106076?v=4" width="100px;" alt=""/><br /><sub><b>Josh Kelly</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=codecog" title="Code">💻</a></td>
219+
<td align="center"><a href="http://aless.co"><img src="https://avatars0.githubusercontent.com/u/5139846?v=4" width="100px;" alt=""/><br /><sub><b>Alessia Bellisario</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=alessbell" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=alessbell" title="Tests">⚠️</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=alessbell" title="Documentation">📖</a></td>
220+
<td align="center"><a href="https://skovy.dev"><img src="https://avatars1.githubusercontent.com/u/5247455?v=4" width="100px;" alt=""/><br /><sub><b>Spencer Miskoviak</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=skovy" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=skovy" title="Tests">⚠️</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=skovy" title="Documentation">📖</a></td>
221+
</tr>
216222
</table>
217223

218224
<!-- markdownlint-enable -->

docs/rules/consistent-data-testid.md

+15-4
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ const baz = props => <div>...</div>;
2424

2525
## Options
2626

27-
| Option | Required | Default | Details | Example |
28-
| ----------------- | -------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
29-
| `testIdPattern` | Yes | None | A regex used to validate the format of the `data-testid` value. `{fileName}` can optionally be used as a placeholder and will be substituted with the name of the file OR the name of the files parent directory in the case when the file name is `index.js` | `^{fileName}(\_\_([A-Z]+[a-z]_?)+)_\$` |
30-
| `testIdAttribute` | No | `data-testid` | A string used to specify the attribute used for querying by ID. This is only required if data-testid has been explicitly overridden in the [RTL configuration](https://testing-library.com/docs/dom-testing-library/api-queries#overriding-data-testid) | `data-my-test-attribute` |
27+
| Option | Required | Default | Details | Example |
28+
| ----------------- | -------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
29+
| `testIdPattern` | Yes | None | A regex used to validate the format of the `data-testid` value. `{fileName}` can optionally be used as a placeholder and will be substituted with the name of the file OR the name of the files parent directory in the case when the file name is `index.js` | `^{fileName}(\_\_([A-Z]+[a-z]_?)+)_\$` |
30+
| `testIdAttribute` | No | `data-testid` | A string (or array of strings) used to specify the attribute used for querying by ID. This is only required if data-testid has been explicitly overridden in the [RTL configuration](https://testing-library.com/docs/dom-testing-library/api-queries#overriding-data-testid) | `data-my-test-attribute`, `["data-testid", "testId"]` |
3131

3232
## Example
3333

@@ -41,3 +41,14 @@ const baz = props => <div>...</div>;
4141
]
4242
}
4343
```
44+
45+
```json
46+
{
47+
"testing-library/consistent-data-testid": [
48+
2,
49+
{
50+
"testIdAttribute": ["data-testid", "testId"]
51+
}
52+
]
53+
}
54+
```

docs/rules/no-render-in-setup.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Disallow the use of `render` in setup functions (no-render-in-setup)
2+
3+
## Rule Details
4+
5+
This rule disallows the usage of `render` (or a custom render function) in setup functions (`beforeEach` and `beforeAll`) in favor of moving `render` closer to test assertions.
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```js
10+
beforeEach(() => {
11+
render(<MyComponent />);
12+
});
13+
14+
it('Should have foo', () => {
15+
expect(screen.getByText('foo')).toBeInTheDocument();
16+
});
17+
18+
it('Should have bar', () => {
19+
expect(screen.getByText('bar')).toBeInTheDocument();
20+
});
21+
```
22+
23+
```js
24+
beforeAll(() => {
25+
render(<MyComponent />);
26+
});
27+
28+
it('Should have foo', () => {
29+
expect(screen.getByText('foo')).toBeInTheDocument();
30+
});
31+
32+
it('Should have bar', () => {
33+
expect(screen.getByText('bar')).toBeInTheDocument();
34+
});
35+
```
36+
37+
Examples of **correct** code for this rule:
38+
39+
```js
40+
it('Should have foo and bar', () => {
41+
render(<MyComponent />);
42+
expect(screen.getByText('foo')).toBeInTheDocument();
43+
expect(screen.getByText('bar')).toBeInTheDocument();
44+
});
45+
```
46+
47+
If you use [custom render functions](https://testing-library.com/docs/example-react-redux) then you can set a config option in your `.eslintrc` to look for these.
48+
49+
```
50+
"testing-library/no-render-in-setup": ["error", {"renderFunctions": ["renderWithRedux", "renderWithRouter"]}],
51+
```
52+
53+
If you would like to allow the use of `render` (or a custom render function) in _either_ `beforeAll` or `beforeEach`, this can be configured using the option `allowTestingFrameworkSetupHook`. This may be useful if you have configured your tests to [skip auto cleanup](https://testing-library.com/docs/react-testing-library/setup#skipping-auto-cleanup). `allowTestingFrameworkSetupHook` is an enum that accepts either `"beforeAll"` or `"beforeEach"`.
54+
55+
```
56+
"testing-library/no-render-in-setup": ["error", {"allowTestingFrameworkSetupHook": "beforeAll"}],
57+
```

docs/rules/prefer-screen-queries.md

+5
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ const { rerender, unmount, asFragment } = render(<Foo />);
6060
rerender(<Foo />);
6161
asFragment();
6262
unmount();
63+
64+
// using baseElement
65+
const { getByText } = render(<Foo />, { baseElement: treeA });
66+
// using container
67+
const { getAllByText } = render(<Foo />, { container: treeA });
6368
```
6469

6570
## Further Reading

docs/rules/prefer-wait-for.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const foo = async () => {
2323
await waitForElement(() => {});
2424
await waitForDomChange();
2525
await waitForDomChange(mutationObserverOptions);
26-
await waitForDomChange({ timeout: 100});
26+
await waitForDomChange({ timeout: 100 });
2727
};
2828
```
2929

lib/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import noDebug from './rules/no-debug';
88
import noDomImport from './rules/no-dom-import';
99
import noManualCleanup from './rules/no-manual-cleanup';
1010
import noNodeAccess from './rules/no-node-access';
11+
import noRenderInSetup from './rules/no-render-in-setup';
1112
import noWaitForEmptyCallback from './rules/no-wait-for-empty-callback';
1213
import noPromiseInFireEvent from './rules/no-promise-in-fire-event';
1314
import preferExplicitAssert from './rules/prefer-explicit-assert';
@@ -33,6 +34,7 @@ const rules = {
3334
'no-multiple-assertions-wait-for': noMultipleAssertionsWaitFor,
3435
'no-node-access': noNodeAccess,
3536
'no-promise-in-fire-event': noPromiseInFireEvent,
37+
'no-render-in-setup': noRenderInSetup,
3638
'no-side-effects-wait-for': noSideEffectsWaitFor,
3739
'no-wait-for-empty-callback': noWaitForEmptyCallback,
3840
'prefer-explicit-assert': preferExplicitAssert,

lib/node-utils.ts

+59-25
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import { TSESTree } from '@typescript-eslint/experimental-utils';
1+
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/experimental-utils';
2+
import { RuleContext } from '@typescript-eslint/experimental-utils/dist/ts-eslint';
23

34
export function isCallExpression(
45
node: TSESTree.Node
56
): node is TSESTree.CallExpression {
6-
return node && node.type === 'CallExpression';
7-
}
8-
9-
export function isAwaitExpression(
10-
node: TSESTree.Node
11-
): node is TSESTree.AwaitExpression {
12-
return node && node.type === 'AwaitExpression';
7+
return node && node.type === AST_NODE_TYPES.CallExpression;
138
}
149

1510
export function isNewExpression(
@@ -19,57 +14,57 @@ export function isNewExpression(
1914
}
2015

2116
export function isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier {
22-
return node && node.type === 'Identifier';
17+
return node && node.type === AST_NODE_TYPES.Identifier;
2318
}
2419

2520
export function isMemberExpression(
2621
node: TSESTree.Node
2722
): node is TSESTree.MemberExpression {
28-
return node && node.type === 'MemberExpression';
23+
return node && node.type === AST_NODE_TYPES.MemberExpression;
2924
}
3025

3126
export function isLiteral(node: TSESTree.Node): node is TSESTree.Literal {
32-
return node && node.type === 'Literal';
27+
return node && node.type === AST_NODE_TYPES.Literal;
3328
}
3429

3530
export function isImportSpecifier(
3631
node: TSESTree.Node
3732
): node is TSESTree.ImportSpecifier {
38-
return node && node.type === 'ImportSpecifier';
33+
return node && node.type === AST_NODE_TYPES.ImportSpecifier;
3934
}
4035

4136
export function isImportDefaultSpecifier(
4237
node: TSESTree.Node
4338
): node is TSESTree.ImportDefaultSpecifier {
44-
return node && node.type === 'ImportDefaultSpecifier';
39+
return node && node.type === AST_NODE_TYPES.ImportDefaultSpecifier;
4540
}
4641

4742
export function isBlockStatement(
4843
node: TSESTree.Node
4944
): node is TSESTree.BlockStatement {
50-
return node && node.type === 'BlockStatement';
45+
return node && node.type === AST_NODE_TYPES.BlockStatement;
5146
}
5247

5348
export function isVariableDeclarator(
5449
node: TSESTree.Node
5550
): node is TSESTree.VariableDeclarator {
56-
return node && node.type === 'VariableDeclarator';
51+
return node && node.type === AST_NODE_TYPES.VariableDeclarator;
5752
}
5853

5954
export function isObjectPattern(
6055
node: TSESTree.Node
6156
): node is TSESTree.ObjectPattern {
62-
return node && node.type === 'ObjectPattern';
57+
return node && node.type === AST_NODE_TYPES.ObjectPattern;
6358
}
6459

6560
export function isProperty(node: TSESTree.Node): node is TSESTree.Property {
66-
return node && node.type === 'Property';
61+
return node && node.type === AST_NODE_TYPES.Property;
6762
}
6863

6964
export function isJSXAttribute(
7065
node: TSESTree.Node
7166
): node is TSESTree.JSXAttribute {
72-
return node && node.type === 'JSXAttribute';
67+
return node && node.type === AST_NODE_TYPES.JSXAttribute;
7368
}
7469

7570
export function findClosestCallExpressionNode(
@@ -101,6 +96,10 @@ export function findClosestCallNode(
10196
}
10297
}
10398

99+
export function isObjectExpression(node: TSESTree.Expression): node is TSESTree.ObjectExpression {
100+
return node?.type === AST_NODE_TYPES.ObjectExpression
101+
}
102+
104103
export function hasThenProperty(node: TSESTree.Node) {
105104
return (
106105
isMemberExpression(node) &&
@@ -109,19 +108,54 @@ export function hasThenProperty(node: TSESTree.Node) {
109108
);
110109
}
111110

112-
export function isArrowFunctionExpression(
111+
export function isAwaitExpression(
113112
node: TSESTree.Node
114-
): node is TSESTree.ArrowFunctionExpression {
115-
return node && node.type === 'ArrowFunctionExpression';
113+
): node is TSESTree.AwaitExpression {
114+
return node && node.type === AST_NODE_TYPES.AwaitExpression;
116115
}
117116

118-
function isRenderFunction(
117+
export function isArrowFunctionExpression(node: TSESTree.Node): node is TSESTree.ArrowFunctionExpression {
118+
return node && node.type === AST_NODE_TYPES.ArrowFunctionExpression
119+
}
120+
121+
export function isReturnStatement(node: TSESTree.Node): node is TSESTree.ReturnStatement {
122+
return node && node.type === AST_NODE_TYPES.ReturnStatement
123+
}
124+
125+
export function isAwaited(node: TSESTree.Node) {
126+
return isAwaitExpression(node) || isArrowFunctionExpression(node) || isReturnStatement(node)
127+
}
128+
129+
export function isPromiseResolved(node: TSESTree.Node) {
130+
const parent = node.parent;
131+
132+
// wait(...).then(...)
133+
if (isCallExpression(parent)) {
134+
return hasThenProperty(parent.parent);
135+
}
136+
137+
// promise.then(...)
138+
return hasThenProperty(parent);
139+
}
140+
141+
export function getVariableReferences(context: RuleContext<string, []>, node: TSESTree.Node) {
142+
return (isVariableDeclarator(node) && context.getDeclaredVariables(node)[0].references.slice(1)) || [];
143+
}
144+
145+
export function isRenderFunction(
119146
callNode: TSESTree.CallExpression,
120147
renderFunctions: string[]
121148
) {
122-
return ['render', ...renderFunctions].some(
123-
name => isIdentifier(callNode.callee) && name === callNode.callee.name
124-
);
149+
// returns true for `render` and e.g. `customRenderFn`
150+
// as well as `someLib.render` and `someUtils.customRenderFn`
151+
return renderFunctions.some(name => {
152+
return (
153+
(isIdentifier(callNode.callee) && name === callNode.callee.name) ||
154+
(isMemberExpression(callNode.callee) &&
155+
isIdentifier(callNode.callee.property) &&
156+
name === callNode.callee.property.name)
157+
);
158+
});
125159
}
126160

127161
export function isRenderVariableDeclarator(

0 commit comments

Comments
 (0)