Skip to content

Commit

Permalink
fix(prefer-explicit-assert): only enforce assertion when presence/abs…
Browse files Browse the repository at this point in the history
…ence matcher (#238)

Relates to #218
  • Loading branch information
skovy committed Oct 16, 2020
1 parent 6ce0140 commit f78720d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 13 deletions.
6 changes: 5 additions & 1 deletion docs/rules/prefer-explicit-assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ This rule has a few options:
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.
assertion. This rule only allows defining a presence matcher
(`toBeInTheDocument`, `toBeTruthy`, or `toBeDefined`), but checks for both
presence and absence matchers (`not.toBeFalsy` and `not.toBeNull`). This means
other assertions such as `toHaveValue` or `toBeDisabled` will not trigger this
rule since these are valid uses with `getBy*`.

```js
"testing-library/prefer-explicit-assert": ["error", {"assertion": "toBeInTheDocument"}],
Expand Down
38 changes: 31 additions & 7 deletions lib/rules/prefer-explicit-assert.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
import { getDocsUrl, ALL_QUERIES_METHODS } from '../utils';
import { isMemberExpression } from '../node-utils';
import {
getDocsUrl,
ALL_QUERIES_METHODS,
PRESENCE_MATCHERS,
ABSENCE_MATCHERS,
} from '../utils';
import {
findClosestCallNode,
isIdentifier,
isMemberExpression,
} from '../node-utils';

export const RULE_NAME = 'prefer-explicit-assert';
export type MessageIds =
Expand Down Expand Up @@ -48,6 +57,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
properties: {
assertion: {
type: 'string',
enum: PRESENCE_MATCHERS,
},
customQueryNames: {
type: 'array',
Expand Down Expand Up @@ -84,15 +94,29 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
messageId: 'preferExplicitAssert',
});
} else if (assertion) {
const expectation = node.parent.parent.parent;
const expectCallNode = findClosestCallNode(node, 'expect');

const expectStatement = expectCallNode.parent as TSESTree.MemberExpression;
const property = expectStatement.property as TSESTree.Identifier;
let matcher = property.name;
let isNegatedMatcher = false;

if (
expectation.type === 'MemberExpression' &&
expectation.property.type === 'Identifier' &&
expectation.property.name !== assertion
matcher === 'not' &&
isMemberExpression(expectStatement.parent) &&
isIdentifier(expectStatement.parent.property)
) {
isNegatedMatcher = true;
matcher = expectStatement.parent.property.name;
}

const shouldEnforceAssertion =
(!isNegatedMatcher && PRESENCE_MATCHERS.includes(matcher)) ||
(isNegatedMatcher && ABSENCE_MATCHERS.includes(matcher));

if (shouldEnforceAssertion && matcher !== assertion) {
context.report({
node: expectation.property,
node: property,
messageId: 'preferExplicitAssertAssertion',
data: {
assertion,
Expand Down
4 changes: 1 addition & 3 deletions lib/rules/prefer-presence-queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils';
import { getDocsUrl, ALL_QUERIES_METHODS } from '../utils';
import { getDocsUrl, ALL_QUERIES_METHODS, PRESENCE_MATCHERS, ABSENCE_MATCHERS } from '../utils';
import {
findClosestCallNode,
isMemberExpression,
Expand All @@ -13,8 +13,6 @@ type Options = [];
const QUERIES_REGEXP = new RegExp(
`^(get|query)(All)?(${ALL_QUERIES_METHODS.join('|')})$`
);
const PRESENCE_MATCHERS = ['toBeInTheDocument', 'toBeTruthy', 'toBeDefined'];
const ABSENCE_MATCHERS = ['toBeNull', 'toBeFalsy'];

function isThrowingQuery(node: TSESTree.Identifier) {
return node.name.startsWith('get');
Expand Down
5 changes: 5 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ const ASYNC_UTILS = [

const TESTING_FRAMEWORK_SETUP_HOOKS = ['beforeEach', 'beforeAll'];

const PRESENCE_MATCHERS = ['toBeInTheDocument', 'toBeTruthy', 'toBeDefined'];
const ABSENCE_MATCHERS = ['toBeNull', 'toBeFalsy'];

export {
getDocsUrl,
SYNC_QUERIES_VARIANTS,
Expand All @@ -77,4 +80,6 @@ export {
ASYNC_UTILS,
TESTING_FRAMEWORK_SETUP_HOOKS,
LIBRARY_MODULES,
PRESENCE_MATCHERS,
ABSENCE_MATCHERS
};
42 changes: 40 additions & 2 deletions tests/lib/rules/prefer-explicit-assert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ ruleTester.run(RULE_NAME, rule, {
},
],
},
{
code: `expect(getByText('foo')).toBeEnabled()`,
options: [
{
assertion: 'toBeInTheDocument',
},
],
},
],

invalid: [
Expand Down Expand Up @@ -144,14 +152,44 @@ ruleTester.run(RULE_NAME, rule, {
code: `expect(getByText('foo')).toBeDefined()`,
options: [
{
assertion: 'toBeInDocument',
assertion: 'toBeInTheDocument',
},
],
errors: [
{
messageId: 'preferExplicitAssertAssertion',
column: 26,
data: { assertion: 'toBeInTheDocument' },
},
],
},
{
code: `expect(getByText('foo')).not.toBeNull()`,
options: [
{
assertion: 'toBeInTheDocument',
},
],
errors: [
{
messageId: 'preferExplicitAssertAssertion',
column: 26,
data: { assertion: 'toBeInTheDocument' },
},
],
},
{
code: `expect(getByText('foo')).not.toBeFalsy()`,
options: [
{
assertion: 'toBeInTheDocument',
},
],
errors: [
{
messageId: 'preferExplicitAssertAssertion',
column: 26,
data: { assertion: 'toBeInDocument' },
data: { assertion: 'toBeInTheDocument' },
},
],
},
Expand Down

0 comments on commit f78720d

Please sign in to comment.