Skip to content

Commit

Permalink
chore: merge master into v4 (#233)
Browse files Browse the repository at this point in the history
* 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 <5247455+skovy@users.noreply.github.com>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
Co-authored-by: Giorgio Polvara <polvara@gmail.com>
Co-authored-by: Josh David <joshua.david@uqconnect.edu.au>
Co-authored-by: Mario Beltrán Alarcón <belco90@gmail.com>
  • Loading branch information
6 people committed Sep 20, 2020
1 parent 44de9fc commit 4b7300e
Show file tree
Hide file tree
Showing 29 changed files with 617 additions and 15,086 deletions.
31 changes: 31 additions & 0 deletions .all-contributorsrc
Expand Up @@ -335,8 +335,39 @@
"contributions": [
"code",
"test",
"doc",
"ideas"
]
},
{
"login": "Gpx",
"name": "Giorgio Polvara",
"avatar_url": "https://avatars0.githubusercontent.com/u/767959?v=4",
"profile": "https://twitter.com/Gpx",
"contributions": [
"code",
"test",
"doc"
]
},
{
"login": "jdanil",
"name": "Josh David",
"avatar_url": "https://avatars0.githubusercontent.com/u/8342105?v=4",
"profile": "https://github.com/jdanil",
"contributions": [
"doc"
]
},
{
"login": "MichaelDeBoey",
"name": "Michaël De Boey",
"avatar_url": "https://avatars3.githubusercontent.com/u/6643991?v=4",
"profile": "https://michaeldeboey.be",
"contributions": [
"code",
"platform"
]
}
],
"contributorsPerLine": 7,
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
@@ -1,4 +1,4 @@
# Output
# Output
dist

# Logs
Expand Down Expand Up @@ -66,3 +66,8 @@ yarn-error.log
.pnp.js
# Yarn Integrity file
.yarn-integrity

# these cause more harm than good
# when working with contributors
package-lock.json
yarn.lock
9 changes: 6 additions & 3 deletions .travis.yml
Expand Up @@ -6,9 +6,12 @@ env:
matrix:
- ESLINT=5
- ESLINT=6
- ESLINT=7

node_js:
- 10.12
- 10
- 12.0
- 12
- 14

Expand All @@ -19,14 +22,14 @@ jobs:
include:
- stage: validation
node_js: 14
env: ESLINT=6
env: ESLINT=7
script:
- npm run format:check
- npm run lint -- --max-warnings 0
- stage: release
if: branch = master AND type != pull_request
if: branch = master AND type != pull_request AND fork = false
node_js: 14
env: ESLINT=6
env: ESLINT=7
script: npm run build
deploy:
provider: script
Expand Down
10 changes: 7 additions & 3 deletions README.md
Expand Up @@ -24,7 +24,7 @@

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

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

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

Expand Down Expand Up @@ -138,10 +138,11 @@ To enable this configuration use the `extends` property in your
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
| [no-multiple-assertions-wait-for](docs/rules/no-multiple-assertions-wait-for.md) | Disallow the use of multiple expect inside `waitFor` | | |
| [no-node-access](docs/rules/no-node-access.md) | Disallow direct Node access | ![angular-badge][] ![react-badge][] ![vue-badge][] | |
| [no-render-in-setup](docs/rules/no-render-in-setup.md) | Disallow the use of `render` in setup functions | | |
| [no-promise-in-fire-event](docs/rules/no-promise-in-fire-event.md) | Disallow the use of promises passed to a `fireEvent` method | | |
| [no-render-in-setup](docs/rules/no-render-in-setup.md) | Disallow the use of `render` in setup functions | | |
| [no-side-effects-wait-for](docs/rules/no-side-effects-wait-for.md) | Disallow the use of side effects inside `waitFor` | | |
| [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][] | |
| [no-wait-for-snapshot](docs/rules/no-wait-for-snapshot.md) | Ensures no snapshot is generated inside of a `waitFor` call | | |
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
| [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][] |
| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | |
Expand Down Expand Up @@ -217,7 +218,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<tr>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
</tr>
</table>

Expand Down
4 changes: 3 additions & 1 deletion commitlint.config.js
@@ -1 +1,3 @@
module.exports = { extends: ['@commitlint/config-conventional'] };
module.exports = {
extends: ['@commitlint/config-conventional'],
};
56 changes: 56 additions & 0 deletions docs/rules/no-wait-for-snapshot.md
@@ -0,0 +1,56 @@
# Ensures no snapshot is generated inside of a `wait` call' (no-wait-for-snapshot)

Ensure that no calls to `toMatchSnapshot` or `toMatchInlineSnapshot` are made from within a `waitFor` method (or any of the other async utility methods).

## Rule Details

The `waitFor()` method runs in a timer loop. So it'll retry every n amount of time.
If a snapshot is generated inside the wait condition, jest will generate one snapshot per loop.

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.

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).

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

```js
const foo = async () => {
// ...
await waitFor(() => expect(container).toMatchSnapshot());
// ...
};

const bar = async () => {
// ...
await waitFor(() => expect(container).toMatchInlineSnapshot());
// ...
};

const baz = async () => {
// ...
await wait(() => {
expect(container).toMatchSnapshot();
});
// ...
};
```

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

```js
const foo = () => {
// ...
expect(container).toMatchSnapshot();
// ...
};

const bar = () => {
// ...
expect(container).toMatchInlineSnapshot();
// ...
};
```

## Further Reading

- [Async Utilities](https://testing-library.com/docs/dom-testing-library/api-async)
12 changes: 11 additions & 1 deletion docs/rules/prefer-explicit-assert.md
Expand Up @@ -50,7 +50,17 @@ getByNonTestingLibraryVariant('foo');

## Options

This rule accepts a single options argument:
This rule has a few options:

- `assertion`: this string allows defining the preferred assertion to use
with `getBy*` queries. By default, any assertion is valid (`toBeTruthy`,
`toBeDefined`, etc.). However, they all assert slightly different things.
This option ensures all `getBy*` assertions are consistent and use the same
assertion.

```js
"testing-library/prefer-explicit-assert": ["error", {"assertion": "toBeInTheDocument"}],
```

- `customQueryNames`: this array option allows to extend default Testing
Library queries with custom ones for including them into rule
Expand Down
4 changes: 3 additions & 1 deletion lib/index.ts
Expand Up @@ -8,9 +8,10 @@ import noDebug from './rules/no-debug';
import noDomImport from './rules/no-dom-import';
import noManualCleanup from './rules/no-manual-cleanup';
import noNodeAccess from './rules/no-node-access';
import noPromiseInFireEvent from './rules/no-promise-in-fire-event';
import noRenderInSetup from './rules/no-render-in-setup';
import noWaitForEmptyCallback from './rules/no-wait-for-empty-callback';
import noPromiseInFireEvent from './rules/no-promise-in-fire-event';
import noWaitForSnapshot from './rules/no-wait-for-snapshot';
import preferExplicitAssert from './rules/prefer-explicit-assert';
import preferPresenceQueries from './rules/prefer-presence-queries';
import preferScreenQueries from './rules/prefer-screen-queries';
Expand All @@ -37,6 +38,7 @@ const rules = {
'no-render-in-setup': noRenderInSetup,
'no-side-effects-wait-for': noSideEffectsWaitFor,
'no-wait-for-empty-callback': noWaitForEmptyCallback,
'no-wait-for-snapshot': noWaitForSnapshot,
'prefer-explicit-assert': preferExplicitAssert,
'prefer-find-by': preferFindBy,
'prefer-presence-queries': preferPresenceQueries,
Expand Down
17 changes: 11 additions & 6 deletions lib/node-utils.ts
@@ -1,5 +1,6 @@
import {
AST_NODE_TYPES,
TSESLint,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import { RuleContext } from '@typescript-eslint/experimental-utils/dist/ts-eslint';
Expand Down Expand Up @@ -77,6 +78,10 @@ export function findClosestCallExpressionNode(
return node;
}

if (!node.parent) {
return null;
}

return findClosestCallExpressionNode(node.parent);
}

Expand Down Expand Up @@ -105,7 +110,7 @@ export function isObjectExpression(
return node?.type === AST_NODE_TYPES.ObjectExpression;
}

export function hasThenProperty(node: TSESTree.Node) {
export function hasThenProperty(node: TSESTree.Node): boolean {
return (
isMemberExpression(node) &&
isIdentifier(node.property) &&
Expand All @@ -131,15 +136,15 @@ export function isReturnStatement(
return node && node.type === AST_NODE_TYPES.ReturnStatement;
}

export function isAwaited(node: TSESTree.Node) {
export function isAwaited(node: TSESTree.Node): boolean {
return (
isAwaitExpression(node) ||
isArrowFunctionExpression(node) ||
isReturnStatement(node)
);
}

export function isPromiseResolved(node: TSESTree.Node) {
export function isPromiseResolved(node: TSESTree.Node): boolean {
const parent = node.parent;

// wait(...).then(...)
Expand All @@ -154,7 +159,7 @@ export function isPromiseResolved(node: TSESTree.Node) {
export function getVariableReferences(
context: RuleContext<string, []>,
node: TSESTree.Node
) {
): TSESLint.Scope.Reference[] {
return (
(isVariableDeclarator(node) &&
context.getDeclaredVariables(node)[0].references.slice(1)) ||
Expand All @@ -165,7 +170,7 @@ export function getVariableReferences(
export function isRenderFunction(
callNode: TSESTree.CallExpression,
renderFunctions: string[]
) {
): boolean {
// returns true for `render` and e.g. `customRenderFn`
// as well as `someLib.render` and `someUtils.customRenderFn`
return renderFunctions.some(name => {
Expand All @@ -181,7 +186,7 @@ export function isRenderFunction(
export function isRenderVariableDeclarator(
node: TSESTree.VariableDeclarator,
renderFunctions: string[]
) {
): boolean {
if (node.init) {
if (isAwaitExpression(node.init)) {
return (
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/await-async-utils.ts
Expand Up @@ -43,7 +43,9 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
) {
const parent = node.parent as TSESTree.ImportDeclaration;

if (!LIBRARY_MODULES.includes(parent.source.value.toString())) return;
if (!LIBRARY_MODULES.includes(parent.source.value.toString())) {
return;
}

if (node.type === 'ImportSpecifier') {
importedAsyncUtils.push(node.imported.name);
Expand Down
8 changes: 4 additions & 4 deletions lib/rules/consistent-data-testid.ts
Expand Up @@ -3,11 +3,11 @@ import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
import { isJSXAttribute, isLiteral } from '../node-utils';

export const RULE_NAME = 'consistent-data-testid';
export type MessageIds = 'invalidTestId';
export type MessageIds = 'consistentDataTestId';
type Options = [
{
testIdPattern: string;
testIdAttribute?: string | string[];
testIdPattern: string;
}
];

Expand All @@ -23,7 +23,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
recommended: false,
},
messages: {
invalidTestId: '`{{attr}}` "{{value}}" should match `{{regex}}`',
consistentDataTestId: '`{{attr}}` "{{value}}" should match `{{regex}}`',
},
fixable: null,
schema: [
Expand Down Expand Up @@ -105,7 +105,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
if (value && typeof value === 'string' && !regex.test(value)) {
context.report({
node,
messageId: 'invalidTestId',
messageId: 'consistentDataTestId',
data: {
attr: node.name,
value,
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-container.ts
Expand Up @@ -9,8 +9,10 @@ import {
} from '../node-utils';

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

export default ESLintUtils.RuleCreator(getDocsUrl)({
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
name: RULE_NAME,
meta: {
type: 'problem',
Expand Down
13 changes: 10 additions & 3 deletions lib/rules/no-debug.ts
Expand Up @@ -16,8 +16,10 @@ import {
} from '../node-utils';

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

export default ESLintUtils.RuleCreator(getDocsUrl)({
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
name: RULE_NAME,
meta: {
type: 'problem',
Expand Down Expand Up @@ -107,7 +109,10 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
// checks if import has shape:
// import { screen } from '@testing-library/dom';
ImportDeclaration(node: TSESTree.ImportDeclaration) {
if (!hasTestingLibraryImportModule(node)) return;
if (!hasTestingLibraryImportModule(node)) {
return;
}

hasImportedScreen = node.specifiers.some(
s => isImportSpecifier(s) && s.imported.name === 'screen'
);
Expand All @@ -118,7 +123,9 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
node: TSESTree.ImportNamespaceSpecifier
) {
const importDeclarationNode = node.parent as TSESTree.ImportDeclaration;
if (!hasTestingLibraryImportModule(importDeclarationNode)) return;
if (!hasTestingLibraryImportModule(importDeclarationNode)) {
return;
}

wildcardImportName = node.local && node.local.name;
},
Expand Down
5 changes: 2 additions & 3 deletions lib/rules/no-multiple-assertions-wait-for.ts
Expand Up @@ -8,12 +8,11 @@ import {
} from '../node-utils';

export const RULE_NAME = 'no-multiple-assertions-wait-for';

const WAIT_EXPRESSION_QUERY = 'CallExpression[callee.name=/^(waitFor)$/]';

export type MessageIds = 'noMultipleAssertionWaitFor';
type Options = [];

const WAIT_EXPRESSION_QUERY = 'CallExpression[callee.name=/^(waitFor)$/]';

export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
name: RULE_NAME,
meta: {
Expand Down

0 comments on commit 4b7300e

Please sign in to comment.