From cfd40cec54dface278487041a498cdda66b42540 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 11 Apr 2021 12:04:13 +1200 Subject: [PATCH 01/10] feat: create `prefer-to-be` --- README.md | 1 + docs/rules/prefer-to-be.md | 30 ++++++++++ .../__snapshots__/rules.test.ts.snap | 1 + src/__tests__/rules.test.ts | 2 +- src/rules/__tests__/prefer-to-be.test.ts | 60 +++++++++++++++++++ src/rules/prefer-to-be.ts | 56 +++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 docs/rules/prefer-to-be.md create mode 100644 src/rules/__tests__/prefer-to-be.test.ts create mode 100644 src/rules/prefer-to-be.ts diff --git a/README.md b/README.md index aea3318fe..2f6186117 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ installations requiring long-term consistency. | [prefer-hooks-on-top](docs/rules/prefer-hooks-on-top.md) | Suggest having hooks before any test cases | | | | [prefer-spy-on](docs/rules/prefer-spy-on.md) | Suggest using `jest.spyOn()` | | ![fixable][] | | [prefer-strict-equal](docs/rules/prefer-strict-equal.md) | Suggest using `toStrictEqual()` | | ![suggest][] | +| [prefer-to-be](docs/rules/prefer-to-be.md) | Suggest using `toBe()` for primitive literals | | ![fixable][] | | [prefer-to-be-null](docs/rules/prefer-to-be-null.md) | Suggest using `toBeNull()` | ![style][] | ![fixable][] | | [prefer-to-be-undefined](docs/rules/prefer-to-be-undefined.md) | Suggest using `toBeUndefined()` | ![style][] | ![fixable][] | | [prefer-to-contain](docs/rules/prefer-to-contain.md) | Suggest using `toContain()` | ![style][] | ![fixable][] | diff --git a/docs/rules/prefer-to-be.md b/docs/rules/prefer-to-be.md new file mode 100644 index 000000000..1082f9f66 --- /dev/null +++ b/docs/rules/prefer-to-be.md @@ -0,0 +1,30 @@ +# Suggest using `toBe()` for primitive literals (`prefer-to-be`) + +When asserting against primitive literals such as numbers and strings, the +equality matchers all operate the same, but read slightly differently in code. + +This rule recommends using the `toBe` matcher in these situations, as it forms +the most grammatically natural sentence. + +## Rule details + +This rule triggers a warning if `toEqual()` or `toStrictEqual()` are used to +assert a primitive literal value such as a string or a number. + +The following patterns are considered warnings: + +```js +expect(value).not.toEqual(5); +expect(getMessage()).toStrictEqual('hello world'); +expect(loadMessage()).resolves.toEqual('hello world'); +``` + +The following pattern is not warning: + +```js +expect(value).not.toBe(5); +expect(getMessage()).toBe('hello world'); +expect(loadMessage()).resolves.toBe('hello world'); + +expect(catchError()).toStrictEqual({ message: 'oh noes!' }); +``` diff --git a/src/__tests__/__snapshots__/rules.test.ts.snap b/src/__tests__/__snapshots__/rules.test.ts.snap index eb3178266..bf6e2af3b 100644 --- a/src/__tests__/__snapshots__/rules.test.ts.snap +++ b/src/__tests__/__snapshots__/rules.test.ts.snap @@ -40,6 +40,7 @@ Object { "jest/prefer-hooks-on-top": "error", "jest/prefer-spy-on": "error", "jest/prefer-strict-equal": "error", + "jest/prefer-to-be": "error", "jest/prefer-to-be-null": "error", "jest/prefer-to-be-undefined": "error", "jest/prefer-to-contain": "error", diff --git a/src/__tests__/rules.test.ts b/src/__tests__/rules.test.ts index 6703e7a13..88f10b1a1 100644 --- a/src/__tests__/rules.test.ts +++ b/src/__tests__/rules.test.ts @@ -2,7 +2,7 @@ import { existsSync } from 'fs'; import { resolve } from 'path'; import plugin from '../'; -const numberOfRules = 46; +const numberOfRules = 47; const ruleNames = Object.keys(plugin.rules); const deprecatedRules = Object.entries(plugin.rules) .filter(([, rule]) => rule.meta.deprecated) diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts new file mode 100644 index 000000000..d640a9bc0 --- /dev/null +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -0,0 +1,60 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import rule from '../prefer-to-be'; + +const ruleTester = new TSESLint.RuleTester(); + +ruleTester.run('prefer-to-be', rule, { + valid: [ + 'expect(null).toBeNull();', + 'expect(null).not.toBeNull();', + 'expect(null).toBe(1);', + 'expect(obj).toStrictEqual([ x, 1 ]);', + 'expect(obj).toStrictEqual({ x: 1 });', + 'expect(obj).not.toStrictEqual({ x: 1 });', + 'expect(value).toMatchSnapshot();', + "expect(catchError()).toStrictEqual({ message: 'oh noes!' })", + 'expect("something");', + ], + invalid: [ + { + code: 'expect(null).toEqual(null);', + output: 'expect(null).toBe(null);', + errors: [{ messageId: 'useToBe', column: 14, line: 1 }], + }, + { + code: 'expect(value).toEqual("my string");', + output: 'expect(value).toBe("my string");', + errors: [{ messageId: 'useToBe', column: 15, line: 1 }], + }, + { + code: 'expect(value).toStrictEqual("my string");', + output: 'expect(value).toBe("my string");', + errors: [{ messageId: 'useToBe', column: 15, line: 1 }], + }, + { + code: 'expect(loadMessage()).resolves.toStrictEqual("hello world");', + output: 'expect(loadMessage()).resolves.toBe("hello world");', + errors: [{ messageId: 'useToBe', column: 32, line: 1 }], + }, + ], +}); + +new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), +}).run('prefer-to-be: typescript edition', rule, { + valid: [ + "(expect('Model must be bound to an array if the multiple property is true') as any).toHaveBeenTipped()", + ], + invalid: [ + { + code: 'expect(null).toEqual(1 as unknown as string as unknown as any);', + output: 'expect(null).toBe(1 as unknown as string as unknown as any);', + errors: [{ messageId: 'useToBe', column: 14, line: 1 }], + }, + { + code: 'expect("a string").not.toStrictEqual(null as number);', + output: 'expect("a string").not.toBe(null as number);', + errors: [{ messageId: 'useToBe', column: 24, line: 1 }], + }, + ], +}); diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts new file mode 100644 index 000000000..20610fb31 --- /dev/null +++ b/src/rules/prefer-to-be.ts @@ -0,0 +1,56 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; +import { + EqualityMatcher, + ParsedExpectMatcher, + createRule, + followTypeAssertionChain, + isExpectCall, + isParsedEqualityMatcherCall, + parseExpectCall, +} from './utils'; + +const isPrimitiveLiteral = (matcher: ParsedExpectMatcher) => + isParsedEqualityMatcherCall(matcher) && + followTypeAssertionChain(matcher.arguments[0]).type === + AST_NODE_TYPES.Literal; + +export default createRule({ + name: __filename, + meta: { + docs: { + category: 'Best Practices', + description: 'Suggest using `toBe()` for primitive literals', + recommended: false, + }, + messages: { + useToBe: 'Use `toBe` when expecting primitive literals', + }, + fixable: 'code', + type: 'suggestion', + schema: [], + }, + defaultOptions: [], + create(context) { + return { + CallExpression(node) { + if (!isExpectCall(node)) { + return; + } + + const { matcher } = parseExpectCall(node); + + if ( + matcher && + isPrimitiveLiteral(matcher) && + !isParsedEqualityMatcherCall(matcher, EqualityMatcher.toBe) + ) { + context.report({ + fix: fixer => fixer.replaceText(matcher.node.property, 'toBe'), + messageId: 'useToBe', + node: matcher.node.property, + }); + } + }, + }; + }, +}); From 97bd9cd9b644bd6d87f40d1459d9a7ba053dd992 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 16 Jul 2021 10:41:50 +1200 Subject: [PATCH 02/10] feat(prefer-to-be): support `null` --- src/rules/__tests__/prefer-to-be.test.ts | 72 +++++++++++++++++++++--- src/rules/prefer-to-be.ts | 43 +++++++++++++- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts index d640a9bc0..6e534bb24 100644 --- a/src/rules/__tests__/prefer-to-be.test.ts +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -16,11 +16,6 @@ ruleTester.run('prefer-to-be', rule, { 'expect("something");', ], invalid: [ - { - code: 'expect(null).toEqual(null);', - output: 'expect(null).toBe(null);', - errors: [{ messageId: 'useToBe', column: 14, line: 1 }], - }, { code: 'expect(value).toEqual("my string");', output: 'expect(value).toBe("my string");', @@ -39,6 +34,59 @@ ruleTester.run('prefer-to-be', rule, { ], }); +ruleTester.run('prefer-to-be: null', rule, { + valid: [ + 'expect(null).toBeNull();', + 'expect(null).not.toBeNull();', + 'expect(null).toBe(1);', + 'expect(obj).toStrictEqual([ x, 1 ]);', + 'expect(obj).toStrictEqual({ x: 1 });', + 'expect(obj).not.toStrictEqual({ x: 1 });', + 'expect(value).toMatchSnapshot();', + "expect(catchError()).toStrictEqual({ message: 'oh noes!' })", + 'expect("something");', + // + 'expect(null).not.toEqual();', + 'expect(null).toBe();', + 'expect(null).toMatchSnapshot();', + 'expect("a string").toMatchSnapshot(null);', + 'expect("a string").not.toMatchSnapshot();', + 'expect(null).toBe', + ], + invalid: [ + { + code: 'expect(null).toBe(null);', + output: 'expect(null).toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }], + }, + { + code: 'expect(null).toEqual(null);', + output: 'expect(null).toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }], + }, + { + code: 'expect(null).toStrictEqual(null);', + output: 'expect(null).toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }], + }, + { + code: 'expect("a string").not.toBe(null);', + output: 'expect("a string").not.toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], + }, + { + code: 'expect("a string").not.toEqual(null);', + output: 'expect("a string").not.toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], + }, + { + code: 'expect("a string").not.toStrictEqual(null);', + output: 'expect("a string").not.toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], + }, + ], +}); + new TSESLint.RuleTester({ parser: require.resolve('@typescript-eslint/parser'), }).run('prefer-to-be: typescript edition', rule, { @@ -52,9 +100,19 @@ new TSESLint.RuleTester({ errors: [{ messageId: 'useToBe', column: 14, line: 1 }], }, { - code: 'expect("a string").not.toStrictEqual(null as number);', - output: 'expect("a string").not.toBe(null as number);', + code: 'expect("a string").not.toStrictEqual("string" as number);', + output: 'expect("a string").not.toBe("string" as number);', errors: [{ messageId: 'useToBe', column: 24, line: 1 }], }, + { + code: 'expect(null).toBe(null as unknown as string as unknown as any);', + output: 'expect(null).toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 14, line: 1 }], + }, + { + code: 'expect("a string").not.toEqual(null as number);', + output: 'expect("a string").not.toBeNull();', + errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], + }, ], }); diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index 20610fb31..ce3dc123c 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -1,6 +1,11 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import { EqualityMatcher, + MaybeTypeCast, + ParsedEqualityMatcherCall, ParsedExpectMatcher, createRule, followTypeAssertionChain, @@ -9,6 +14,23 @@ import { parseExpectCall, } from './utils'; +const isNullLiteral = (node: TSESTree.Node): node is TSESTree.NullLiteral => + node.type === AST_NODE_TYPES.Literal && node.value === null; + +/** + * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, + * with a `null` literal as the sole argument. + * + * @param {ParsedExpectMatcher} matcher + * + * @return {matcher is ParsedEqualityMatcherCall>} + */ +const isNullEqualityMatcher = ( + matcher: ParsedExpectMatcher, +): matcher is ParsedEqualityMatcherCall> => + isParsedEqualityMatcherCall(matcher) && + isNullLiteral(followTypeAssertionChain(matcher.arguments[0])); + const isPrimitiveLiteral = (matcher: ParsedExpectMatcher) => isParsedEqualityMatcherCall(matcher) && followTypeAssertionChain(matcher.arguments[0]).type === @@ -24,6 +46,7 @@ export default createRule({ }, messages: { useToBe: 'Use `toBe` when expecting primitive literals', + useToBeNull: 'Use `toBeNull` instead', }, fixable: 'code', type: 'suggestion', @@ -39,8 +62,24 @@ export default createRule({ const { matcher } = parseExpectCall(node); + if (!matcher || !isParsedEqualityMatcherCall(matcher)) { + return; + } + + if (isNullEqualityMatcher(matcher)) { + context.report({ + fix: fixer => [ + fixer.replaceText(matcher.node.property, 'toBeNull'), + fixer.remove(matcher.arguments[0]), + ], + messageId: 'useToBeNull', + node: matcher.node.property, + }); + + return; + } + if ( - matcher && isPrimitiveLiteral(matcher) && !isParsedEqualityMatcherCall(matcher, EqualityMatcher.toBe) ) { From 8160cef5aad858c714d97dded779ae2fbc3af763 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 16 Jul 2021 10:46:41 +1200 Subject: [PATCH 03/10] feat(prefer-to-be): support `undefined` --- src/rules/__tests__/prefer-to-be.test.ts | 58 ++++++++++++++++++++++++ src/rules/prefer-to-be.ts | 37 +++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts index 6e534bb24..d49c3f224 100644 --- a/src/rules/__tests__/prefer-to-be.test.ts +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -87,6 +87,54 @@ ruleTester.run('prefer-to-be: null', rule, { ], }); +ruleTester.run('prefer-to-be: undefined', rule, { + valid: [ + 'expect(undefined).toBeUndefined();', + 'expect(true).not.toBeUndefined();', + 'expect({}).toEqual({});', + 'expect(something).toBe()', + 'expect(something).toBe(somethingElse)', + 'expect(something).toEqual(somethingElse)', + 'expect(something).not.toBe(somethingElse)', + 'expect(something).not.toEqual(somethingElse)', + 'expect(undefined).toBe', + 'expect("something");', + ], + + invalid: [ + { + code: 'expect(undefined).toBe(undefined);', + output: 'expect(undefined).toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }], + }, + { + code: 'expect(undefined).toEqual(undefined);', + output: 'expect(undefined).toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }], + }, + { + code: 'expect(undefined).toStrictEqual(undefined);', + output: 'expect(undefined).toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }], + }, + { + code: 'expect("a string").not.toBe(undefined);', + output: 'expect("a string").not.toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + }, + { + code: 'expect("a string").not.toEqual(undefined);', + output: 'expect("a string").not.toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + }, + { + code: 'expect("a string").not.toStrictEqual(undefined);', + output: 'expect("a string").not.toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + }, + ], +}); + new TSESLint.RuleTester({ parser: require.resolve('@typescript-eslint/parser'), }).run('prefer-to-be: typescript edition', rule, { @@ -114,5 +162,15 @@ new TSESLint.RuleTester({ output: 'expect("a string").not.toBeNull();', errors: [{ messageId: 'useToBeNull', column: 24, line: 1 }], }, + { + code: 'expect(undefined).toBe(undefined as unknown as string as any);', + output: 'expect(undefined).toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }], + }, + { + code: 'expect("a string").not.toEqual(undefined as number);', + output: 'expect("a string").not.toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + }, ], }); diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index ce3dc123c..6800f6d4a 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -31,6 +31,29 @@ const isNullEqualityMatcher = ( isParsedEqualityMatcherCall(matcher) && isNullLiteral(followTypeAssertionChain(matcher.arguments[0])); +interface UndefinedIdentifier extends TSESTree.Identifier { + name: 'undefined'; +} + +const isUndefinedIdentifier = ( + node: TSESTree.Node, +): node is UndefinedIdentifier => + node.type === AST_NODE_TYPES.Identifier && node.name === 'undefined'; + +/** + * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, + * with a `undefined` identifier as the sole argument. + * + * @param {ParsedExpectMatcher} matcher + * + * @return {matcher is ParsedEqualityMatcherCall>} + */ +const isUndefinedEqualityMatcher = ( + matcher: ParsedExpectMatcher, +): matcher is ParsedEqualityMatcherCall => + isParsedEqualityMatcherCall(matcher) && + isUndefinedIdentifier(followTypeAssertionChain(matcher.arguments[0])); + const isPrimitiveLiteral = (matcher: ParsedExpectMatcher) => isParsedEqualityMatcherCall(matcher) && followTypeAssertionChain(matcher.arguments[0]).type === @@ -46,6 +69,7 @@ export default createRule({ }, messages: { useToBe: 'Use `toBe` when expecting primitive literals', + useToBeUndefined: 'Use `toBeUndefined` instead', useToBeNull: 'Use `toBeNull` instead', }, fixable: 'code', @@ -79,6 +103,19 @@ export default createRule({ return; } + if (isUndefinedEqualityMatcher(matcher)) { + context.report({ + fix: fixer => [ + fixer.replaceText(matcher.node.property, 'toBeUndefined'), + fixer.remove(matcher.arguments[0]), + ], + messageId: 'useToBeUndefined', + node: matcher.node.property, + }); + + return; + } + if ( isPrimitiveLiteral(matcher) && !isParsedEqualityMatcherCall(matcher, EqualityMatcher.toBe) From fe2754fa836357deb27856c56748a8c4b802b9bd Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 16 Jul 2021 10:50:24 +1200 Subject: [PATCH 04/10] feat(prefer-to-be): support `NaN` --- src/rules/__tests__/prefer-to-be.test.ts | 47 ++++++++++++++++++++++++ src/rules/prefer-to-be.ts | 35 ++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts index d49c3f224..0498e06be 100644 --- a/src/rules/__tests__/prefer-to-be.test.ts +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -135,6 +135,53 @@ ruleTester.run('prefer-to-be: undefined', rule, { ], }); +ruleTester.run('prefer-to-be: NaN', rule, { + valid: [ + 'expect(NaN).toBeNaN();', + 'expect(true).not.toBeNaN();', + 'expect({}).toEqual({});', + 'expect(something).toBe()', + 'expect(something).toBe(somethingElse)', + 'expect(something).toEqual(somethingElse)', + 'expect(something).not.toBe(somethingElse)', + 'expect(something).not.toEqual(somethingElse)', + 'expect(undefined).toBe', + 'expect("something");', + ], + invalid: [ + { + code: 'expect(NaN).toBe(NaN);', + output: 'expect(NaN).toBeNaN();', + errors: [{ messageId: 'useToBeNaN', column: 13, line: 1 }], + }, + { + code: 'expect(NaN).toEqual(NaN);', + output: 'expect(NaN).toBeNaN();', + errors: [{ messageId: 'useToBeNaN', column: 13, line: 1 }], + }, + { + code: 'expect(NaN).toStrictEqual(NaN);', + output: 'expect(NaN).toBeNaN();', + errors: [{ messageId: 'useToBeNaN', column: 13, line: 1 }], + }, + { + code: 'expect("a string").not.toBe(NaN);', + output: 'expect("a string").not.toBeNaN();', + errors: [{ messageId: 'useToBeNaN', column: 24, line: 1 }], + }, + { + code: 'expect("a string").not.toEqual(NaN);', + output: 'expect("a string").not.toBeNaN();', + errors: [{ messageId: 'useToBeNaN', column: 24, line: 1 }], + }, + { + code: 'expect("a string").not.toStrictEqual(NaN);', + output: 'expect("a string").not.toBeNaN();', + errors: [{ messageId: 'useToBeNaN', column: 24, line: 1 }], + }, + ], +}); + new TSESLint.RuleTester({ parser: require.resolve('@typescript-eslint/parser'), }).run('prefer-to-be: typescript edition', rule, { diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index 6800f6d4a..efb5b1fbf 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -54,6 +54,27 @@ const isUndefinedEqualityMatcher = ( isParsedEqualityMatcherCall(matcher) && isUndefinedIdentifier(followTypeAssertionChain(matcher.arguments[0])); +interface NaNIdentifier extends TSESTree.Identifier { + name: 'NaN'; +} + +const isNaNIdentifier = (node: TSESTree.Node): node is NaNIdentifier => + node.type === AST_NODE_TYPES.Identifier && node.name === 'NaN'; + +/** + * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, + * with a `NaN` identifier as the sole argument. + * + * @param {ParsedExpectMatcher} matcher + * + * @return {matcher is ParsedEqualityMatcherCall>} + */ +const isNaNEqualityMatcher = ( + matcher: ParsedExpectMatcher, +): matcher is ParsedEqualityMatcherCall => + isParsedEqualityMatcherCall(matcher) && + isNaNIdentifier(followTypeAssertionChain(matcher.arguments[0])); + const isPrimitiveLiteral = (matcher: ParsedExpectMatcher) => isParsedEqualityMatcherCall(matcher) && followTypeAssertionChain(matcher.arguments[0]).type === @@ -71,6 +92,7 @@ export default createRule({ useToBe: 'Use `toBe` when expecting primitive literals', useToBeUndefined: 'Use `toBeUndefined` instead', useToBeNull: 'Use `toBeNull` instead', + useToBeNaN: 'Use `toBeNaN` instead', }, fixable: 'code', type: 'suggestion', @@ -116,6 +138,19 @@ export default createRule({ return; } + if (isNaNEqualityMatcher(matcher)) { + context.report({ + fix: fixer => [ + fixer.replaceText(matcher.node.property, 'toBeNaN'), + fixer.remove(matcher.arguments[0]), + ], + messageId: 'useToBeNaN', + node: matcher.node.property, + }); + + return; + } + if ( isPrimitiveLiteral(matcher) && !isParsedEqualityMatcherCall(matcher, EqualityMatcher.toBe) From 45a7b837c54cf7d12601d5e35d9035e13aaed11b Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 16 Jul 2021 10:52:41 +1200 Subject: [PATCH 05/10] refactor(prefer-to-be): simplify matcher checks --- src/rules/prefer-to-be.ts | 53 ++++++++++++--------------------------- src/rules/utils.ts | 2 +- 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index efb5b1fbf..912ff9c68 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -6,10 +6,10 @@ import { EqualityMatcher, MaybeTypeCast, ParsedEqualityMatcherCall, - ParsedExpectMatcher, createRule, followTypeAssertionChain, isExpectCall, + isIdentifier, isParsedEqualityMatcherCall, parseExpectCall, } from './utils'; @@ -18,67 +18,46 @@ const isNullLiteral = (node: TSESTree.Node): node is TSESTree.NullLiteral => node.type === AST_NODE_TYPES.Literal && node.value === null; /** - * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, + * Checks if the given `ParsedEqualityMatcherCall` is a call to one of the equality matchers, * with a `null` literal as the sole argument. - * - * @param {ParsedExpectMatcher} matcher - * - * @return {matcher is ParsedEqualityMatcherCall>} */ const isNullEqualityMatcher = ( - matcher: ParsedExpectMatcher, + matcher: ParsedEqualityMatcherCall, ): matcher is ParsedEqualityMatcherCall> => - isParsedEqualityMatcherCall(matcher) && - isNullLiteral(followTypeAssertionChain(matcher.arguments[0])); + isNullLiteral(getFirstArgument(matcher)); interface UndefinedIdentifier extends TSESTree.Identifier { name: 'undefined'; } -const isUndefinedIdentifier = ( - node: TSESTree.Node, -): node is UndefinedIdentifier => - node.type === AST_NODE_TYPES.Identifier && node.name === 'undefined'; - /** - * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, + * Checks if the given `ParsedEqualityMatcherCall` is a call to one of the equality matchers, * with a `undefined` identifier as the sole argument. - * - * @param {ParsedExpectMatcher} matcher - * - * @return {matcher is ParsedEqualityMatcherCall>} */ const isUndefinedEqualityMatcher = ( - matcher: ParsedExpectMatcher, + matcher: ParsedEqualityMatcherCall, ): matcher is ParsedEqualityMatcherCall => - isParsedEqualityMatcherCall(matcher) && - isUndefinedIdentifier(followTypeAssertionChain(matcher.arguments[0])); + isIdentifier(getFirstArgument(matcher), 'undefined'); interface NaNIdentifier extends TSESTree.Identifier { name: 'NaN'; } -const isNaNIdentifier = (node: TSESTree.Node): node is NaNIdentifier => - node.type === AST_NODE_TYPES.Identifier && node.name === 'NaN'; - /** - * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers, + * Checks if the given `ParsedEqualityMatcherCall` is a call to one of the equality matchers, * with a `NaN` identifier as the sole argument. - * - * @param {ParsedExpectMatcher} matcher - * - * @return {matcher is ParsedEqualityMatcherCall>} */ const isNaNEqualityMatcher = ( - matcher: ParsedExpectMatcher, + matcher: ParsedEqualityMatcherCall, ): matcher is ParsedEqualityMatcherCall => - isParsedEqualityMatcherCall(matcher) && - isNaNIdentifier(followTypeAssertionChain(matcher.arguments[0])); + isIdentifier(getFirstArgument(matcher), 'NaN'); + +const isPrimitiveLiteral = (matcher: ParsedEqualityMatcherCall) => + getFirstArgument(matcher).type === AST_NODE_TYPES.Literal; -const isPrimitiveLiteral = (matcher: ParsedExpectMatcher) => - isParsedEqualityMatcherCall(matcher) && - followTypeAssertionChain(matcher.arguments[0]).type === - AST_NODE_TYPES.Literal; +const getFirstArgument = (matcher: ParsedEqualityMatcherCall) => { + return followTypeAssertionChain(matcher.arguments[0]); +}; export default createRule({ name: __filename, diff --git a/src/rules/utils.ts b/src/rules/utils.ts index 24ce68d34..c51c7ee29 100644 --- a/src/rules/utils.ts +++ b/src/rules/utils.ts @@ -210,7 +210,7 @@ interface KnownIdentifier extends TSESTree.Identifier { * * @template V */ -const isIdentifier = ( +export const isIdentifier = ( node: TSESTree.Node, name?: V, ): node is KnownIdentifier => From 5b843f25e9b3583f4e6e5c5ab2c2dc8352580daf Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sat, 17 Jul 2021 09:02:04 +1200 Subject: [PATCH 06/10] feat(prefer-to-be): support `toBeDefined` --- src/rules/__tests__/prefer-to-be.test.ts | 57 +++++++++++++++++---- src/rules/prefer-to-be.ts | 64 +++++++++++++++++++++--- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts index 0498e06be..34f217900 100644 --- a/src/rules/__tests__/prefer-to-be.test.ts +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -90,7 +90,7 @@ ruleTester.run('prefer-to-be: null', rule, { ruleTester.run('prefer-to-be: undefined', rule, { valid: [ 'expect(undefined).toBeUndefined();', - 'expect(true).not.toBeUndefined();', + 'expect(true).toBeDefined();', 'expect({}).toEqual({});', 'expect(something).toBe()', 'expect(something).toBe(somethingElse)', @@ -119,18 +119,18 @@ ruleTester.run('prefer-to-be: undefined', rule, { }, { code: 'expect("a string").not.toBe(undefined);', - output: 'expect("a string").not.toBeUndefined();', - errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + output: 'expect("a string").toBeDefined();', + errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }], }, { code: 'expect("a string").not.toEqual(undefined);', - output: 'expect("a string").not.toBeUndefined();', - errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + output: 'expect("a string").toBeDefined();', + errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }], }, { code: 'expect("a string").not.toStrictEqual(undefined);', - output: 'expect("a string").not.toBeUndefined();', - errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + output: 'expect("a string").toBeDefined();', + errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }], }, ], }); @@ -182,6 +182,43 @@ ruleTester.run('prefer-to-be: NaN', rule, { ], }); +ruleTester.run('prefer-to-be: undefined vs defined', rule, { + valid: [ + 'expect(NaN).toBeNaN();', + 'expect(true).not.toBeNaN();', + 'expect({}).toEqual({});', + 'expect(something).toBe()', + 'expect(something).toBe(somethingElse)', + 'expect(something).toEqual(somethingElse)', + 'expect(something).not.toBe(somethingElse)', + 'expect(something).not.toEqual(somethingElse)', + 'expect(undefined).toBe', + 'expect("something");', + ], + invalid: [ + { + code: 'expect(undefined).not.toBeDefined();', + output: 'expect(undefined).toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 23, line: 1 }], + }, + { + code: 'expect(undefined).resolves.not.toBeDefined();', + output: 'expect(undefined).resolves.toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 32, line: 1 }], + }, + { + code: 'expect("a string").not.toBeUndefined();', + output: 'expect("a string").toBeDefined();', + errors: [{ messageId: 'useToBeDefined', column: 24, line: 1 }], + }, + { + code: 'expect("a string").rejects.not.toBeUndefined();', + output: 'expect("a string").rejects.toBeDefined();', + errors: [{ messageId: 'useToBeDefined', column: 32, line: 1 }], + }, + ], +}); + new TSESLint.RuleTester({ parser: require.resolve('@typescript-eslint/parser'), }).run('prefer-to-be: typescript edition', rule, { @@ -215,9 +252,9 @@ new TSESLint.RuleTester({ errors: [{ messageId: 'useToBeUndefined', column: 19, line: 1 }], }, { - code: 'expect("a string").not.toEqual(undefined as number);', - output: 'expect("a string").not.toBeUndefined();', - errors: [{ messageId: 'useToBeUndefined', column: 24, line: 1 }], + code: 'expect("a string").toEqual(undefined as number);', + output: 'expect("a string").toBeUndefined();', + errors: [{ messageId: 'useToBeUndefined', column: 20, line: 1 }], }, ], }); diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index 912ff9c68..ceb9c1c5b 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -5,6 +5,7 @@ import { import { EqualityMatcher, MaybeTypeCast, + ModifierName, ParsedEqualityMatcherCall, createRule, followTypeAssertionChain, @@ -70,6 +71,7 @@ export default createRule({ messages: { useToBe: 'Use `toBe` when expecting primitive literals', useToBeUndefined: 'Use `toBeUndefined` instead', + useToBeDefined: 'Use `toBeDefined` instead', useToBeNull: 'Use `toBeNull` instead', useToBeNaN: 'Use `toBeNaN` instead', }, @@ -85,9 +87,36 @@ export default createRule({ return; } - const { matcher } = parseExpectCall(node); + const { matcher, modifier } = parseExpectCall(node); - if (!matcher || !isParsedEqualityMatcherCall(matcher)) { + if (!matcher) { + return; + } + + if ( + modifier && + (modifier.name === ModifierName.not || modifier.negation) && + ['toBeUndefined', 'toBeDefined'].includes(matcher.name) + ) { + const modifierNode = modifier.negation || modifier.node; + const name = matcher.name === 'toBeDefined' ? 'Undefined' : 'Defined'; + + context.report({ + fix: fixer => [ + fixer.replaceText(matcher.node.property, `toBe${name}`), + fixer.removeRange([ + modifierNode.property.range[0] - 1, + modifierNode.property.range[1], + ]), + ], + messageId: `useToBe${name}`, + node: matcher.node.property, + }); + + return; + } + + if (!isParsedEqualityMatcherCall(matcher)) { return; } @@ -105,12 +134,33 @@ export default createRule({ } if (isUndefinedEqualityMatcher(matcher)) { + const name = + modifier && + (modifier.name === ModifierName.not || modifier.negation) + ? 'Defined' + : 'Undefined'; + context.report({ - fix: fixer => [ - fixer.replaceText(matcher.node.property, 'toBeUndefined'), - fixer.remove(matcher.arguments[0]), - ], - messageId: 'useToBeUndefined', + fix(fixer) { + const fixes = [ + fixer.replaceText(matcher.node.property, `toBe${name}`), + fixer.remove(matcher.arguments[0]), + ]; + + const modifierNode = modifier?.negation || modifier?.node; + + if (modifierNode) { + fixes.push( + fixer.removeRange([ + modifierNode.property.range[0] - 1, + modifierNode.property.range[1], + ]), + ); + } + + return fixes; + }, + messageId: `useToBe${name}`, node: matcher.node.property, }); From 4eb23337be529b2b2a9ed05bbaa11f36fe40c121 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 18 Jul 2021 13:58:06 +1200 Subject: [PATCH 07/10] refactor(prefer-to-be): deduplicate `context.report` calls --- src/rules/prefer-to-be.ts | 114 +++++++++++++++++++------------------- src/rules/utils.ts | 2 +- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index ceb9c1c5b..2a875406c 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -1,5 +1,6 @@ import { AST_NODE_TYPES, + TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; import { @@ -7,6 +8,8 @@ import { MaybeTypeCast, ModifierName, ParsedEqualityMatcherCall, + ParsedExpectMatcher, + ParsedExpectModifier, createRule, followTypeAssertionChain, isExpectCall, @@ -60,6 +63,49 @@ const getFirstArgument = (matcher: ParsedEqualityMatcherCall) => { return followTypeAssertionChain(matcher.arguments[0]); }; +type MessageId = + | 'useToBe' + | 'useToBeUndefined' + | 'useToBeDefined' + | 'useToBeNull' + | 'useToBeNaN'; + +type ToBeWhat = MessageId extends `useToBe${infer M}` ? M : never; + +const reportPreferToBe = ( + context: TSESLint.RuleContext, + whatToBe: ToBeWhat, + matcher: ParsedExpectMatcher, + modifier?: ParsedExpectModifier, +) => { + const modifierNode = modifier?.negation || modifier?.node; + + context.report({ + messageId: `useToBe${whatToBe}`, + fix(fixer) { + const fixes = [ + fixer.replaceText(matcher.node.property, `toBe${whatToBe}`), + ]; + + if (matcher.arguments?.length && whatToBe !== '') { + fixes.push(fixer.remove(matcher.arguments[0])); + } + + if (modifierNode) { + fixes.push( + fixer.removeRange([ + modifierNode.property.range[0] - 1, + modifierNode.property.range[1], + ]), + ); + } + + return fixes; + }, + node: matcher.node.property, + }); +}; + export default createRule({ name: __filename, meta: { @@ -98,20 +144,12 @@ export default createRule({ (modifier.name === ModifierName.not || modifier.negation) && ['toBeUndefined', 'toBeDefined'].includes(matcher.name) ) { - const modifierNode = modifier.negation || modifier.node; - const name = matcher.name === 'toBeDefined' ? 'Undefined' : 'Defined'; - - context.report({ - fix: fixer => [ - fixer.replaceText(matcher.node.property, `toBe${name}`), - fixer.removeRange([ - modifierNode.property.range[0] - 1, - modifierNode.property.range[1], - ]), - ], - messageId: `useToBe${name}`, - node: matcher.node.property, - }); + reportPreferToBe( + context, + matcher.name === 'toBeDefined' ? 'Undefined' : 'Defined', + matcher, + modifier, + ); return; } @@ -121,14 +159,7 @@ export default createRule({ } if (isNullEqualityMatcher(matcher)) { - context.report({ - fix: fixer => [ - fixer.replaceText(matcher.node.property, 'toBeNull'), - fixer.remove(matcher.arguments[0]), - ], - messageId: 'useToBeNull', - node: matcher.node.property, - }); + reportPreferToBe(context, 'Null', matcher); return; } @@ -140,42 +171,13 @@ export default createRule({ ? 'Defined' : 'Undefined'; - context.report({ - fix(fixer) { - const fixes = [ - fixer.replaceText(matcher.node.property, `toBe${name}`), - fixer.remove(matcher.arguments[0]), - ]; - - const modifierNode = modifier?.negation || modifier?.node; - - if (modifierNode) { - fixes.push( - fixer.removeRange([ - modifierNode.property.range[0] - 1, - modifierNode.property.range[1], - ]), - ); - } - - return fixes; - }, - messageId: `useToBe${name}`, - node: matcher.node.property, - }); + reportPreferToBe(context, name, matcher, modifier); return; } if (isNaNEqualityMatcher(matcher)) { - context.report({ - fix: fixer => [ - fixer.replaceText(matcher.node.property, 'toBeNaN'), - fixer.remove(matcher.arguments[0]), - ], - messageId: 'useToBeNaN', - node: matcher.node.property, - }); + reportPreferToBe(context, 'NaN', matcher); return; } @@ -184,11 +186,7 @@ export default createRule({ isPrimitiveLiteral(matcher) && !isParsedEqualityMatcherCall(matcher, EqualityMatcher.toBe) ) { - context.report({ - fix: fixer => fixer.replaceText(matcher.node.property, 'toBe'), - messageId: 'useToBe', - node: matcher.node.property, - }); + reportPreferToBe(context, '', matcher); } }, }; diff --git a/src/rules/utils.ts b/src/rules/utils.ts index c51c7ee29..776169f58 100644 --- a/src/rules/utils.ts +++ b/src/rules/utils.ts @@ -391,7 +391,7 @@ export interface NotNegatableParsedModifier< negation?: never; } -type ParsedExpectModifier = +export type ParsedExpectModifier = | NotNegatableParsedModifier | NegatableParsedModifier; From 8201c23e6595adb474deba944f1a1bcd23897ebc Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Sun, 18 Jul 2021 16:36:46 +1200 Subject: [PATCH 08/10] refactor(prefer-to-be): simplify some checks --- src/rules/prefer-to-be.ts | 39 ++++++++------------------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/src/rules/prefer-to-be.ts b/src/rules/prefer-to-be.ts index 2a875406c..687d774b8 100644 --- a/src/rules/prefer-to-be.ts +++ b/src/rules/prefer-to-be.ts @@ -30,31 +30,10 @@ const isNullEqualityMatcher = ( ): matcher is ParsedEqualityMatcherCall> => isNullLiteral(getFirstArgument(matcher)); -interface UndefinedIdentifier extends TSESTree.Identifier { - name: 'undefined'; -} - -/** - * Checks if the given `ParsedEqualityMatcherCall` is a call to one of the equality matchers, - * with a `undefined` identifier as the sole argument. - */ -const isUndefinedEqualityMatcher = ( - matcher: ParsedEqualityMatcherCall, -): matcher is ParsedEqualityMatcherCall => - isIdentifier(getFirstArgument(matcher), 'undefined'); - -interface NaNIdentifier extends TSESTree.Identifier { - name: 'NaN'; -} - -/** - * Checks if the given `ParsedEqualityMatcherCall` is a call to one of the equality matchers, - * with a `NaN` identifier as the sole argument. - */ -const isNaNEqualityMatcher = ( +const isFirstArgumentIdentifier = ( matcher: ParsedEqualityMatcherCall, -): matcher is ParsedEqualityMatcherCall => - isIdentifier(getFirstArgument(matcher), 'NaN'); + name: string, +) => isIdentifier(getFirstArgument(matcher), name); const isPrimitiveLiteral = (matcher: ParsedEqualityMatcherCall) => getFirstArgument(matcher).type === AST_NODE_TYPES.Literal; @@ -140,8 +119,7 @@ export default createRule({ } if ( - modifier && - (modifier.name === ModifierName.not || modifier.negation) && + (modifier?.name === ModifierName.not || modifier?.negation) && ['toBeUndefined', 'toBeDefined'].includes(matcher.name) ) { reportPreferToBe( @@ -164,10 +142,9 @@ export default createRule({ return; } - if (isUndefinedEqualityMatcher(matcher)) { + if (isFirstArgumentIdentifier(matcher, 'undefined')) { const name = - modifier && - (modifier.name === ModifierName.not || modifier.negation) + modifier?.name === ModifierName.not || modifier?.negation ? 'Defined' : 'Undefined'; @@ -176,7 +153,7 @@ export default createRule({ return; } - if (isNaNEqualityMatcher(matcher)) { + if (isFirstArgumentIdentifier(matcher, 'NaN')) { reportPreferToBe(context, 'NaN', matcher); return; @@ -184,7 +161,7 @@ export default createRule({ if ( isPrimitiveLiteral(matcher) && - !isParsedEqualityMatcherCall(matcher, EqualityMatcher.toBe) + matcher.name !== EqualityMatcher.toBe ) { reportPreferToBe(context, '', matcher); } From 858a1a8fb18dc1c87ac410e1bd74eb8714704cb5 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 23 Jul 2021 09:48:27 +1200 Subject: [PATCH 09/10] docs(prefer-to-be): update with info about advanced `toBe*` matchers --- docs/rules/prefer-to-be.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/rules/prefer-to-be.md b/docs/rules/prefer-to-be.md index 1082f9f66..d892360f5 100644 --- a/docs/rules/prefer-to-be.md +++ b/docs/rules/prefer-to-be.md @@ -4,7 +4,9 @@ When asserting against primitive literals such as numbers and strings, the equality matchers all operate the same, but read slightly differently in code. This rule recommends using the `toBe` matcher in these situations, as it forms -the most grammatically natural sentence. +the most grammatically natural sentence. For `null`, `undefined`, and `NaN` this +rule recommends using their specific `toBe` matchers, as they give better error +messages as well. ## Rule details @@ -28,3 +30,23 @@ expect(loadMessage()).resolves.toBe('hello world'); expect(catchError()).toStrictEqual({ message: 'oh noes!' }); ``` + +For `null`, `undefined`, and `NaN`, this rule triggers a warning if `toBe` is +used to assert against those literal values instead of their more specific +`toBe` counterparts: + +```js +expect(value).not.toBe(undefined); +expect(getMessage()).toBe(null); +expect(countMessages()).resolves.not.toBe(NaN); +``` + +The following pattern is not warning: + +```js +expect(value).toBeDefined(); +expect(getMessage()).toBeNull(); +expect(countMessages()).resolves.not.toBeNaN(); + +expect(catchError()).toStrictEqual({ message: undefined }); +``` From 04a98849c7bc5a228b20344f1e446e13e75f67f2 Mon Sep 17 00:00:00 2001 From: Gareth Jones Date: Fri, 23 Jul 2021 16:35:33 +1200 Subject: [PATCH 10/10] chore(prefer-to-be): add a few more tests and mention boolean literals --- docs/rules/prefer-to-be.md | 3 ++- src/rules/__tests__/prefer-to-be.test.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/rules/prefer-to-be.md b/docs/rules/prefer-to-be.md index d892360f5..8611b42cf 100644 --- a/docs/rules/prefer-to-be.md +++ b/docs/rules/prefer-to-be.md @@ -11,7 +11,7 @@ messages as well. ## Rule details This rule triggers a warning if `toEqual()` or `toStrictEqual()` are used to -assert a primitive literal value such as a string or a number. +assert a primitive literal value such as numbers, strings, and booleans. The following patterns are considered warnings: @@ -27,6 +27,7 @@ The following pattern is not warning: expect(value).not.toBe(5); expect(getMessage()).toBe('hello world'); expect(loadMessage()).resolves.toBe('hello world'); +expect(didError).not.toBe(true); expect(catchError()).toStrictEqual({ message: 'oh noes!' }); ``` diff --git a/src/rules/__tests__/prefer-to-be.test.ts b/src/rules/__tests__/prefer-to-be.test.ts index 34f217900..2cf92e69b 100644 --- a/src/rules/__tests__/prefer-to-be.test.ts +++ b/src/rules/__tests__/prefer-to-be.test.ts @@ -26,11 +26,21 @@ ruleTester.run('prefer-to-be', rule, { output: 'expect(value).toBe("my string");', errors: [{ messageId: 'useToBe', column: 15, line: 1 }], }, + { + code: 'expect(value).toStrictEqual(1);', + output: 'expect(value).toBe(1);', + errors: [{ messageId: 'useToBe', column: 15, line: 1 }], + }, { code: 'expect(loadMessage()).resolves.toStrictEqual("hello world");', output: 'expect(loadMessage()).resolves.toBe("hello world");', errors: [{ messageId: 'useToBe', column: 32, line: 1 }], }, + { + code: 'expect(loadMessage()).resolves.toStrictEqual(false);', + output: 'expect(loadMessage()).resolves.toBe(false);', + errors: [{ messageId: 'useToBe', column: 32, line: 1 }], + }, ], });