Skip to content

Commit 4b7300e

Browse files
MichaelDeBoeyskovyallcontributors[bot]Gpxjdanil
authored
chore: merge master into v4 (#233)
* feat(prefer-explicit-assert): add 'assertion' config option (#220) Closes #218 * docs: add skovy as a contributor (#221) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * feat: new no-wait-for-snapshot rule (#223) Closes: #214 * docs: add Gpx as a contributor [skip ci] (#224) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * docs(no-wait-for-snapshot): fix link to rule doc (#225) * docs: add jdanil as a contributor [skip ci] (#226) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * Update .travis.yml * feat: support ESLint 7.x (#139) Closes #138 * docs: add MichaelDeBoey as a contributor [skip ci] (#231) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * chore: update dependencies + run prettier on codebase (#232) * chore: update dependencies * chore: run Prettier on full codebase Co-authored-by: Spencer Miskoviak <[email protected]> Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Giorgio Polvara <[email protected]> Co-authored-by: Josh David <[email protected]> Co-authored-by: Mario Beltrán Alarcón <[email protected]>
1 parent 44de9fc commit 4b7300e

29 files changed

+617
-15086
lines changed

.all-contributorsrc

+31
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,39 @@
335335
"contributions": [
336336
"code",
337337
"test",
338+
"doc",
339+
"ideas"
340+
]
341+
},
342+
{
343+
"login": "Gpx",
344+
"name": "Giorgio Polvara",
345+
"avatar_url": "https://avatars0.githubusercontent.com/u/767959?v=4",
346+
"profile": "https://twitter.com/Gpx",
347+
"contributions": [
348+
"code",
349+
"test",
350+
"doc"
351+
]
352+
},
353+
{
354+
"login": "jdanil",
355+
"name": "Josh David",
356+
"avatar_url": "https://avatars0.githubusercontent.com/u/8342105?v=4",
357+
"profile": "https://github.com/jdanil",
358+
"contributions": [
338359
"doc"
339360
]
361+
},
362+
{
363+
"login": "MichaelDeBoey",
364+
"name": "Michaël De Boey",
365+
"avatar_url": "https://avatars3.githubusercontent.com/u/6643991?v=4",
366+
"profile": "https://michaeldeboey.be",
367+
"contributions": [
368+
"code",
369+
"platform"
370+
]
340371
}
341372
],
342373
"contributorsPerLine": 7,

.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Output
1+
# Output
22
dist
33

44
# Logs
@@ -66,3 +66,8 @@ yarn-error.log
6666
.pnp.js
6767
# Yarn Integrity file
6868
.yarn-integrity
69+
70+
# these cause more harm than good
71+
# when working with contributors
72+
package-lock.json
73+
yarn.lock

.travis.yml

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ env:
66
matrix:
77
- ESLINT=5
88
- ESLINT=6
9+
- ESLINT=7
910

1011
node_js:
1112
- 10.12
13+
- 10
14+
- 12.0
1215
- 12
1316
- 14
1417

@@ -19,14 +22,14 @@ jobs:
1922
include:
2023
- stage: validation
2124
node_js: 14
22-
env: ESLINT=6
25+
env: ESLINT=7
2326
script:
2427
- npm run format:check
2528
- npm run lint -- --max-warnings 0
2629
- stage: release
27-
if: branch = master AND type != pull_request
30+
if: branch = master AND type != pull_request AND fork = false
2831
node_js: 14
29-
env: ESLINT=6
32+
env: ESLINT=7
3033
script: npm run build
3134
deploy:
3235
provider: script

README.md

+7-3
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-31-orange.svg?style=flat-square)](#contributors-)
27+
[![All Contributors](https://img.shields.io/badge/all_contributors-34-orange.svg?style=flat-square)](#contributors-)
2828

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

@@ -138,10 +138,11 @@ 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 | | |
142141
| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | |
142+
| [no-render-in-setup](docs/rules/no-render-in-setup.md) | Disallow the use of `render` in setup functions | | |
143143
| [no-side-effects-wait-for](docs/rules/no-side-effects-wait-for.md) | Disallow the use of side effects inside `waitFor` | | |
144144
| [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][] | |
145+
| [no-wait-for-snapshot](docs/rules/no-wait-for-snapshot.md) | Ensures no snapshot is generated inside of a `waitFor` call | | |
145146
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
146147
| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `findBy*` methods instead of the `waitFor` + `getBy` queries | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] |
147148
| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | |
@@ -217,7 +218,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
217218
<tr>
218219
<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>
219220
<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+
<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> <a href="#ideas-skovy" title="Ideas, Planning, & Feedback">🤔</a></td>
222+
<td align="center"><a href="https://twitter.com/Gpx"><img src="https://avatars0.githubusercontent.com/u/767959?v=4" width="100px;" alt=""/><br /><sub><b>Giorgio Polvara</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=Gpx" title="Code">💻</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=Gpx" title="Tests">⚠️</a> <a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=Gpx" title="Documentation">📖</a></td>
223+
<td align="center"><a href="https://github.com/jdanil"><img src="https://avatars0.githubusercontent.com/u/8342105?v=4" width="100px;" alt=""/><br /><sub><b>Josh David</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=jdanil" title="Documentation">📖</a></td>
224+
<td align="center"><a href="https://michaeldeboey.be"><img src="https://avatars3.githubusercontent.com/u/6643991?v=4" width="100px;" alt=""/><br /><sub><b>Michaël De Boey</b></sub></a><br /><a href="https://github.com/testing-library/eslint-plugin-testing-library/commits?author=MichaelDeBoey" title="Code">💻</a> <a href="#platform-MichaelDeBoey" title="Packaging/porting to new platform">📦</a></td>
221225
</tr>
222226
</table>
223227

commitlint.config.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
module.exports = { extends: ['@commitlint/config-conventional'] };
1+
module.exports = {
2+
extends: ['@commitlint/config-conventional'],
3+
};

docs/rules/no-wait-for-snapshot.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Ensures no snapshot is generated inside of a `wait` call' (no-wait-for-snapshot)
2+
3+
Ensure that no calls to `toMatchSnapshot` or `toMatchInlineSnapshot` are made from within a `waitFor` method (or any of the other async utility methods).
4+
5+
## Rule Details
6+
7+
The `waitFor()` method runs in a timer loop. So it'll retry every n amount of time.
8+
If a snapshot is generated inside the wait condition, jest will generate one snapshot per loop.
9+
10+
The problem then is the amount of loop ran until the condition is met will vary between different computers (or CI machines). This leads to tests that will regenerate a lot of snapshots until the condition is matched when devs run those tests locally updating the snapshots; e.g devs cannot run `jest -u` locally or it'll generate a lot of invalid snapshots who'll fail during CI.
11+
12+
Note that this lint rule prevents from generating a snapshot from within any of the [async utility methods](https://testing-library.com/docs/dom-testing-library/api-async).
13+
14+
Examples of **incorrect** code for this rule:
15+
16+
```js
17+
const foo = async () => {
18+
// ...
19+
await waitFor(() => expect(container).toMatchSnapshot());
20+
// ...
21+
};
22+
23+
const bar = async () => {
24+
// ...
25+
await waitFor(() => expect(container).toMatchInlineSnapshot());
26+
// ...
27+
};
28+
29+
const baz = async () => {
30+
// ...
31+
await wait(() => {
32+
expect(container).toMatchSnapshot();
33+
});
34+
// ...
35+
};
36+
```
37+
38+
Examples of **correct** code for this rule:
39+
40+
```js
41+
const foo = () => {
42+
// ...
43+
expect(container).toMatchSnapshot();
44+
// ...
45+
};
46+
47+
const bar = () => {
48+
// ...
49+
expect(container).toMatchInlineSnapshot();
50+
// ...
51+
};
52+
```
53+
54+
## Further Reading
55+
56+
- [Async Utilities](https://testing-library.com/docs/dom-testing-library/api-async)

docs/rules/prefer-explicit-assert.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,17 @@ getByNonTestingLibraryVariant('foo');
5050

5151
## Options
5252

53-
This rule accepts a single options argument:
53+
This rule has a few options:
54+
55+
- `assertion`: this string allows defining the preferred assertion to use
56+
with `getBy*` queries. By default, any assertion is valid (`toBeTruthy`,
57+
`toBeDefined`, etc.). However, they all assert slightly different things.
58+
This option ensures all `getBy*` assertions are consistent and use the same
59+
assertion.
60+
61+
```js
62+
"testing-library/prefer-explicit-assert": ["error", {"assertion": "toBeInTheDocument"}],
63+
```
5464

5565
- `customQueryNames`: this array option allows to extend default Testing
5666
Library queries with custom ones for including them into rule

lib/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ 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 noPromiseInFireEvent from './rules/no-promise-in-fire-event';
1112
import noRenderInSetup from './rules/no-render-in-setup';
1213
import noWaitForEmptyCallback from './rules/no-wait-for-empty-callback';
13-
import noPromiseInFireEvent from './rules/no-promise-in-fire-event';
14+
import noWaitForSnapshot from './rules/no-wait-for-snapshot';
1415
import preferExplicitAssert from './rules/prefer-explicit-assert';
1516
import preferPresenceQueries from './rules/prefer-presence-queries';
1617
import preferScreenQueries from './rules/prefer-screen-queries';
@@ -37,6 +38,7 @@ const rules = {
3738
'no-render-in-setup': noRenderInSetup,
3839
'no-side-effects-wait-for': noSideEffectsWaitFor,
3940
'no-wait-for-empty-callback': noWaitForEmptyCallback,
41+
'no-wait-for-snapshot': noWaitForSnapshot,
4042
'prefer-explicit-assert': preferExplicitAssert,
4143
'prefer-find-by': preferFindBy,
4244
'prefer-presence-queries': preferPresenceQueries,

lib/node-utils.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
AST_NODE_TYPES,
3+
TSESLint,
34
TSESTree,
45
} from '@typescript-eslint/experimental-utils';
56
import { RuleContext } from '@typescript-eslint/experimental-utils/dist/ts-eslint';
@@ -77,6 +78,10 @@ export function findClosestCallExpressionNode(
7778
return node;
7879
}
7980

81+
if (!node.parent) {
82+
return null;
83+
}
84+
8085
return findClosestCallExpressionNode(node.parent);
8186
}
8287

@@ -105,7 +110,7 @@ export function isObjectExpression(
105110
return node?.type === AST_NODE_TYPES.ObjectExpression;
106111
}
107112

108-
export function hasThenProperty(node: TSESTree.Node) {
113+
export function hasThenProperty(node: TSESTree.Node): boolean {
109114
return (
110115
isMemberExpression(node) &&
111116
isIdentifier(node.property) &&
@@ -131,15 +136,15 @@ export function isReturnStatement(
131136
return node && node.type === AST_NODE_TYPES.ReturnStatement;
132137
}
133138

134-
export function isAwaited(node: TSESTree.Node) {
139+
export function isAwaited(node: TSESTree.Node): boolean {
135140
return (
136141
isAwaitExpression(node) ||
137142
isArrowFunctionExpression(node) ||
138143
isReturnStatement(node)
139144
);
140145
}
141146

142-
export function isPromiseResolved(node: TSESTree.Node) {
147+
export function isPromiseResolved(node: TSESTree.Node): boolean {
143148
const parent = node.parent;
144149

145150
// wait(...).then(...)
@@ -154,7 +159,7 @@ export function isPromiseResolved(node: TSESTree.Node) {
154159
export function getVariableReferences(
155160
context: RuleContext<string, []>,
156161
node: TSESTree.Node
157-
) {
162+
): TSESLint.Scope.Reference[] {
158163
return (
159164
(isVariableDeclarator(node) &&
160165
context.getDeclaredVariables(node)[0].references.slice(1)) ||
@@ -165,7 +170,7 @@ export function getVariableReferences(
165170
export function isRenderFunction(
166171
callNode: TSESTree.CallExpression,
167172
renderFunctions: string[]
168-
) {
173+
): boolean {
169174
// returns true for `render` and e.g. `customRenderFn`
170175
// as well as `someLib.render` and `someUtils.customRenderFn`
171176
return renderFunctions.some(name => {
@@ -181,7 +186,7 @@ export function isRenderFunction(
181186
export function isRenderVariableDeclarator(
182187
node: TSESTree.VariableDeclarator,
183188
renderFunctions: string[]
184-
) {
189+
): boolean {
185190
if (node.init) {
186191
if (isAwaitExpression(node.init)) {
187192
return (

lib/rules/await-async-utils.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
4343
) {
4444
const parent = node.parent as TSESTree.ImportDeclaration;
4545

46-
if (!LIBRARY_MODULES.includes(parent.source.value.toString())) return;
46+
if (!LIBRARY_MODULES.includes(parent.source.value.toString())) {
47+
return;
48+
}
4749

4850
if (node.type === 'ImportSpecifier') {
4951
importedAsyncUtils.push(node.imported.name);

lib/rules/consistent-data-testid.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
33
import { isJSXAttribute, isLiteral } from '../node-utils';
44

55
export const RULE_NAME = 'consistent-data-testid';
6-
export type MessageIds = 'invalidTestId';
6+
export type MessageIds = 'consistentDataTestId';
77
type Options = [
88
{
9-
testIdPattern: string;
109
testIdAttribute?: string | string[];
10+
testIdPattern: string;
1111
}
1212
];
1313

@@ -23,7 +23,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
2323
recommended: false,
2424
},
2525
messages: {
26-
invalidTestId: '`{{attr}}` "{{value}}" should match `{{regex}}`',
26+
consistentDataTestId: '`{{attr}}` "{{value}}" should match `{{regex}}`',
2727
},
2828
fixable: null,
2929
schema: [
@@ -105,7 +105,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
105105
if (value && typeof value === 'string' && !regex.test(value)) {
106106
context.report({
107107
node,
108-
messageId: 'invalidTestId',
108+
messageId: 'consistentDataTestId',
109109
data: {
110110
attr: node.name,
111111
value,

lib/rules/no-container.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import {
99
} from '../node-utils';
1010

1111
export const RULE_NAME = 'no-container';
12+
export type MessageIds = 'noContainer';
13+
type Options = [{ renderFunctions?: string[] }];
1214

13-
export default ESLintUtils.RuleCreator(getDocsUrl)({
15+
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
1416
name: RULE_NAME,
1517
meta: {
1618
type: 'problem',

lib/rules/no-debug.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
} from '../node-utils';
1717

1818
export const RULE_NAME = 'no-debug';
19+
export type MessageIds = 'noDebug';
20+
type Options = [{ renderFunctions?: string[] }];
1921

20-
export default ESLintUtils.RuleCreator(getDocsUrl)({
22+
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
2123
name: RULE_NAME,
2224
meta: {
2325
type: 'problem',
@@ -107,7 +109,10 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
107109
// checks if import has shape:
108110
// import { screen } from '@testing-library/dom';
109111
ImportDeclaration(node: TSESTree.ImportDeclaration) {
110-
if (!hasTestingLibraryImportModule(node)) return;
112+
if (!hasTestingLibraryImportModule(node)) {
113+
return;
114+
}
115+
111116
hasImportedScreen = node.specifiers.some(
112117
s => isImportSpecifier(s) && s.imported.name === 'screen'
113118
);
@@ -118,7 +123,9 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
118123
node: TSESTree.ImportNamespaceSpecifier
119124
) {
120125
const importDeclarationNode = node.parent as TSESTree.ImportDeclaration;
121-
if (!hasTestingLibraryImportModule(importDeclarationNode)) return;
126+
if (!hasTestingLibraryImportModule(importDeclarationNode)) {
127+
return;
128+
}
122129

123130
wildcardImportName = node.local && node.local.name;
124131
},

lib/rules/no-multiple-assertions-wait-for.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import {
88
} from '../node-utils';
99

1010
export const RULE_NAME = 'no-multiple-assertions-wait-for';
11-
12-
const WAIT_EXPRESSION_QUERY = 'CallExpression[callee.name=/^(waitFor)$/]';
13-
1411
export type MessageIds = 'noMultipleAssertionWaitFor';
1512
type Options = [];
1613

14+
const WAIT_EXPRESSION_QUERY = 'CallExpression[callee.name=/^(waitFor)$/]';
15+
1716
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
1817
name: RULE_NAME,
1918
meta: {

0 commit comments

Comments
 (0)