Files

Return to Package Diff Home.
Brought to you by Intrinsic.

Package Diff: eslint-plugin-jest @ 22.5.1 .. 22.6.4

docs/rules/no-commented-out-tests.md

@@ -0,0 +1,61 @@
+# Disallow commented out tests (no-commented-out-tests)
+
+This rule raises a warning about commented out tests. It's similar to
+no-disabled-tests rule.
+
+## Rule Details
+
+The rule uses fuzzy matching to do its best to determine what constitutes a
+commented out test, checking for a presence of `it(`, `describe(`, `it.skip(`,
+etc. in code comments.
+
+The following patterns are considered warnings:
+
+```js
+// describe('foo', () => {});
+// it('foo', () => {});
+// test('foo', () => {});
+
+// describe.skip('foo', () => {});
+// it.skip('foo', () => {});
+// test.skip('foo', () => {});
+
+// describe['skip']('bar', () => {});
+// it['skip']('bar', () => {});
+// test['skip']('bar', () => {});
+
+// xdescribe('foo', () => {});
+// xit('foo', () => {});
+// xtest('foo', () => {});
+
+/*
+describe('foo', () => {});
+*/
+```
+
+These patterns would not be considered warnings:
+
+```js
+describe('foo', () => {});
+it('foo', () => {});
+test('foo', () => {});
+
+describe.only('bar', () => {});
+it.only('bar', () => {});
+test.only('bar', () => {});
+
+// foo('bar', () => {});
+```
+
+### Limitations
+
+The plugin looks at the literal function names within test code, so will not
+catch more complex examples of commented out tests, such as:
+
+```js
+// const testSkip = test.skip;
+// testSkip('skipped test', () => {});
+
+// const myTest = test;
+// myTest('does not have function body');
+```

index.js

@@ -1,75 +0,0 @@
-'use strict';
-
-const fs = require('fs');
-const path = require('path');
-
-const rules = fs
- .readdirSync(path.join(__dirname, 'rules'))
- .filter(rule => rule !== '__tests__' && rule !== 'util.js')
- .map(rule => path.basename(rule, '.js'))
- .reduce(
- (acc, curr) => Object.assign(acc, { [curr]: require(`./rules/${curr}`) }),
- {}
- );
-
-const snapshotProcessor = require('./processors/snapshot-processor');
-
-module.exports = {
- configs: {
- recommended: {
- plugins: ['jest'],
- env: {
- 'jest/globals': true,
- },
- rules: {
- 'jest/no-alias-methods': 'warn',
- 'jest/no-disabled-tests': 'warn',
- 'jest/no-focused-tests': 'error',
- 'jest/no-identical-title': 'error',
- 'jest/no-jest-import': 'error',
- // 'jest/no-mocks-import': 'error',
- 'jest/no-jasmine-globals': 'warn',
- 'jest/no-test-prefixes': 'error',
- 'jest/valid-describe': 'error',
- 'jest/valid-expect': 'error',
- 'jest/valid-expect-in-promise': 'error',
- },
- },
- style: {
- plugins: ['jest'],
- rules: {
- 'jest/prefer-to-be-null': 'error',
- 'jest/prefer-to-be-undefined': 'error',
- 'jest/prefer-to-contain': 'error',
- 'jest/prefer-to-have-length': 'error',
- },
- },
- },
- environments: {
- globals: {
- globals: {
- afterAll: false,
- afterEach: false,
- beforeAll: false,
- beforeEach: false,
- describe: false,
- expect: false,
- fit: false,
- it: false,
- jasmine: false,
- jest: false,
- pending: false,
- pit: false,
- require: false,
- test: false,
- xdescribe: false,
- xit: false,
- xtest: false,
- },
- },
- },
- processors: {
- '.snap': snapshotProcessor,
- },
- rules,
-};

lib/index.js

@@ -0,0 +1,71 @@
+'use strict';
+
+const fs = require('fs');
+
+const path = require('path');
+
+const rules = fs.readdirSync(path.join(__dirname, 'rules')).filter(rule => rule !== '__tests__' && rule !== 'util.js').map(rule => path.basename(rule, '.js')).reduce((acc, curr) => Object.assign(acc, {
+ [curr]: require(`./rules/${curr}`)
+}), {});
+
+const snapshotProcessor = require('./processors/snapshot-processor');
+
+module.exports = {
+ configs: {
+ recommended: {
+ plugins: ['jest'],
+ env: {
+ 'jest/globals': true
+ },
+ rules: {
+ 'jest/no-alias-methods': 'warn',
+ 'jest/no-disabled-tests': 'warn',
+ 'jest/no-focused-tests': 'error',
+ 'jest/no-identical-title': 'error',
+ 'jest/no-jest-import': 'error',
+ // 'jest/no-mocks-import': 'error',
+ 'jest/no-jasmine-globals': 'warn',
+ 'jest/no-test-prefixes': 'error',
+ 'jest/valid-describe': 'error',
+ 'jest/valid-expect': 'error',
+ 'jest/valid-expect-in-promise': 'error'
+ }
+ },
+ style: {
+ plugins: ['jest'],
+ rules: {
+ 'jest/prefer-to-be-null': 'error',
+ 'jest/prefer-to-be-undefined': 'error',
+ 'jest/prefer-to-contain': 'error',
+ 'jest/prefer-to-have-length': 'error'
+ }
+ }
+ },
+ environments: {
+ globals: {
+ globals: {
+ afterAll: false,
+ afterEach: false,
+ beforeAll: false,
+ beforeEach: false,
+ describe: false,
+ expect: false,
+ fit: false,
+ it: false,
+ jasmine: false,
+ jest: false,
+ pending: false,
+ pit: false,
+ require: false,
+ test: false,
+ xdescribe: false,
+ xit: false,
+ xtest: false
+ }
+ }
+ },
+ processors: {
+ '.snap': snapshotProcessor
+ },
+ rules
+};
\ No newline at end of file

lib/processors/snapshot-processor.js

@@ -0,0 +1,11 @@
+'use strict'; // https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
+
+const preprocess = source => [source];
+
+const postprocess = messages => messages[0].filter( // snapshot files should only be linted with snapshot specific rules
+message => message.ruleId === 'jest/no-large-snapshots');
+
+module.exports = {
+ preprocess,
+ postprocess
+};
\ No newline at end of file

lib/processors/__tests__/snapshot-processor.test.js

@@ -0,0 +1,35 @@
+'use strict';
+
+const snapshotProcessor = require('../snapshot-processor');
+
+describe('snapshot-processor', () => {
+ it('exports an object with preprocess and postprocess functions', () => {
+ expect(snapshotProcessor).toMatchObject({
+ preprocess: expect.any(Function),
+ postprocess: expect.any(Function)
+ });
+ });
+ describe('preprocess function', () => {
+ it('should pass on untouched source code to source array', () => {
+ const preprocess = snapshotProcessor.preprocess;
+ const sourceCode = "const name = 'johnny bravo';";
+ const result = preprocess(sourceCode);
+ expect(result).toEqual([sourceCode]);
+ });
+ });
+ describe('postprocess function', () => {
+ it('should only return messages about snapshot specific rules', () => {
+ const postprocess = snapshotProcessor.postprocess;
+ const result = postprocess([[{
+ ruleId: 'no-console'
+ }, {
+ ruleId: 'global-require'
+ }, {
+ ruleId: 'jest/no-large-snapshots'
+ }]]);
+ expect(result).toEqual([{
+ ruleId: 'jest/no-large-snapshots'
+ }]);
+ });
+ });
+});
\ No newline at end of file

lib/rules/consistent-test-it.js

@@ -0,0 +1,108 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ getNodeName = _require.getNodeName,
+ isTestCase = _require.isTestCase,
+ isDescribe = _require.isDescribe;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code',
+ schema: [{
+ type: 'object',
+ properties: {
+ fn: {
+ enum: ['it', 'test']
+ },
+ withinDescribe: {
+ enum: ['it', 'test']
+ }
+ },
+ additionalProperties: false
+ }]
+ },
+
+ create(context) {
+ const configObj = context.options[0] || {};
+ const testKeyword = configObj.fn || 'test';
+ const testKeywordWithinDescribe = configObj.withinDescribe || configObj.fn || 'it';
+ let describeNestingLevel = 0;
+ return {
+ CallExpression(node) {
+ const nodeName = getNodeName(node.callee);
+
+ if (isDescribe(node)) {
+ describeNestingLevel++;
+ }
+
+ if (isTestCase(node) && describeNestingLevel === 0 && nodeName.indexOf(testKeyword) === -1) {
+ const oppositeTestKeyword = getOppositeTestKeyword(testKeyword);
+ context.report({
+ message: "Prefer using '{{ testKeyword }}' instead of '{{ oppositeTestKeyword }}'",
+ node: node.callee,
+ data: {
+ testKeyword,
+ oppositeTestKeyword
+ },
+
+ fix(fixer) {
+ const nodeToReplace = node.callee.type === 'MemberExpression' ? node.callee.object : node.callee;
+ const fixedNodeName = getPreferredNodeName(nodeName, testKeyword);
+ return [fixer.replaceText(nodeToReplace, fixedNodeName)];
+ }
+
+ });
+ }
+
+ if (isTestCase(node) && describeNestingLevel > 0 && nodeName.indexOf(testKeywordWithinDescribe) === -1) {
+ const oppositeTestKeyword = getOppositeTestKeyword(testKeywordWithinDescribe);
+ context.report({
+ message: "Prefer using '{{ testKeywordWithinDescribe }}' instead of '{{ oppositeTestKeyword }}' within describe",
+ node: node.callee,
+ data: {
+ testKeywordWithinDescribe,
+ oppositeTestKeyword
+ },
+
+ fix(fixer) {
+ const nodeToReplace = node.callee.type === 'MemberExpression' ? node.callee.object : node.callee;
+ const fixedNodeName = getPreferredNodeName(nodeName, testKeywordWithinDescribe);
+ return [fixer.replaceText(nodeToReplace, fixedNodeName)];
+ }
+
+ });
+ }
+ },
+
+ 'CallExpression:exit'(node) {
+ if (isDescribe(node)) {
+ describeNestingLevel--;
+ }
+ }
+
+ };
+ }
+
+};
+
+function getPreferredNodeName(nodeName, preferredTestKeyword) {
+ switch (nodeName) {
+ case 'fit':
+ return 'test.only';
+
+ default:
+ return nodeName.startsWith('f') || nodeName.startsWith('x') ? nodeName.charAt(0) + preferredTestKeyword : preferredTestKeyword;
+ }
+}
+
+function getOppositeTestKeyword(test) {
+ if (test === 'test') {
+ return 'it';
+ }
+
+ return 'test';
+}
\ No newline at end of file

lib/rules/expect-expect.js

@@ -0,0 +1,82 @@
+'use strict';
+/*
+ * This implementation is adapted from eslint-plugin-jasmine.
+ * MIT license, Remco Haszing.
+ */
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ getNodeName = _require.getNodeName;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ schema: [{
+ type: 'object',
+ properties: {
+ assertFunctionNames: {
+ type: 'array',
+ items: [{
+ type: 'string'
+ }]
+ }
+ },
+ additionalProperties: false
+ }]
+ },
+
+ create(context) {
+ const unchecked = [];
+ const assertFunctionNames = new Set(context.options[0] && context.options[0].assertFunctionNames ? context.options[0].assertFunctionNames : ['expect']);
+ return {
+ CallExpression(node) {
+ const name = getNodeName(node.callee);
+
+ if (name === 'it' || name === 'test') {
+ unchecked.push(node);
+ } else if (assertFunctionNames.has(name)) {
+ // Return early in case of nested `it` statements.
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = context.getAncestors()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ const ancestor = _step.value;
+ const index = unchecked.indexOf(ancestor);
+
+ if (index !== -1) {
+ unchecked.splice(index, 1);
+ break;
+ }
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+ }
+ },
+
+ 'Program:exit'() {
+ unchecked.forEach(node => context.report({
+ message: 'Test has no assertions',
+ node
+ }));
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/lowercase-name.js

@@ -0,0 +1,98 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+const isItTestOrDescribeFunction = node => {
+ return node.type === 'CallExpression' && node.callee && (node.callee.name === 'it' || node.callee.name === 'test' || node.callee.name === 'describe');
+};
+
+const isItDescription = node => {
+ return node.arguments && node.arguments[0] && (node.arguments[0].type === 'Literal' || node.arguments[0].type === 'TemplateLiteral');
+};
+
+const testDescription = node => {
+ const _node$arguments = _slicedToArray(node.arguments, 1),
+ firstArgument = _node$arguments[0];
+
+ const type = firstArgument.type;
+
+ if (type === 'Literal') {
+ return firstArgument.value;
+ } // `isItDescription` guarantees this is `type === 'TemplateLiteral'`
+
+
+ return firstArgument.quasis[0].value.raw;
+};
+
+const descriptionBeginsWithLowerCase = node => {
+ if (isItTestOrDescribeFunction(node) && isItDescription(node)) {
+ const description = testDescription(node);
+
+ if (!description[0]) {
+ return false;
+ }
+
+ if (description[0] !== description[0].toLowerCase()) {
+ return node.callee.name;
+ }
+ }
+
+ return false;
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ const ignore = context.options[0] && context.options[0].ignore || [];
+ const ignoredFunctionNames = ignore.reduce((accumulator, value) => {
+ accumulator[value] = true;
+ return accumulator;
+ }, Object.create(null));
+
+ const isIgnoredFunctionName = node => ignoredFunctionNames[node.callee.name];
+
+ return {
+ CallExpression(node) {
+ const erroneousMethod = descriptionBeginsWithLowerCase(node);
+
+ if (erroneousMethod && !isIgnoredFunctionName(node)) {
+ context.report({
+ message: '`{{ method }}`s should begin with lowercase',
+ data: {
+ method: erroneousMethod
+ },
+ node,
+
+ fix(fixer) {
+ const _node$arguments2 = _slicedToArray(node.arguments, 1),
+ firstArg = _node$arguments2[0];
+
+ const description = testDescription(node);
+ const rangeIgnoringQuotes = [firstArg.range[0] + 1, firstArg.range[1] - 1];
+ const newDescription = description.substring(0, 1).toLowerCase() + description.substring(1);
+ return [fixer.replaceTextRange(rangeIgnoringQuotes, newDescription)];
+ }
+
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-alias-methods.js

@@ -0,0 +1,59 @@
+'use strict';
+
+const _require = require('./util'),
+ expectCase = _require.expectCase,
+ getDocsUrl = _require.getDocsUrl,
+ method = _require.method;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ // The Jest methods which have aliases. The canonical name is the first
+ // index of each item.
+ const methodNames = [['toHaveBeenCalled', 'toBeCalled'], ['toHaveBeenCalledTimes', 'toBeCalledTimes'], ['toHaveBeenCalledWith', 'toBeCalledWith'], ['toHaveBeenLastCalledWith', 'lastCalledWith'], ['toHaveBeenNthCalledWith', 'nthCalledWith'], ['toHaveReturned', 'toReturn'], ['toHaveReturnedTimes', 'toReturnTimes'], ['toHaveReturnedWith', 'toReturnWith'], ['toHaveLastReturnedWith', 'lastReturnedWith'], ['toHaveNthReturnedWith', 'nthReturnedWith'], ['toThrow', 'toThrowError']];
+ return {
+ CallExpression(node) {
+ if (!expectCase(node)) {
+ return;
+ }
+
+ let targetNode = method(node);
+
+ if (targetNode.name === 'resolves' || targetNode.name === 'rejects' || targetNode.name === 'not') {
+ targetNode = method(node.parent);
+ }
+
+ if (!targetNode) {
+ return;
+ } // Check if the method used matches any of ours
+
+
+ const methodItem = methodNames.find(item => item[1] === targetNode.name);
+
+ if (methodItem) {
+ context.report({
+ message: `Replace {{ replace }}() with its canonical name of {{ canonical }}()`,
+ data: {
+ replace: methodItem[1],
+ canonical: methodItem[0]
+ },
+ node: targetNode,
+
+ fix(fixer) {
+ return [fixer.replaceText(targetNode, methodItem[0])];
+ }
+
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-commented-out-tests.js

@@ -0,0 +1,39 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+const message = 'Some tests seem to be commented';
+
+function hasTests(node) {
+ return /^\s*(x|f)?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\(/m.test(node.value);
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ const sourceCode = context.getSourceCode();
+
+ function checkNode(node) {
+ if (!hasTests(node)) return;
+ context.report({
+ message,
+ node
+ });
+ }
+
+ return {
+ Program() {
+ const comments = sourceCode.getAllComments();
+ comments.filter(token => token.type !== 'Shebang').forEach(checkNode);
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-disabled-tests.js

@@ -0,0 +1,103 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ getNodeName = _require.getNodeName,
+ scopeHasLocalReference = _require.scopeHasLocalReference;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ let suiteDepth = 0;
+ let testDepth = 0;
+ return {
+ 'CallExpression[callee.name="describe"]'() {
+ suiteDepth++;
+ },
+
+ 'CallExpression[callee.name=/^(it|test)$/]'() {
+ testDepth++;
+ },
+
+ 'CallExpression[callee.name=/^(it|test)$/][arguments.length<2]'(node) {
+ context.report({
+ message: 'Test is missing function argument',
+ node
+ });
+ },
+
+ CallExpression(node) {
+ const functionName = getNodeName(node.callee);
+
+ switch (functionName) {
+ case 'describe.skip':
+ context.report({
+ message: 'Skipped test suite',
+ node
+ });
+ break;
+
+ case 'it.skip':
+ case 'test.skip':
+ context.report({
+ message: 'Skipped test',
+ node
+ });
+ break;
+ }
+ },
+
+ 'CallExpression[callee.name="pending"]'(node) {
+ if (scopeHasLocalReference(context.getScope(), 'pending')) {
+ return;
+ }
+
+ if (testDepth > 0) {
+ context.report({
+ message: 'Call to pending() within test',
+ node
+ });
+ } else if (suiteDepth > 0) {
+ context.report({
+ message: 'Call to pending() within test suite',
+ node
+ });
+ } else {
+ context.report({
+ message: 'Call to pending()',
+ node
+ });
+ }
+ },
+
+ 'CallExpression[callee.name="xdescribe"]'(node) {
+ context.report({
+ message: 'Disabled test suite',
+ node
+ });
+ },
+
+ 'CallExpression[callee.name=/^xit|xtest$/]'(node) {
+ context.report({
+ message: 'Disabled test',
+ node
+ });
+ },
+
+ 'CallExpression[callee.name="describe"]:exit'() {
+ suiteDepth--;
+ },
+
+ 'CallExpression[callee.name=/^it|test$/]:exit'() {
+ testDepth--;
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-empty-title.js

@@ -0,0 +1,67 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ hasExpressions = _require.hasExpressions,
+ isDescribe = _require.isDescribe,
+ isTestCase = _require.isTestCase,
+ isTemplateLiteral = _require.isTemplateLiteral,
+ isString = _require.isString,
+ getStringValue = _require.getStringValue;
+
+const errorMessages = {
+ describe: 'describe should not have an empty title',
+ test: 'test should not have an empty title'
+};
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const is = {
+ describe: isDescribe(node),
+ testCase: isTestCase(node)
+ };
+
+ if (!is.describe && !is.testCase) {
+ return;
+ }
+
+ const _node$arguments = _slicedToArray(node.arguments, 1),
+ firstArgument = _node$arguments[0];
+
+ if (!isString(firstArgument)) {
+ return;
+ }
+
+ if (isTemplateLiteral(firstArgument) && hasExpressions(firstArgument)) {
+ return;
+ }
+
+ if (getStringValue(firstArgument) === '') {
+ const message = is.describe ? errorMessages.describe : errorMessages.test;
+ context.report({
+ message,
+ node
+ });
+ }
+ }
+
+ };
+ },
+
+ errorMessages
+};
\ No newline at end of file

lib/rules/no-focused-tests.js

@@ -0,0 +1,66 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+const testFunctions = Object.assign(Object.create(null), {
+ describe: true,
+ it: true,
+ test: true
+});
+
+const matchesTestFunction = object => object && testFunctions[object.name];
+
+const isCallToFocusedTestFunction = object => object && object.name[0] === 'f' && testFunctions[object.name.substring(1)];
+
+const isPropertyNamedOnly = property => property && (property.name === 'only' || property.value === 'only');
+
+const isCallToTestOnlyFunction = callee => matchesTestFunction(callee.object) && isPropertyNamedOnly(callee.property);
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+ create: context => ({
+ CallExpression(node) {
+ const callee = node.callee;
+
+ if (callee.type === 'MemberExpression') {
+ if (callee.object.type === 'Identifier' && isCallToFocusedTestFunction(callee.object)) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee.object
+ });
+ return;
+ }
+
+ if (callee.object.type === 'MemberExpression' && isCallToTestOnlyFunction(callee.object)) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee.object.property
+ });
+ return;
+ }
+
+ if (isCallToTestOnlyFunction(callee)) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee.property
+ });
+ return;
+ }
+ }
+
+ if (callee.type === 'Identifier' && isCallToFocusedTestFunction(callee)) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee
+ });
+ return;
+ }
+ }
+
+ })
+};
\ No newline at end of file

lib/rules/no-hooks.js

@@ -0,0 +1,57 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+ schema: [{
+ type: 'object',
+ properties: {
+ allow: {
+ type: 'array',
+ contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']
+ }
+ },
+ additionalProperties: false
+ }],
+
+ create(context) {
+ const testHookNames = Object.assign(Object.create(null), {
+ beforeAll: true,
+ beforeEach: true,
+ afterAll: true,
+ afterEach: true
+ });
+ const whitelistedHookNames = (context.options[0] || {
+ allow: []
+ }).allow.reduce((hashMap, value) => {
+ hashMap[value] = true;
+ return hashMap;
+ }, Object.create(null));
+
+ const isHook = node => testHookNames[node.callee.name];
+
+ const isWhitelisted = node => whitelistedHookNames[node.callee.name];
+
+ return {
+ CallExpression(node) {
+ if (isHook(node) && !isWhitelisted(node)) {
+ context.report({
+ node,
+ message: "Unexpected '{{ hookName }}' hook",
+ data: {
+ hookName: node.callee.name
+ }
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-identical-title.js

@@ -0,0 +1,102 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ isDescribe = _require.isDescribe,
+ isTestCase = _require.isTestCase,
+ isString = _require.isString,
+ hasExpressions = _require.hasExpressions,
+ getStringValue = _require.getStringValue;
+
+const newDescribeContext = () => ({
+ describeTitles: [],
+ testTitles: []
+});
+
+const handleTestCaseTitles = (context, titles, node, title) => {
+ if (isTestCase(node)) {
+ if (titles.indexOf(title) !== -1) {
+ context.report({
+ message: 'Test title is used multiple times in the same describe block.',
+ node
+ });
+ }
+
+ titles.push(title);
+ }
+};
+
+const handleDescribeBlockTitles = (context, titles, node, title) => {
+ if (!isDescribe(node)) {
+ return;
+ }
+
+ if (titles.indexOf(title) !== -1) {
+ context.report({
+ message: 'Describe block title is used multiple times in the same describe block.',
+ node
+ });
+ }
+
+ titles.push(title);
+};
+
+const isFirstArgValid = arg => {
+ if (!arg || !isString(arg)) {
+ return false;
+ }
+
+ if (arg.type === 'TemplateLiteral' && hasExpressions(arg)) {
+ return false;
+ }
+
+ return true;
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ const contexts = [newDescribeContext()];
+ return {
+ CallExpression(node) {
+ const currentLayer = contexts[contexts.length - 1];
+
+ if (isDescribe(node)) {
+ contexts.push(newDescribeContext());
+ }
+
+ const _node$arguments = _slicedToArray(node.arguments, 1),
+ firstArgument = _node$arguments[0];
+
+ if (!isFirstArgValid(firstArgument)) {
+ return;
+ }
+
+ const title = getStringValue(firstArgument);
+ handleTestCaseTitles(context, currentLayer.testTitles, node, title);
+ handleDescribeBlockTitles(context, currentLayer.describeTitles, node, title);
+ },
+
+ 'CallExpression:exit'(node) {
+ if (isDescribe(node)) {
+ contexts.pop();
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-jasmine-globals.js

@@ -0,0 +1,145 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ getNodeName = _require.getNodeName,
+ scopeHasLocalReference = _require.scopeHasLocalReference;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code',
+ messages: {
+ illegalGlobal: 'Illegal usage of global `{{ global }}`, prefer `{{ replacement }}`',
+ illegalMethod: 'Illegal usage of `{{ method }}`, prefer `{{ replacement }}`',
+ illegalFail: 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
+ illegalPending: 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
+ illegalJasmine: 'Illegal usage of jasmine global'
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const calleeName = getNodeName(node.callee);
+
+ if (!calleeName) {
+ return;
+ }
+
+ if (calleeName === 'spyOn' || calleeName === 'spyOnProperty' || calleeName === 'fail' || calleeName === 'pending') {
+ if (scopeHasLocalReference(context.getScope(), calleeName)) {
+ // It's a local variable, not a jasmine global.
+ return;
+ }
+
+ switch (calleeName) {
+ case 'spyOn':
+ case 'spyOnProperty':
+ context.report({
+ node,
+ messageId: 'illegalGlobal',
+ data: {
+ global: calleeName,
+ replacement: 'jest.spyOn'
+ }
+ });
+ break;
+
+ case 'fail':
+ context.report({
+ node,
+ messageId: 'illegalFail'
+ });
+ break;
+
+ case 'pending':
+ context.report({
+ node,
+ messageId: 'illegalPending'
+ });
+ break;
+ }
+
+ return;
+ }
+
+ if (calleeName.startsWith('jasmine.')) {
+ const functionName = calleeName.replace('jasmine.', '');
+
+ if (functionName === 'any' || functionName === 'anything' || functionName === 'arrayContaining' || functionName === 'objectContaining' || functionName === 'stringMatching') {
+ context.report({
+ fix(fixer) {
+ return [fixer.replaceText(node.callee.object, 'expect')];
+ },
+
+ node,
+ messageId: 'illegalMethod',
+ data: {
+ method: calleeName,
+ replacement: `expect.${functionName}`
+ }
+ });
+ return;
+ }
+
+ if (functionName === 'addMatchers') {
+ context.report({
+ node,
+ messageId: 'illegalMethod',
+ data: {
+ method: calleeName,
+ replacement: `expect.extend`
+ }
+ });
+ return;
+ }
+
+ if (functionName === 'createSpy') {
+ context.report({
+ node,
+ messageId: 'illegalMethod',
+ data: {
+ method: calleeName,
+ replacement: 'jest.fn'
+ }
+ });
+ return;
+ }
+
+ context.report({
+ node,
+ messageId: 'illegalJasmine'
+ });
+ }
+ },
+
+ MemberExpression(node) {
+ if (node.object.name === 'jasmine') {
+ if (node.parent.type === 'AssignmentExpression') {
+ if (node.property.name === 'DEFAULT_TIMEOUT_INTERVAL') {
+ context.report({
+ fix(fixer) {
+ return [fixer.replaceText(node.parent, `jest.setTimeout(${node.parent.right.value})`)];
+ },
+
+ node,
+ message: 'Illegal usage of jasmine global'
+ });
+ return;
+ }
+
+ context.report({
+ node,
+ message: 'Illegal usage of jasmine global'
+ });
+ }
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-jest-import.js

@@ -0,0 +1,33 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+const message = `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`;
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ 'ImportDeclaration[source.value="jest"]'(node) {
+ context.report({
+ node,
+ message
+ });
+ },
+
+ 'CallExpression[callee.name="require"][arguments.0.value="jest"]'(node) {
+ context.report({
+ loc: node.arguments[0].loc,
+ message
+ });
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-large-snapshots.js

@@ -0,0 +1,55 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+const reportOnViolation = (context, node) => {
+ const lineLimit = context.options[0] && Number.isFinite(context.options[0].maxSize) ? context.options[0].maxSize : 50;
+ const startLine = node.loc.start.line;
+ const endLine = node.loc.end.line;
+ const lineCount = endLine - startLine;
+
+ if (lineCount > lineLimit) {
+ context.report({
+ message: lineLimit === 0 ? 'Expected to not encounter a Jest snapshot but was found with {{ lineCount }} lines long' : 'Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long',
+ data: {
+ lineLimit,
+ lineCount
+ },
+ node
+ });
+ }
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ if (context.getFilename().endsWith('.snap')) {
+ return {
+ ExpressionStatement(node) {
+ reportOnViolation(context, node);
+ }
+
+ };
+ } else if (context.getFilename().endsWith('.js')) {
+ return {
+ CallExpression(node) {
+ const propertyName = node.callee.property && node.callee.property.name;
+
+ if (propertyName === 'toMatchInlineSnapshot' || propertyName === 'toThrowErrorMatchingInlineSnapshot') {
+ reportOnViolation(context, node);
+ }
+ }
+
+ };
+ }
+
+ return {};
+ }
+
+};
\ No newline at end of file

lib/rules/no-mocks-import.js

@@ -0,0 +1,44 @@
+'use strict';
+
+const _require = require('path'),
+ posix = _require.posix;
+
+const _require2 = require('./util'),
+ getDocsUrl = _require2.getDocsUrl;
+
+const mocksDirName = '__mocks__';
+const message = `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use jest.mock and import from the original module path.`;
+
+const isMockPath = path => path.split(posix.sep).includes(mocksDirName);
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ ImportDeclaration(node) {
+ if (isMockPath(node.source.value)) {
+ context.report({
+ node,
+ message
+ });
+ }
+ },
+
+ 'CallExpression[callee.name="require"]'(node) {
+ if (node.arguments.length && node.arguments[0].value && isMockPath(node.arguments[0].value)) {
+ context.report({
+ loc: node.arguments[0].loc,
+ message
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-test-callback.js

@@ -0,0 +1,84 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ isTestCase = _require.isTestCase;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!isTestCase(node) || node.arguments.length !== 2) {
+ return;
+ }
+
+ const _node$arguments = _slicedToArray(node.arguments, 2),
+ callback = _node$arguments[1];
+
+ if (!/^(Arrow)?FunctionExpression$/.test(callback.type) || callback.params.length !== 1) {
+ return;
+ }
+
+ const _callback$params = _slicedToArray(callback.params, 1),
+ argument = _callback$params[0];
+
+ context.report({
+ node: argument,
+ message: 'Illegal usage of test callback',
+
+ fix(fixer) {
+ const sourceCode = context.getSourceCode();
+ const body = callback.body;
+ const firstBodyToken = sourceCode.getFirstToken(body);
+ const lastBodyToken = sourceCode.getLastToken(body);
+ const tokenBeforeArgument = sourceCode.getTokenBefore(argument);
+ const tokenAfterArgument = sourceCode.getTokenAfter(argument);
+ const argumentInParens = tokenBeforeArgument.value === '(' && tokenAfterArgument.value === ')';
+ let argumentFix = fixer.replaceText(argument, '()');
+
+ if (argumentInParens) {
+ argumentFix = fixer.remove(argument);
+ }
+
+ let newCallback = argument.name;
+
+ if (argumentInParens) {
+ newCallback = `(${newCallback})`;
+ }
+
+ let beforeReplacement = `new Promise(${newCallback} => `;
+ let afterReplacement = ')';
+ let replaceBefore = true;
+
+ if (body.type === 'BlockStatement') {
+ const keyword = callback.async ? 'await' : 'return';
+ beforeReplacement = `${keyword} ${beforeReplacement}{`;
+ afterReplacement += '}';
+ replaceBefore = false;
+ }
+
+ return [argumentFix, replaceBefore ? fixer.insertTextBefore(firstBodyToken, beforeReplacement) : fixer.insertTextAfter(firstBodyToken, beforeReplacement), fixer.insertTextAfter(lastBodyToken, afterReplacement)];
+ }
+
+ });
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-test-prefixes.js

@@ -0,0 +1,53 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ getNodeName = _require.getNodeName,
+ isTestCase = _require.isTestCase,
+ isDescribe = _require.isDescribe;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const nodeName = getNodeName(node.callee);
+ if (!isDescribe(node) && !isTestCase(node)) return;
+ const preferredNodeName = getPreferredNodeName(nodeName);
+ if (!preferredNodeName) return;
+ context.report({
+ message: 'Use "{{ preferredNodeName }}" instead',
+ node: node.callee,
+ data: {
+ preferredNodeName
+ },
+
+ fix(fixer) {
+ return [fixer.replaceText(node.callee, preferredNodeName)];
+ }
+
+ });
+ }
+
+ };
+ }
+
+};
+
+function getPreferredNodeName(nodeName) {
+ const firstChar = nodeName.charAt(0);
+
+ if (firstChar === 'f') {
+ return `${nodeName.slice(1)}.only`;
+ }
+
+ if (firstChar === 'x') {
+ return `${nodeName.slice(1)}.skip`;
+ }
+}
\ No newline at end of file

lib/rules/no-test-return-statement.js

@@ -0,0 +1,43 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ isFunction = _require.isFunction,
+ isTestCase = _require.isTestCase;
+
+const MESSAGE = 'Jest tests should not return a value.';
+const RETURN_STATEMENT = 'ReturnStatement';
+const BLOCK_STATEMENT = 'BlockStatement';
+
+const getBody = args => {
+ if (args.length > 1 && isFunction(args[1]) && args[1].body.type === BLOCK_STATEMENT) {
+ return args[1].body.body;
+ }
+
+ return [];
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!isTestCase(node)) return;
+ const body = getBody(node.arguments);
+ const returnStmt = body.find(t => t.type === RETURN_STATEMENT);
+ if (!returnStmt) return;
+ context.report({
+ message: MESSAGE,
+ node: returnStmt
+ });
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/no-truthy-falsy.js

@@ -0,0 +1,41 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ expectCase = _require.expectCase,
+ expectNotCase = _require.expectNotCase,
+ expectResolveCase = _require.expectResolveCase,
+ expectRejectCase = _require.expectRejectCase,
+ method = _require.method;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (expectCase(node) || expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) {
+ const targetNode = node.parent.parent.type === 'MemberExpression' ? node.parent : node;
+ const methodNode = method(targetNode);
+ const methodName = methodNode.name;
+
+ if (methodName === 'toBeTruthy' || methodName === 'toBeFalsy') {
+ context.report({
+ data: {
+ methodName
+ },
+ message: 'Avoid {{methodName}}',
+ node: methodNode
+ });
+ }
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-called-with.js

@@ -0,0 +1,39 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ expectCase = _require.expectCase,
+ expectNotCase = _require.expectNotCase,
+ method = _require.method;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ // Could check resolves/rejects here but not a likely idiom.
+ if (expectCase(node) && !expectNotCase(node)) {
+ const methodNode = method(node);
+ const name = methodNode.name;
+
+ if (name === 'toBeCalled' || name === 'toHaveBeenCalled') {
+ context.report({
+ data: {
+ name
+ },
+ message: 'Prefer {{name}}With(/* expected args */)',
+ node: methodNode
+ });
+ }
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-expect-assertions.js

@@ -0,0 +1,67 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+const ruleMsg = 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';
+
+const validateArguments = expression => {
+ return expression.arguments && expression.arguments.length === 1 && Number.isInteger(expression.arguments[0].value);
+};
+
+const isExpectAssertionsOrHasAssertionsCall = expression => {
+ try {
+ const expectAssertionOrHasAssertionCall = expression.type === 'CallExpression' && expression.callee.type === 'MemberExpression' && expression.callee.object.name === 'expect' && (expression.callee.property.name === 'assertions' || expression.callee.property.name === 'hasAssertions');
+
+ if (expression.callee.property.name === 'assertions') {
+ return expectAssertionOrHasAssertionCall && validateArguments(expression);
+ }
+
+ return expectAssertionOrHasAssertionCall;
+ } catch (e) {
+ return false;
+ }
+};
+
+const getFunctionFirstLine = functionBody => {
+ return functionBody[0] && functionBody[0].expression;
+};
+
+const isFirstLineExprStmt = functionBody => {
+ return functionBody[0] && functionBody[0].type === 'ExpressionStatement';
+};
+
+const reportMsg = (context, node) => {
+ context.report({
+ message: ruleMsg,
+ node
+ });
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ 'CallExpression[callee.name=/^(it|test)$/][arguments.1.body.body]'(node) {
+ const testFuncBody = node.arguments[1].body.body;
+
+ if (!isFirstLineExprStmt(testFuncBody)) {
+ reportMsg(context, node);
+ } else {
+ const testFuncFirstLine = getFunctionFirstLine(testFuncBody);
+
+ if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) {
+ reportMsg(context, node);
+ }
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-inline-snapshots.js

@@ -0,0 +1,43 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const propertyName = node.callee.property && node.callee.property.name;
+
+ if (propertyName === 'toMatchSnapshot') {
+ context.report({
+ fix(fixer) {
+ return [fixer.replaceText(node.callee.property, 'toMatchInlineSnapshot')];
+ },
+
+ message: 'Use toMatchInlineSnapshot() instead',
+ node: node.callee.property
+ });
+ } else if (propertyName === 'toThrowErrorMatchingSnapshot') {
+ context.report({
+ fix(fixer) {
+ return [fixer.replaceText(node.callee.property, 'toThrowErrorMatchingInlineSnapshot')];
+ },
+
+ message: 'Use toThrowErrorMatchingInlineSnapshot() instead',
+ node: node.callee.property
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-spy-on.js

@@ -0,0 +1,64 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ getNodeName = _require.getNodeName;
+
+const getJestFnCall = node => {
+ if (node.type !== 'CallExpression' && node.type !== 'MemberExpression' || node.callee && node.callee.type !== 'MemberExpression') {
+ return null;
+ }
+
+ const obj = node.callee ? node.callee.object : node.object;
+
+ if (obj.type === 'Identifier') {
+ return node.type === 'CallExpression' && getNodeName(node.callee) === 'jest.fn' ? node : null;
+ }
+
+ return getJestFnCall(obj);
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ AssignmentExpression(node) {
+ if (node.left.type !== 'MemberExpression') return;
+ const jestFnCall = getJestFnCall(node.right);
+ if (!jestFnCall) return;
+ context.report({
+ node,
+ message: 'Use jest.spyOn() instead.',
+
+ fix(fixer) {
+ const leftPropQuote = node.left.property.type === 'Identifier' ? "'" : '';
+
+ const _jestFnCall$arguments = _slicedToArray(jestFnCall.arguments, 1),
+ arg = _jestFnCall$arguments[0];
+
+ const argSource = arg && context.getSourceCode().getText(arg);
+ const mockImplementation = argSource ? `.mockImplementation(${argSource})` : '';
+ return [fixer.insertTextBefore(node.left, `jest.spyOn(`), fixer.replaceTextRange([node.left.object.range[1], node.left.property.range[0]], `, ${leftPropQuote}`), fixer.replaceTextRange([node.left.property.range[1], jestFnCall.range[1]], `${leftPropQuote})${mockImplementation}`)];
+ }
+
+ });
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-strict-equal.js

@@ -0,0 +1,40 @@
+'use strict';
+
+const _require = require('./util'),
+ expectCase = _require.expectCase,
+ getDocsUrl = _require.getDocsUrl,
+ method = _require.method;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!expectCase(node)) {
+ return;
+ }
+
+ const propertyName = method(node) && method(node).name;
+
+ if (propertyName === 'toEqual') {
+ context.report({
+ fix(fixer) {
+ return [fixer.replaceText(method(node), 'toStrictEqual')];
+ },
+
+ message: 'Use toStrictEqual() instead',
+ node: method(node)
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-to-be-null.js

@@ -0,0 +1,47 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ argument = _require.argument,
+ argument2 = _require.argument2,
+ expectToBeCase = _require.expectToBeCase,
+ expectToEqualCase = _require.expectToEqualCase,
+ expectNotToEqualCase = _require.expectNotToEqualCase,
+ expectNotToBeCase = _require.expectNotToBeCase,
+ method = _require.method,
+ method2 = _require.method2;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const is = expectToBeCase(node, null) || expectToEqualCase(node, null);
+ const isNot = expectNotToEqualCase(node, null) || expectNotToBeCase(node, null);
+
+ if (is || isNot) {
+ context.report({
+ fix(fixer) {
+ if (is) {
+ return [fixer.replaceText(method(node), 'toBeNull'), fixer.remove(argument(node))];
+ }
+
+ return [fixer.replaceText(method2(node), 'toBeNull'), fixer.remove(argument2(node))];
+ },
+
+ message: 'Use toBeNull() instead',
+ node: is ? method(node) : method2(node)
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-to-be-undefined.js

@@ -0,0 +1,47 @@
+'use strict';
+
+const _require = require('./util'),
+ argument = _require.argument,
+ argument2 = _require.argument2,
+ expectToBeCase = _require.expectToBeCase,
+ expectNotToBeCase = _require.expectNotToBeCase,
+ expectToEqualCase = _require.expectToEqualCase,
+ expectNotToEqualCase = _require.expectNotToEqualCase,
+ getDocsUrl = _require.getDocsUrl,
+ method = _require.method,
+ method2 = _require.method2;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const is = expectToBeCase(node, undefined) || expectToEqualCase(node, undefined);
+ const isNot = expectNotToEqualCase(node, undefined) || expectNotToBeCase(node, undefined);
+
+ if (is || isNot) {
+ context.report({
+ fix(fixer) {
+ if (is) {
+ return [fixer.replaceText(method(node), 'toBeUndefined'), fixer.remove(argument(node))];
+ }
+
+ return [fixer.replaceText(method2(node), 'toBeUndefined'), fixer.remove(argument2(node))];
+ },
+
+ message: 'Use toBeUndefined() instead',
+ node: is ? method(node) : method2(node)
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-to-contain.js

@@ -0,0 +1,94 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ expectCase = _require.expectCase,
+ expectResolveCase = _require.expectResolveCase,
+ expectRejectCase = _require.expectRejectCase,
+ method = _require.method,
+ argument = _require.argument;
+
+const isEqualityCheck = node => method(node) && (method(node).name === 'toBe' || method(node).name === 'toEqual');
+
+const isArgumentValid = node => argument(node).value === true || argument(node).value === false;
+
+const hasOneArgument = node => node.arguments && node.arguments.length === 1;
+
+const isValidEqualityCheck = node => isEqualityCheck(node) && hasOneArgument(node.parent.parent) && isArgumentValid(node);
+
+const isEqualityNegation = node => method(node).name === 'not' && isValidEqualityCheck(node.parent);
+
+const hasIncludesMethod = node => node.arguments[0] && node.arguments[0].callee && node.arguments[0].callee.property && node.arguments[0].callee.property.name === 'includes';
+
+const isValidIncludesMethod = node => hasIncludesMethod(node) && hasOneArgument(node.arguments[0]);
+
+const getNegationFixes = (node, sourceCode, fixer) => {
+ const negationPropertyDot = sourceCode.getFirstTokenBetween(node.parent.object, node.parent.property, token => token.value === '.');
+ const toContainFunc = isEqualityNegation(node) && argument(node.parent).value ? 'not.toContain' : 'toContain'; //.includes function argument
+
+ const _node$arguments$0$arg = _slicedToArray(node.arguments[0].arguments, 1),
+ containArg = _node$arguments$0$arg[0];
+
+ return [fixer.remove(negationPropertyDot), fixer.remove(method(node)), fixer.replaceText(method(node.parent), toContainFunc), fixer.replaceText(argument(node.parent), sourceCode.getText(containArg))];
+};
+
+const getCommonFixes = (node, sourceCode, fixer) => {
+ const _node$arguments$0$arg2 = _slicedToArray(node.arguments[0].arguments, 1),
+ containArg = _node$arguments$0$arg2[0];
+
+ const includesCaller = node.arguments[0].callee;
+ const propertyDot = sourceCode.getFirstTokenBetween(includesCaller.object, includesCaller.property, token => token.value === '.');
+ const closingParenthesis = sourceCode.getTokenAfter(containArg);
+ const openParenthesis = sourceCode.getTokenBefore(containArg);
+ return [fixer.remove(containArg), fixer.remove(includesCaller.property), fixer.remove(propertyDot), fixer.remove(closingParenthesis), fixer.remove(openParenthesis)];
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!(expectResolveCase(node) || expectRejectCase(node)) && expectCase(node) && (isEqualityNegation(node) || isValidEqualityCheck(node)) && isValidIncludesMethod(node)) {
+ context.report({
+ fix(fixer) {
+ const sourceCode = context.getSourceCode();
+ let fixArr = getCommonFixes(node, sourceCode, fixer);
+
+ if (isEqualityNegation(node)) {
+ return getNegationFixes(node, sourceCode, fixer).concat(fixArr);
+ }
+
+ const toContainFunc = argument(node).value ? 'toContain' : 'not.toContain'; //.includes function argument
+
+ const _node$arguments$0$arg3 = _slicedToArray(node.arguments[0].arguments, 1),
+ containArg = _node$arguments$0$arg3[0];
+
+ fixArr.push(fixer.replaceText(method(node), toContainFunc));
+ fixArr.push(fixer.replaceText(argument(node), sourceCode.getText(containArg)));
+ return fixArr;
+ },
+
+ message: 'Use toContain() instead',
+ node: method(node)
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/prefer-todo.js

@@ -0,0 +1,78 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ isFunction = _require.isFunction,
+ composeFixers = _require.composeFixers,
+ getNodeName = _require.getNodeName,
+ isString = _require.isString;
+
+function isOnlyTestTitle(node) {
+ return node.arguments.length === 1;
+}
+
+function isFunctionBodyEmpty(node) {
+ return node.body.body && !node.body.body.length;
+}
+
+function isTestBodyEmpty(node) {
+ const fn = node.arguments[1]; // eslint-disable-line prefer-destructuring
+
+ return fn && isFunction(fn) && isFunctionBodyEmpty(fn);
+}
+
+function addTodo(node, fixer) {
+ const testName = getNodeName(node.callee).split('.').shift();
+ return fixer.replaceText(node.callee, `${testName}.todo`);
+}
+
+function removeSecondArg({
+ arguments: [first, second]
+}, fixer) {
+ return fixer.removeRange([first.range[1], second.range[1]]);
+}
+
+function isFirstArgString({
+ arguments: [firstArg]
+}) {
+ return firstArg && isString(firstArg);
+}
+
+const isTestCase = node => node && node.type === 'CallExpression' && ['it', 'test', 'it.skip', 'test.skip'].includes(getNodeName(node.callee));
+
+function create(context) {
+ return {
+ CallExpression(node) {
+ if (isTestCase(node) && isFirstArgString(node)) {
+ const combineFixers = composeFixers(node);
+
+ if (isTestBodyEmpty(node)) {
+ context.report({
+ message: 'Prefer todo test case over empty test case',
+ node,
+ fix: combineFixers(removeSecondArg, addTodo)
+ });
+ }
+
+ if (isOnlyTestTitle(node)) {
+ context.report({
+ message: 'Prefer todo test case over unimplemented test case',
+ node,
+ fix: combineFixers(addTodo)
+ });
+ }
+ }
+ }
+
+ };
+}
+
+module.exports = {
+ create,
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ }
+};
\ No newline at end of file

lib/rules/prefer-to-have-length.js

@@ -0,0 +1,38 @@
+'use strict';
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ expectCase = _require.expectCase,
+ expectNotCase = _require.expectNotCase,
+ expectResolveCase = _require.expectResolveCase,
+ expectRejectCase = _require.expectRejectCase,
+ method = _require.method;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ },
+ fixable: 'code'
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) && expectCase(node) && (method(node).name === 'toBe' || method(node).name === 'toEqual') && node.arguments[0].property && node.arguments[0].property.name === 'length') {
+ const propertyDot = context.getSourceCode().getFirstTokenBetween(node.arguments[0].object, node.arguments[0].property, token => token.value === '.');
+ context.report({
+ fix(fixer) {
+ return [fixer.remove(propertyDot), fixer.remove(node.arguments[0].property), fixer.replaceText(method(node), 'toHaveLength')];
+ },
+
+ message: 'Use toHaveLength() instead',
+ node: method(node)
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/require-tothrow-message.js

@@ -0,0 +1,39 @@
+'use strict';
+
+const _require = require('./util'),
+ argument = _require.argument,
+ expectCase = _require.expectCase,
+ getDocsUrl = _require.getDocsUrl,
+ method = _require.method;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!expectCase(node)) {
+ return;
+ }
+
+ const propertyName = method(node) && method(node).name; // Look for `toThrow` calls with no arguments.
+
+ if (['toThrow', 'toThrowError'].includes(propertyName) && !argument(node)) {
+ context.report({
+ message: `Add an error message to {{ propertyName }}()`,
+ data: {
+ propertyName
+ },
+ node: method(node)
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/__tests__/consistent-test-it.test.js

@@ -0,0 +1,470 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../consistent-test-it');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6
+ }
+});
+ruleTester.run('consistent-test-it with fn=test', rule, {
+ valid: [{
+ code: 'test("foo")',
+ options: [{
+ fn: 'test'
+ }]
+ }, {
+ code: 'test.only("foo")',
+ options: [{
+ fn: 'test'
+ }]
+ }, {
+ code: 'test.skip("foo")',
+ options: [{
+ fn: 'test'
+ }]
+ }, {
+ code: 'xtest("foo")',
+ options: [{
+ fn: 'test'
+ }]
+ }, {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{
+ fn: 'test'
+ }]
+ }],
+ invalid: [{
+ code: 'it("foo")',
+ options: [{
+ fn: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'test("foo")'
+ }, {
+ code: 'xit("foo")',
+ options: [{
+ fn: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'xtest("foo")'
+ }, {
+ code: 'fit("foo")',
+ options: [{
+ fn: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'test.only("foo")'
+ }, {
+ code: 'it.skip("foo")',
+ options: [{
+ fn: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'test.skip("foo")'
+ }, {
+ code: 'it.only("foo")',
+ options: [{
+ fn: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'test.only("foo")'
+ }, {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{
+ fn: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it' within describe"
+ }],
+ output: 'describe("suite", () => { test("foo") })'
+ }]
+});
+ruleTester.run('consistent-test-it with fn=it', rule, {
+ valid: [{
+ code: 'it("foo")',
+ options: [{
+ fn: 'it'
+ }]
+ }, {
+ code: 'fit("foo")',
+ options: [{
+ fn: 'it'
+ }]
+ }, {
+ code: 'xit("foo")',
+ options: [{
+ fn: 'it'
+ }]
+ }, {
+ code: 'it.only("foo")',
+ options: [{
+ fn: 'it'
+ }]
+ }, {
+ code: 'it.skip("foo")',
+ options: [{
+ fn: 'it'
+ }]
+ }, {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{
+ fn: 'it'
+ }]
+ }],
+ invalid: [{
+ code: 'test("foo")',
+ options: [{
+ fn: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test'"
+ }],
+ output: 'it("foo")'
+ }, {
+ code: 'xtest("foo")',
+ options: [{
+ fn: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test'"
+ }],
+ output: 'xit("foo")'
+ }, {
+ code: 'test.skip("foo")',
+ options: [{
+ fn: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test'"
+ }],
+ output: 'it.skip("foo")'
+ }, {
+ code: 'test.only("foo")',
+ options: [{
+ fn: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test'"
+ }],
+ output: 'it.only("foo")'
+ }, {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{
+ fn: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { it("foo") })'
+ }]
+});
+ruleTester.run('consistent-test-it with fn=test and withinDescribe=it ', rule, {
+ valid: [{
+ code: 'test("foo")',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }]
+ }, {
+ code: 'test.only("foo")',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }]
+ }, {
+ code: 'test.skip("foo")',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }]
+ }, {
+ code: 'xtest("foo")',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }]
+ }, {
+ code: '[1,2,3].forEach(() => { test("foo") })',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }]
+ }],
+ invalid: [{
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { it("foo") })'
+ }, {
+ code: 'describe("suite", () => { test.only("foo") })',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { it.only("foo") })'
+ }, {
+ code: 'describe("suite", () => { xtest("foo") })',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { xit("foo") })'
+ }, {
+ code: 'describe("suite", () => { test.skip("foo") })',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { it.skip("foo") })'
+ }]
+});
+ruleTester.run('consistent-test-it with fn=it and withinDescribe=test ', rule, {
+ valid: [{
+ code: 'it("foo")',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }]
+ }, {
+ code: 'it.only("foo")',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }]
+ }, {
+ code: 'it.skip("foo")',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }]
+ }, {
+ code: 'xit("foo")',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }]
+ }, {
+ code: '[1,2,3].forEach(() => { it("foo") })',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }]
+ }],
+ invalid: [{
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it' within describe"
+ }],
+ output: 'describe("suite", () => { test("foo") })'
+ }, {
+ code: 'describe("suite", () => { it.only("foo") })',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it' within describe"
+ }],
+ output: 'describe("suite", () => { test.only("foo") })'
+ }, {
+ code: 'describe("suite", () => { xit("foo") })',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it' within describe"
+ }],
+ output: 'describe("suite", () => { xtest("foo") })'
+ }, {
+ code: 'describe("suite", () => { it.skip("foo") })',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it' within describe"
+ }],
+ output: 'describe("suite", () => { test.skip("foo") })'
+ }]
+});
+ruleTester.run('consistent-test-it with fn=test and withinDescribe=test ', rule, {
+ valid: [{
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'test'
+ }]
+ }, {
+ code: 'test("foo");',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'test'
+ }]
+ }],
+ invalid: [{
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it' within describe"
+ }],
+ output: 'describe("suite", () => { test("foo") })'
+ }, {
+ code: 'it("foo")',
+ options: [{
+ fn: 'test',
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'test("foo")'
+ }]
+});
+ruleTester.run('consistent-test-it with fn=it and withinDescribe=it ', rule, {
+ valid: [{
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'it'
+ }]
+ }, {
+ code: 'it("foo")',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'it'
+ }]
+ }],
+ invalid: [{
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { it("foo") })'
+ }, {
+ code: 'test("foo")',
+ options: [{
+ fn: 'it',
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test'"
+ }],
+ output: 'it("foo")'
+ }]
+});
+ruleTester.run('consistent-test-it defaults without config object', rule, {
+ valid: [{
+ code: 'test("foo")'
+ }],
+ invalid: [{
+ code: 'describe("suite", () => { test("foo") })',
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { it("foo") })'
+ }]
+});
+ruleTester.run('consistent-test-it with withinDescribe=it', rule, {
+ valid: [{
+ code: 'test("foo")',
+ options: [{
+ withinDescribe: 'it'
+ }]
+ }, {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{
+ withinDescribe: 'it'
+ }]
+ }],
+ invalid: [{
+ code: 'it("foo")',
+ options: [{
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'test("foo")'
+ }, {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{
+ withinDescribe: 'it'
+ }],
+ errors: [{
+ message: "Prefer using 'it' instead of 'test' within describe"
+ }],
+ output: 'describe("suite", () => { it("foo") })'
+ }]
+});
+ruleTester.run('consistent-test-it with withinDescribe=test', rule, {
+ valid: [{
+ code: 'test("foo")',
+ options: [{
+ withinDescribe: 'test'
+ }]
+ }, {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{
+ withinDescribe: 'test'
+ }]
+ }],
+ invalid: [{
+ code: 'it("foo")',
+ options: [{
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it'"
+ }],
+ output: 'test("foo")'
+ }, {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{
+ withinDescribe: 'test'
+ }],
+ errors: [{
+ message: "Prefer using 'test' instead of 'it' within describe"
+ }],
+ output: 'describe("suite", () => { test("foo") })'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/expect-expect.test.js

@@ -0,0 +1,67 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../expect-expect');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6
+ }
+});
+ruleTester.run('expect-expect', rule, {
+ valid: ['it("should pass", () => expect(true).toBeDefined())', 'test("should pass", () => expect(true).toBeDefined())', 'it("should pass", () => somePromise().then(() => expect(true).toBeDefined()))', {
+ code: 'test("should pass", () => { expect(true).toBeDefined(); foo(true).toBe(true); })',
+ options: [{
+ assertFunctionNames: ['expect', 'foo']
+ }]
+ }, {
+ code: 'it("should return undefined",() => expectSaga(mySaga).returns());',
+ options: [{
+ assertFunctionNames: ['expectSaga']
+ }]
+ }, {
+ code: ['test("verifies the function call", () => {', ' td.verify(someFunctionCall())', '})'].join('\n'),
+ options: [{
+ assertFunctionNames: ['td.verify']
+ }]
+ }],
+ invalid: [{
+ code: 'it("should fail", () => {});',
+ errors: [{
+ message: 'Test has no assertions',
+ type: 'CallExpression'
+ }]
+ }, {
+ code: 'test("should fail", () => {});',
+ errors: [{
+ message: 'Test has no assertions',
+ type: 'CallExpression'
+ }]
+ }, {
+ code: 'it("should fail", () => { somePromise.then(() => {}); });',
+ errors: [{
+ message: 'Test has no assertions',
+ type: 'CallExpression'
+ }]
+ }, {
+ code: 'test("should fail", () => { foo(true).toBe(true); })',
+ options: [{
+ assertFunctionNames: ['expect']
+ }],
+ errors: [{
+ message: 'Test has no assertions',
+ type: 'CallExpression'
+ }]
+ }, {
+ code: 'it("should also fail",() => expectSaga(mySaga).returns());',
+ options: [{
+ assertFunctionNames: ['expect']
+ }],
+ errors: [{
+ message: 'Test has no assertions',
+ type: 'CallExpression'
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/lowercase-name.test.js

@@ -0,0 +1,153 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../lowercase-name');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6
+ }
+});
+ruleTester.run('lowercase-name', rule, {
+ valid: ['it()', "it(' ', function () {})", 'it(" ", function () {})', 'it(` `, function () {})', "it('foo', function () {})", 'it("foo", function () {})', 'it(`foo`, function () {})', 'it("<Foo/>", function () {})', 'it("123 foo", function () {})', 'it(42, function () {})', 'it(``)', 'test()', "test('foo', function () {})", 'test("foo", function () {})', 'test(`foo`, function () {})', 'test("<Foo/>", function () {})', 'test("123 foo", function () {})', 'test("42", function () {})', 'test(``)', 'describe()', "describe('foo', function () {})", 'describe("foo", function () {})', 'describe(`foo`, function () {})', 'describe("<Foo/>", function () {})', 'describe("123 foo", function () {})', 'describe("42", function () {})', 'describe(function () {})', 'describe(``)'],
+ invalid: [{
+ code: "it('Foo', function () {})",
+ output: "it('foo', function () {})",
+ errors: [{
+ message: '`it`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'it("Foo", function () {})',
+ output: 'it("foo", function () {})',
+ errors: [{
+ message: '`it`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'it(`Foo`, function () {})',
+ output: 'it(`foo`, function () {})',
+ errors: [{
+ message: '`it`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: "test('Foo', function () {})",
+ output: "test('foo', function () {})",
+ errors: [{
+ message: '`test`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'test("Foo", function () {})',
+ output: 'test("foo", function () {})',
+ errors: [{
+ message: '`test`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'test(`Foo`, function () {})',
+ output: 'test(`foo`, function () {})',
+ errors: [{
+ message: '`test`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: "describe('Foo', function () {})",
+ output: "describe('foo', function () {})",
+ errors: [{
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'describe("Foo", function () {})',
+ output: 'describe("foo", function () {})',
+ errors: [{
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'describe(`Foo`, function () {})',
+ output: 'describe(`foo`, function () {})',
+ errors: [{
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'describe(`Some longer description`, function () {})',
+ output: 'describe(`some longer description`, function () {})',
+ errors: [{
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1
+ }]
+ }]
+});
+ruleTester.run('lowercase-name with ignore=describe', rule, {
+ valid: [{
+ code: "describe('Foo', function () {})",
+ options: [{
+ ignore: ['describe']
+ }]
+ }, {
+ code: 'describe("Foo", function () {})',
+ options: [{
+ ignore: ['describe']
+ }]
+ }, {
+ code: 'describe(`Foo`, function () {})',
+ options: [{
+ ignore: ['describe']
+ }]
+ }],
+ invalid: []
+});
+ruleTester.run('lowercase-name with ignore=test', rule, {
+ valid: [{
+ code: "test('Foo', function () {})",
+ options: [{
+ ignore: ['test']
+ }]
+ }, {
+ code: 'test("Foo", function () {})',
+ options: [{
+ ignore: ['test']
+ }]
+ }, {
+ code: 'test(`Foo`, function () {})',
+ options: [{
+ ignore: ['test']
+ }]
+ }],
+ invalid: []
+});
+ruleTester.run('lowercase-name with ignore=it', rule, {
+ valid: [{
+ code: "it('Foo', function () {})",
+ options: [{
+ ignore: ['it']
+ }]
+ }, {
+ code: 'it("Foo", function () {})',
+ options: [{
+ ignore: ['it']
+ }]
+ }, {
+ code: 'it(`Foo`, function () {})',
+ options: [{
+ ignore: ['it']
+ }]
+ }],
+ invalid: []
+});
\ No newline at end of file

lib/rules/__tests__/no-alias-methods.test.js

@@ -0,0 +1,124 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-alias-methods');
+
+const ruleTester = new RuleTester();
+ruleTester.run('no-alias-methods', rule, {
+ valid: ['expect(a).toHaveBeenCalled()', 'expect(a).toHaveBeenCalledTimes()', 'expect(a).toHaveBeenCalledWith()', 'expect(a).toHaveBeenLastCalledWith()', 'expect(a).toHaveBeenNthCalledWith()', 'expect(a).toHaveReturned()', 'expect(a).toHaveReturnedTimes()', 'expect(a).toHaveReturnedWith()', 'expect(a).toHaveLastReturnedWith()', 'expect(a).toHaveNthReturnedWith()', 'expect(a).toThrow()', 'expect(a).rejects;'],
+ invalid: [{
+ code: 'expect(a).toBeCalled()',
+ errors: [{
+ message: 'Replace toBeCalled() with its canonical name of toHaveBeenCalled()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveBeenCalled()'
+ }, {
+ code: 'expect(a).toBeCalledTimes()',
+ errors: [{
+ message: 'Replace toBeCalledTimes() with its canonical name of toHaveBeenCalledTimes()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveBeenCalledTimes()'
+ }, {
+ code: 'expect(a).toBeCalledWith()',
+ errors: [{
+ message: 'Replace toBeCalledWith() with its canonical name of toHaveBeenCalledWith()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveBeenCalledWith()'
+ }, {
+ code: 'expect(a).lastCalledWith()',
+ errors: [{
+ message: 'Replace lastCalledWith() with its canonical name of toHaveBeenLastCalledWith()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveBeenLastCalledWith()'
+ }, {
+ code: 'expect(a).nthCalledWith()',
+ errors: [{
+ message: 'Replace nthCalledWith() with its canonical name of toHaveBeenNthCalledWith()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveBeenNthCalledWith()'
+ }, {
+ code: 'expect(a).toReturn()',
+ errors: [{
+ message: 'Replace toReturn() with its canonical name of toHaveReturned()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveReturned()'
+ }, {
+ code: 'expect(a).toReturnTimes()',
+ errors: [{
+ message: 'Replace toReturnTimes() with its canonical name of toHaveReturnedTimes()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveReturnedTimes()'
+ }, {
+ code: 'expect(a).toReturnWith()',
+ errors: [{
+ message: 'Replace toReturnWith() with its canonical name of toHaveReturnedWith()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveReturnedWith()'
+ }, {
+ code: 'expect(a).lastReturnedWith()',
+ errors: [{
+ message: 'Replace lastReturnedWith() with its canonical name of toHaveLastReturnedWith()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveLastReturnedWith()'
+ }, {
+ code: 'expect(a).nthReturnedWith()',
+ errors: [{
+ message: 'Replace nthReturnedWith() with its canonical name of toHaveNthReturnedWith()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toHaveNthReturnedWith()'
+ }, {
+ code: 'expect(a).toThrowError()',
+ errors: [{
+ message: 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 11,
+ line: 1
+ }],
+ output: 'expect(a).toThrow()'
+ }, {
+ code: 'expect(a).resolves.toThrowError()',
+ errors: [{
+ message: 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 20,
+ line: 1
+ }],
+ output: 'expect(a).resolves.toThrow()'
+ }, {
+ code: 'expect(a).rejects.toThrowError()',
+ errors: [{
+ message: 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 19,
+ line: 1
+ }],
+ output: 'expect(a).rejects.toThrow()'
+ }, {
+ code: 'expect(a).not.toThrowError()',
+ errors: [{
+ message: 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 15,
+ line: 1
+ }],
+ output: 'expect(a).not.toThrow()'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-commented-out-tests.test.js

@@ -0,0 +1,167 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-commented-out-tests');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ sourceType: 'module'
+ }
+});
+ruleTester.run('no-commented-out-tests', rule, {
+ valid: ['// foo("bar", function () {})', 'describe("foo", function () {})', 'it("foo", function () {})', 'describe.only("foo", function () {})', 'it.only("foo", function () {})', 'test("foo", function () {})', 'test.only("foo", function () {})', 'var appliedSkip = describe.skip; appliedSkip.apply(describe)', 'var calledSkip = it.skip; calledSkip.call(it)', '({ f: function () {} }).f()', '(a || b).f()', 'itHappensToStartWithIt()', 'testSomething()', '// latest(dates)', '// TODO: unify with Git implementation from Shipit (?)', ['import { pending } from "actions"', '', 'test("foo", () => {', ' expect(pending()).toEqual({})', '})'].join('\n'), ['const { pending } = require("actions")', '', 'test("foo", () => {', ' expect(pending()).toEqual({})', '})'].join('\n'), ['test("foo", () => {', ' const pending = getPending()', ' expect(pending()).toEqual({})', '})'].join('\n'), ['test("foo", () => {', ' expect(pending()).toEqual({})', '})', '', 'function pending() {', ' return {}', '}'].join('\n')],
+ invalid: [{
+ code: '// describe("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// describe["skip"]("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// describe[\'skip\']("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// it.skip("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// it.only("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// it["skip"]("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// test.skip("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// test["skip"]("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// xdescribe("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// xit("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// fit("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// xtest("foo", function () {})',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: `// test(
+ // "foo", function () {}
+ // )`,
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: `/* test
+ (
+ "foo", function () {}
+ )
+ */`,
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// it("has title but no callback")',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// it()',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// test.someNewMethodThatMightBeAddedInTheFuture()',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// test["someNewMethodThatMightBeAddedInTheFuture"]()',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: '// test("has title but no callback")',
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: `
+ foo()
+ /*
+ describe("has title but no callback", () => {})
+ */
+ bar()`,
+ errors: [{
+ message: 'Some tests seem to be commented',
+ column: 7,
+ line: 3
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-disabled-tests.test.js

@@ -0,0 +1,114 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-disabled-tests');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ sourceType: 'module'
+ }
+});
+ruleTester.run('no-disabled-tests', rule, {
+ valid: ['describe("foo", function () {})', 'it("foo", function () {})', 'describe.only("foo", function () {})', 'it.only("foo", function () {})', 'test("foo", function () {})', 'test.only("foo", function () {})', 'var appliedSkip = describe.skip; appliedSkip.apply(describe)', 'var calledSkip = it.skip; calledSkip.call(it)', '({ f: function () {} }).f()', '(a || b).f()', 'itHappensToStartWithIt()', 'testSomething()', ['import { pending } from "actions"', '', 'test("foo", () => {', ' expect(pending()).toEqual({})', '})'].join('\n'), ['const { pending } = require("actions")', '', 'test("foo", () => {', ' expect(pending()).toEqual({})', '})'].join('\n'), ['test("foo", () => {', ' const pending = getPending()', ' expect(pending()).toEqual({})', '})'].join('\n'), ['test("foo", () => {', ' expect(pending()).toEqual({})', '})', '', 'function pending() {', ' return {}', '}'].join('\n')],
+ invalid: [{
+ code: 'describe.skip("foo", function () {})',
+ errors: [{
+ message: 'Skipped test suite',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'describe["skip"]("foo", function () {})',
+ errors: [{
+ message: 'Skipped test suite',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'it.skip("foo", function () {})',
+ errors: [{
+ message: 'Skipped test',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'it["skip"]("foo", function () {})',
+ errors: [{
+ message: 'Skipped test',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'test.skip("foo", function () {})',
+ errors: [{
+ message: 'Skipped test',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'test["skip"]("foo", function () {})',
+ errors: [{
+ message: 'Skipped test',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'xdescribe("foo", function () {})',
+ errors: [{
+ message: 'Disabled test suite',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'xit("foo", function () {})',
+ errors: [{
+ message: 'Disabled test',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'xtest("foo", function () {})',
+ errors: [{
+ message: 'Disabled test',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'it("has title but no callback")',
+ errors: [{
+ message: 'Test is missing function argument',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'test("has title but no callback")',
+ errors: [{
+ message: 'Test is missing function argument',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'it("contains a call to pending", function () { pending() })',
+ errors: [{
+ message: 'Call to pending() within test',
+ column: 48,
+ line: 1
+ }]
+ }, {
+ code: 'pending();',
+ errors: [{
+ message: 'Call to pending()',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'describe("contains a call to pending", function () { pending() })',
+ errors: [{
+ message: 'Call to pending() within test suite',
+ column: 54,
+ line: 1
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-empty-title.js

@@ -0,0 +1,72 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-empty-title');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ sourceType: 'module'
+ }
+});
+ruleTester.run('no-empty-title', rule, {
+ valid: ['someFn("", function () {})', 'describe(1, function () {})', 'describe("foo", function () {})', 'describe("foo", function () { it("bar", function () {}) })', 'test("foo", function () {})', 'test(`foo`, function () {})', 'test(`${foo}`, function () {})', "it('foo', function () {})", "xdescribe('foo', function () {})", "xit('foo', function () {})", "xtest('foo', function () {})"],
+ invalid: [{
+ code: 'describe("", function () {})',
+ errors: [{
+ message: rule.errorMessages.describe,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: ["describe('foo', () => {", "it('', () => {})", '})'].join('\n'),
+ errors: [{
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: 'it("", function () {})',
+ errors: [{
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'test("", function () {})',
+ errors: [{
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'test(``, function () {})',
+ errors: [{
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: "xdescribe('', () => {})",
+ errors: [{
+ message: rule.errorMessages.describe,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: "xit('', () => {})",
+ errors: [{
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: "xtest('', () => {})",
+ errors: [{
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-focused-tests.test.js

@@ -0,0 +1,97 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-focused-tests');
+
+const ruleTester = new RuleTester();
+const expectedErrorMessage = 'Unexpected focused test.';
+ruleTester.run('no-focused-tests', rule, {
+ valid: ['describe()', 'it()', 'describe.skip()', 'it.skip()', 'test()', 'test.skip()', 'var appliedOnly = describe.only; appliedOnly.apply(describe)', 'var calledOnly = it.only; calledOnly.call(it)'],
+ invalid: [{
+ code: 'describe.only()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 10,
+ line: 1
+ }]
+ }, {
+ code: 'describe.only.each()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 10,
+ line: 1
+ }]
+ }, {
+ code: 'describe["only"]()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 10,
+ line: 1
+ }]
+ }, {
+ code: 'it.only()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 4,
+ line: 1
+ }]
+ }, {
+ code: 'it.only.each()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 4,
+ line: 1
+ }]
+ }, {
+ code: 'it["only"]()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 4,
+ line: 1
+ }]
+ }, {
+ code: 'test.only()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 6,
+ line: 1
+ }]
+ }, {
+ code: 'test.only.each()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 6,
+ line: 1
+ }]
+ }, {
+ code: 'test["only"]()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 6,
+ line: 1
+ }]
+ }, {
+ code: 'fdescribe()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'fit()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'fit.each()',
+ errors: [{
+ message: expectedErrorMessage,
+ column: 1,
+ line: 1
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-hooks.test.js

@@ -0,0 +1,49 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-hooks');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6
+ }
+});
+ruleTester.run('no-hooks', rule, {
+ valid: ['test("foo")', 'describe("foo", () => { it("bar") })', 'test("foo", () => { expect(subject.beforeEach()).toBe(true) })', {
+ code: 'afterEach(() => {}); afterAll(() => {});',
+ options: [{
+ allow: ['afterEach', 'afterAll']
+ }]
+ }],
+ invalid: [{
+ code: 'beforeAll(() => {})',
+ errors: [{
+ message: "Unexpected 'beforeAll' hook"
+ }]
+ }, {
+ code: 'beforeEach(() => {})',
+ errors: [{
+ message: "Unexpected 'beforeEach' hook"
+ }]
+ }, {
+ code: 'afterAll(() => {})',
+ errors: [{
+ message: "Unexpected 'afterAll' hook"
+ }]
+ }, {
+ code: 'afterEach(() => {})',
+ errors: [{
+ message: "Unexpected 'afterEach' hook"
+ }]
+ }, {
+ code: 'beforeEach(() => {}); afterEach(() => { jest.resetModules() });',
+ options: [{
+ allow: ['afterEach']
+ }],
+ errors: [{
+ message: "Unexpected 'beforeEach' hook"
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-identical-title.test.js

@@ -0,0 +1,110 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-identical-title');
+
+const ruleTester = new RuleTester();
+ruleTester.run('no-identical-title', rule, {
+ valid: [['describe("describe", function() {', ' it("it", function() {});', '});'].join('\n'), ['describe();describe();'].join('\n'), ['it();it();'].join('\n'), ['describe("describe1", function() {', ' it("it1", function() {});', ' it("it2", function() {});', '});'].join('\n'), ['it("it1", function() {});', 'it("it2", function() {});'].join('\n'), ['it.only("it1", function() {});', 'it("it2", function() {});'].join('\n'), ['it.only("it1", function() {});', 'it.only("it2", function() {});'].join('\n'), ['describe("title", function() {});', 'it("title", function() {});'].join('\n'), ['describe("describe1", function() {', ' it("it1", function() {});', ' describe("describe2", function() {', ' it("it1", function() {});', ' });', '});'].join('\n'), ['describe("describe1", function() {', ' describe("describe2", function() {', ' it("it1", function() {});', ' });', ' it("it1", function() {});', '});'].join('\n'), ['describe("describe1", function() {', ' describe("describe2", function() {});', '});'].join('\n'), ['describe("describe1", function() {', ' describe("describe2", function() {});', '});', 'describe("describe2", function() {});'].join('\n'), ['describe("describe1", function() {});', 'describe("describe2", function() {});'].join('\n'), ['it("it" + n, function() {});', 'it("it" + n, function() {});'].join('\n'), {
+ code: ['it(`it${n}`, function() {});', 'it(`it${n}`, function() {});'].join('\n'),
+ env: {
+ es6: true
+ }
+ }, {
+ code: 'it(`${n}`, function() {});',
+ env: {
+ es6: true
+ }
+ }, ['describe("title " + foo, function() {', ' describe("describe1", function() {});', '});', 'describe("describe1", function() {});'].join('\n'), ['describe("describe1", function() {', ' describe("describe2", function() {});', ' describe("title " + foo, function() {', ' describe("describe2", function() {});', ' });', '});'].join('\n'), {
+ code: ['describe("describe", () => {', ' it(`testing ${someVar} with the same title`, () => {});', ' it(`testing ${someVar} with the same title`, () => {});', '});'].join('\n'),
+ env: {
+ es6: true
+ }
+ }, {
+ code: ['it(`it1`, () => {});', 'it(`it2`, () => {});'].join('\n'),
+ env: {
+ es6: true
+ }
+ }, {
+ code: ['describe(`describe1`, () => {});', 'describe(`describe2`, () => {});'].join('\n'),
+ env: {
+ es6: true
+ }
+ }],
+ invalid: [{
+ code: ['describe("describe1", function() {', ' it("it1", function() {});', ' it("it1", function() {});', '});'].join('\n'),
+ errors: [{
+ message: 'Test title is used multiple times in the same describe block.',
+ column: 4,
+ line: 3
+ }]
+ }, {
+ code: ['it("it1", function() {});', 'it("it1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: ['it.only("it1", function() {});', 'it("it1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: ['fit("it1", function() {});', 'it("it1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: ['it.only("it1", function() {});', 'it.only("it1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: ['describe("describe1", function() {});', 'describe("describe1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: ['describe("describe1", function() {});', 'xdescribe("describe1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: ['fdescribe("describe1", function() {});', 'describe("describe1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2
+ }]
+ }, {
+ code: ['describe("describe1", function() {', ' describe("describe2", function() {});', '});', 'describe("describe1", function() {});'].join('\n'),
+ errors: [{
+ message: 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 4
+ }]
+ }, {
+ code: ['describe("describe", () => {', ' it(`testing backticks with the same title`, () => {});', ' it(`testing backticks with the same title`, () => {});', '});'].join('\n'),
+ env: {
+ es6: true
+ },
+ errors: [{
+ message: 'Test title is used multiple times in the same describe block.',
+ column: 5,
+ line: 3
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-jasmine-globals.test.js

@@ -0,0 +1,151 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-jasmine-globals');
+
+const ruleTester = new RuleTester();
+ruleTester.run('no-jasmine-globals', rule, {
+ valid: ['jest.spyOn()', 'jest.fn()', 'expect.extend()', 'expect.any()', 'it("foo", function () {})', 'test("foo", function () {})', 'foo()', `require('foo')('bar')`, 'function callback(fail) { fail() }', 'var spyOn = require("actions"); spyOn("foo")', 'function callback(pending) { pending() }'],
+ invalid: [{
+ code: 'spyOn(some, "object")',
+ errors: [{
+ message: 'Illegal usage of global `spyOn`, prefer `jest.spyOn`',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'spyOnProperty(some, "object")',
+ errors: [{
+ message: 'Illegal usage of global `spyOnProperty`, prefer `jest.spyOn`',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'fail()',
+ errors: [{
+ message: 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'pending()',
+ errors: [{
+ message: 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }],
+ output: 'jest.setTimeout(5000);'
+ }, {
+ code: 'jasmine.addMatchers(matchers)',
+ errors: [{
+ message: 'Illegal usage of `jasmine.addMatchers`, prefer `expect.extend`',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.createSpy()',
+ errors: [{
+ message: 'Illegal usage of `jasmine.createSpy`, prefer `jest.fn`',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.any()',
+ errors: [{
+ message: 'Illegal usage of `jasmine.any`, prefer `expect.any`',
+ column: 1,
+ line: 1
+ }],
+ output: 'expect.any()'
+ }, {
+ code: 'jasmine.anything()',
+ errors: [{
+ message: 'Illegal usage of `jasmine.anything`, prefer `expect.anything`',
+ column: 1,
+ line: 1
+ }],
+ output: 'expect.anything()'
+ }, {
+ code: 'jasmine.arrayContaining()',
+ errors: [{
+ message: 'Illegal usage of `jasmine.arrayContaining`, prefer `expect.arrayContaining`',
+ column: 1,
+ line: 1
+ }],
+ output: 'expect.arrayContaining()'
+ }, {
+ code: 'jasmine.objectContaining()',
+ errors: [{
+ message: 'Illegal usage of `jasmine.objectContaining`, prefer `expect.objectContaining`',
+ column: 1,
+ line: 1
+ }],
+ output: 'expect.objectContaining()'
+ }, {
+ code: 'jasmine.stringMatching()',
+ errors: [{
+ message: 'Illegal usage of `jasmine.stringMatching`, prefer `expect.stringMatching`',
+ column: 1,
+ line: 1
+ }],
+ output: 'expect.stringMatching()'
+ }, {
+ code: 'jasmine.getEnv()',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.empty()',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.falsy()',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.truthy()',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.arrayWithExactContents()',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.clock()',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }]
+ }, {
+ code: 'jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH = 42',
+ errors: [{
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-jest-import.test.js

@@ -0,0 +1,62 @@
+'use strict';
+
+const rule = require('../no-jest-import.js');
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const ruleTester = new RuleTester();
+const message = `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`;
+ruleTester.run('no-jest-import', rule, {
+ valid: [{
+ code: 'import something from "something"',
+ parserOptions: {
+ sourceType: 'module'
+ }
+ }, 'require("somethingElse")', 'require()', 'entirelyDifferent(fn)'],
+ invalid: [{
+ code: 'require("jest")',
+ errors: [{
+ endColumn: 15,
+ column: 9,
+ message
+ }]
+ }, {
+ code: 'import jest from "jest"',
+ parserOptions: {
+ sourceType: 'module'
+ },
+ errors: [{
+ endColumn: 24,
+ column: 1,
+ message
+ }]
+ }, {
+ code: 'var jest = require("jest")',
+ errors: [{
+ endColumn: 26,
+ column: 20,
+ message
+ }]
+ }, {
+ code: 'import {jest as test} from "jest"',
+ parserOptions: {
+ sourceType: 'module'
+ },
+ errors: [{
+ endColumn: 34,
+ column: 1,
+ message
+ }]
+ }, {
+ code: 'const jest = require("jest")',
+ parserOptions: {
+ sourceType: 'module'
+ },
+ errors: [{
+ endColumn: 28,
+ column: 22,
+ message
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-large-snapshots.test.js

@@ -0,0 +1,145 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-large-snapshots');
+
+const noLargeSnapshots = rule.create;
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2015
+ }
+});
+ruleTester.run('no-large-snapshots', rule, {
+ valid: [{
+ filename: 'mock.js',
+ code: `expect(something).toMatchInlineSnapshot(\`\n${'line\n'.repeat(2)}\`);`
+ }, {
+ filename: 'mock.js',
+ code: `expect(something).toThrowErrorMatchingInlineSnapshot(\`\n${'line\n'.repeat(2)}\`);`
+ }],
+ invalid: [{
+ filename: 'mock.js',
+ code: `expect(something).toMatchInlineSnapshot(\`\n${'line\n'.repeat(50)}\`);`,
+ errors: [{
+ message: 'Expected Jest snapshot to be smaller than 50 lines but was 51 lines long'
+ }]
+ }, {
+ filename: 'mock.js',
+ code: `expect(something).toThrowErrorMatchingInlineSnapshot(\`\n${'line\n'.repeat(50)}\`);`,
+ errors: [{
+ message: 'Expected Jest snapshot to be smaller than 50 lines but was 51 lines long'
+ }]
+ }]
+}); // was not able to use https://eslint.org/docs/developer-guide/nodejs-api#ruletester for these because there is no way to configure RuleTester to run non .js files
+
+describe('no-large-snapshots', () => {
+ it('should return an empty object for non snapshot files', () => {
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx',
+ options: []
+ };
+ const result = noLargeSnapshots(mockContext);
+ expect(result).toEqual({});
+ });
+ it('should return an object with an ExpressionStatement function for snapshot files', () => {
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: []
+ };
+ const result = noLargeSnapshots(mockContext);
+ expect(result).toMatchObject({
+ ExpressionStatement: expect.any(Function)
+ });
+ });
+ describe('ExpressionStatement function', () => {
+ it('should report if node has more than 50 lines of code and no sizeThreshold option is passed', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [],
+ report: mockReport
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 1
+ },
+ end: {
+ line: 53
+ }
+ }
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+ expect(mockReport).toHaveBeenCalledTimes(1);
+ expect(mockReport.mock.calls[0]).toMatchSnapshot();
+ });
+ it('should report if node has more lines of code than number given in sizeThreshold option', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [{
+ maxSize: 70
+ }],
+ report: mockReport
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 20
+ },
+ end: {
+ line: 103
+ }
+ }
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+ expect(mockReport).toHaveBeenCalledTimes(1);
+ expect(mockReport.mock.calls[0]).toMatchSnapshot();
+ });
+ it('should report if maxSize is zero', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [{
+ maxSize: 0
+ }],
+ report: mockReport
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 1
+ },
+ end: {
+ line: 2
+ }
+ }
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+ expect(mockReport).toHaveBeenCalledTimes(1);
+ expect(mockReport.mock.calls[0]).toMatchSnapshot();
+ });
+ it('should not report if node has fewer lines of code than limit', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [],
+ report: mockReport
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 1
+ },
+ end: {
+ line: 18
+ }
+ }
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+ expect(mockReport).not.toHaveBeenCalled();
+ });
+ });
+});
\ No newline at end of file

lib/rules/__tests__/no-mocks-import.test.js

@@ -0,0 +1,70 @@
+'use strict';
+
+const rule = require('../no-mocks-import.js');
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const ruleTester = new RuleTester();
+const message = `Mocks should not be manually imported from a __mocks__ directory. Instead use jest.mock and import from the original module path.`;
+ruleTester.run('no-mocks-import', rule, {
+ valid: [{
+ code: 'import something from "something"',
+ parserOptions: {
+ sourceType: 'module'
+ }
+ }, 'require("somethingElse")', 'require("./__mocks__.js")', 'require("./__mocks__x")', 'require("./__mocks__x/x")', 'require("./x__mocks__")', 'require("./x__mocks__/x")', 'require()', 'var path = "./__mocks__.js"; require(path)', 'entirelyDifferent(fn)'],
+ invalid: [{
+ code: 'require("./__mocks__")',
+ errors: [{
+ endColumn: 22,
+ column: 9,
+ message
+ }]
+ }, {
+ code: 'require("./__mocks__/")',
+ errors: [{
+ endColumn: 23,
+ column: 9,
+ message
+ }]
+ }, {
+ code: 'require("./__mocks__/index")',
+ errors: [{
+ endColumn: 28,
+ column: 9,
+ message
+ }]
+ }, {
+ code: 'require("__mocks__")',
+ errors: [{
+ endColumn: 20,
+ column: 9,
+ message
+ }]
+ }, {
+ code: 'require("__mocks__/")',
+ errors: [{
+ endColumn: 21,
+ column: 9,
+ message
+ }]
+ }, {
+ code: 'require("__mocks__/index")',
+ errors: [{
+ endColumn: 26,
+ column: 9,
+ message
+ }]
+ }, {
+ code: 'import thing from "./__mocks__/index"',
+ parserOptions: {
+ sourceType: 'module'
+ },
+ errors: [{
+ endColumn: 38,
+ column: 1,
+ message
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-test-callback.test.js

@@ -0,0 +1,88 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-test-callback');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 8
+ }
+});
+ruleTester.run('no-test-callback', rule, {
+ valid: ['test("something", () => {})', 'test("something", async () => {})', 'test("something", function() {})', 'test("something", async function () {})', 'test("something", someArg)'],
+ invalid: [{
+ code: 'test("something", done => {done();})',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 19
+ }],
+ output: 'test("something", () => {return new Promise(done => {done();})})'
+ }, {
+ code: 'test("something", (done) => {done();})',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 20
+ }],
+ output: 'test("something", () => {return new Promise((done) => {done();})})'
+ }, {
+ code: 'test("something", done => done())',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 19
+ }],
+ output: 'test("something", () => new Promise(done => done()))'
+ }, {
+ code: 'test("something", (done) => done())',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 20
+ }],
+ output: 'test("something", () => new Promise((done) => done()))'
+ }, {
+ code: 'test("something", function(done) {done();})',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 28
+ }],
+ output: 'test("something", function() {return new Promise((done) => {done();})})'
+ }, {
+ code: 'test("something", function (done) {done();})',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 29
+ }],
+ output: 'test("something", function () {return new Promise((done) => {done();})})'
+ }, {
+ code: 'test("something", async done => {done();})',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 25
+ }],
+ output: 'test("something", async () => {await new Promise(done => {done();})})'
+ }, {
+ code: 'test("something", async done => done())',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 25
+ }],
+ output: 'test("something", async () => new Promise(done => done()))'
+ }, {
+ code: 'test("something", async function (done) {done();})',
+ errors: [{
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 35
+ }],
+ output: 'test("something", async function () {await new Promise((done) => {done();})})'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-test-prefixes.test.js

@@ -0,0 +1,52 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-test-prefixes');
+
+const ruleTester = new RuleTester();
+ruleTester.run('no-test-prefixes', rule, {
+ valid: ['describe("foo", function () {})', 'it("foo", function () {})', 'test("foo", function () {})', 'describe.only("foo", function () {})', 'it.only("foo", function () {})', 'test.only("foo", function () {})', 'describe.skip("foo", function () {})', 'it.skip("foo", function () {})', 'test.skip("foo", function () {})', 'foo()'],
+ invalid: [{
+ code: 'fdescribe("foo", function () {})',
+ errors: [{
+ message: 'Use "describe.only" instead',
+ column: 1,
+ line: 1
+ }],
+ output: 'describe.only("foo", function () {})'
+ }, {
+ code: 'fit("foo", function () {})',
+ errors: [{
+ message: 'Use "it.only" instead',
+ column: 1,
+ line: 1
+ }],
+ output: 'it.only("foo", function () {})'
+ }, {
+ code: 'xdescribe("foo", function () {})',
+ errors: [{
+ message: 'Use "describe.skip" instead',
+ column: 1,
+ line: 1
+ }],
+ output: 'describe.skip("foo", function () {})'
+ }, {
+ code: 'xit("foo", function () {})',
+ errors: [{
+ message: 'Use "it.skip" instead',
+ column: 1,
+ line: 1
+ }],
+ output: 'it.skip("foo", function () {})'
+ }, {
+ code: 'xtest("foo", function () {})',
+ errors: [{
+ message: 'Use "test.skip" instead',
+ column: 1,
+ line: 1
+ }],
+ output: 'test.skip("foo", function () {})'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-test-return-statement.test.js

@@ -0,0 +1,46 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-test-return-statement');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2015
+ }
+});
+ruleTester.run('no-test-prefixes', rule, {
+ valid: ['it("noop", function () {});', 'test("noop", () => {});', 'test("one", () => expect(1).toBe(1));', 'test("empty")', `
+ test("one", () => {
+ expect(1).toBe(1);
+ });
+ `, `
+ it("one", function () {
+ expect(1).toBe(1);
+ });
+ `],
+ invalid: [{
+ code: `
+ test("one", () => {
+ return expect(1).toBe(1);
+ });
+ `,
+ errors: [{
+ message: 'Jest tests should not return a value.',
+ column: 9,
+ line: 3
+ }]
+ }, {
+ code: `
+ it("one", function () {
+ return expect(1).toBe(1);
+ });
+ `,
+ errors: [{
+ message: 'Jest tests should not return a value.',
+ column: 9,
+ line: 3
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/no-truthy-falsy.test.js

@@ -0,0 +1,68 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../no-truthy-falsy');
+
+const ruleTester = new RuleTester();
+ruleTester.run('no-truthy-falsy', rule, {
+ valid: ['expect(true).toBe(true);', 'expect(false).toBe(false);', 'expect("anything").toBe(true);', 'expect("anything").toEqual(false);', 'expect("anything").not.toBe(true);', 'expect("anything").not.toEqual(true);', 'expect(Promise.resolve({})).resolves.toBe(true);', 'expect(Promise.reject({})).rejects.toBe(true);'],
+ invalid: [{
+ code: 'expect(true).toBeTruthy();',
+ errors: [{
+ message: 'Avoid toBeTruthy',
+ column: 14,
+ line: 1
+ }]
+ }, {
+ code: 'expect(false).not.toBeTruthy();',
+ errors: [{
+ message: 'Avoid toBeTruthy',
+ column: 19,
+ line: 1
+ }]
+ }, {
+ code: 'expect(Promise.resolve({})).resolves.toBeTruthy()',
+ errors: [{
+ message: 'Avoid toBeTruthy',
+ column: 38,
+ line: 1
+ }]
+ }, {
+ code: 'expect(Promise.resolve({})).rejects.toBeTruthy()',
+ errors: [{
+ message: 'Avoid toBeTruthy',
+ column: 37,
+ line: 1
+ }]
+ }, {
+ code: 'expect(false).toBeFalsy();',
+ errors: [{
+ message: 'Avoid toBeFalsy',
+ column: 15,
+ line: 1
+ }]
+ }, {
+ code: 'expect(true).not.toBeFalsy();',
+ errors: [{
+ message: 'Avoid toBeFalsy',
+ column: 18,
+ line: 1
+ }]
+ }, {
+ code: 'expect(Promise.resolve({})).resolves.toBeFalsy()',
+ errors: [{
+ message: 'Avoid toBeFalsy',
+ column: 38,
+ line: 1
+ }]
+ }, {
+ code: 'expect(Promise.resolve({})).rejects.toBeFalsy()',
+ errors: [{
+ message: 'Avoid toBeFalsy',
+ column: 37,
+ line: 1
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-called-with.js

@@ -0,0 +1,26 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-called-with');
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-called-with', rule, {
+ valid: ['expect(fn).toBeCalledWith();', 'expect(fn).toHaveBeenCalledWith();', 'expect(fn).toBeCalledWith(expect.anything());', 'expect(fn).toHaveBeenCalledWith(expect.anything());', 'expect(fn).not.toBeCalled();', 'expect(fn).not.toHaveBeenCalled();', 'expect(fn).not.toBeCalledWith();', 'expect(fn).not.toHaveBeenCalledWith();', 'expect(fn).toBeCalledTimes(0);', 'expect(fn).toHaveBeenCalledTimes(0);'],
+ invalid: [{
+ code: 'expect(fn).toBeCalled();',
+ errors: [{
+ message: 'Prefer toBeCalledWith(/* expected args */)',
+ column: 12,
+ line: 1
+ }]
+ }, {
+ code: 'expect(fn).toHaveBeenCalled();',
+ errors: [{
+ message: 'Prefer toHaveBeenCalledWith(/* expected args */)',
+ column: 12,
+ line: 1
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-expect-assertions.test.js

@@ -0,0 +1,54 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-expect-assertions');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6
+ }
+});
+const expectedMsg = 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';
+ruleTester.run('prefer-expect-assertions', rule, {
+ invalid: [{
+ code: 'it("it1", () => {})',
+ errors: [{
+ message: expectedMsg
+ }]
+ }, {
+ code: 'it("it1", () => { foo()})',
+ errors: [{
+ message: expectedMsg
+ }]
+ }, {
+ code: 'it("it1", function() {' + '\n\t\t\tsomeFunctionToDo();' + '\n\t\t\tsomeFunctionToDo2();\n' + '\t\t\t})',
+ errors: [{
+ message: expectedMsg
+ }]
+ }, {
+ code: 'it("it1", function() {var a = 2;})',
+ errors: [{
+ message: expectedMsg
+ }]
+ }, {
+ code: 'it("it1", function() {expect.assertions();})',
+ errors: [{
+ message: expectedMsg
+ }]
+ }, {
+ code: 'it("it1", function() {expect.assertions(1,2);})',
+ errors: [{
+ message: expectedMsg
+ }]
+ }, {
+ code: 'it("it1", function() {expect.assertions("1");})',
+ errors: [{
+ message: expectedMsg
+ }]
+ }],
+ valid: [{
+ code: 'test("it1", () => {expect.assertions(0);})'
+ }, 'test("it1", function() {expect.assertions(0);})', 'test("it1", function() {expect.hasAssertions();})', 'it("it1", function() {expect.assertions(0);})', 'it("it1", function() {\n\t\t\texpect.assertions(1);' + '\n\t\t\texpect(someValue).toBe(true)\n' + '\t\t\t})', 'test("it1")', 'itHappensToStartWithIt("foo", function() {})', 'testSomething("bar", function() {})']
+});
\ No newline at end of file

lib/rules/__tests__/prefer-inline-snapshots.test.js

@@ -0,0 +1,28 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-inline-snapshots');
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-inline-snapshots', rule, {
+ valid: ['expect(something).toMatchInlineSnapshot();', 'expect(something).toThrowErrorMatchingInlineSnapshot();'],
+ invalid: [{
+ code: 'expect(something).toMatchSnapshot();',
+ errors: [{
+ message: 'Use toMatchInlineSnapshot() instead',
+ column: 19,
+ line: 1
+ }],
+ output: 'expect(something).toMatchInlineSnapshot();'
+ }, {
+ code: 'expect(something).toThrowErrorMatchingSnapshot();',
+ errors: [{
+ message: 'Use toThrowErrorMatchingInlineSnapshot() instead',
+ column: 19,
+ line: 1
+ }],
+ output: 'expect(something).toThrowErrorMatchingInlineSnapshot();'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-spy-on.test.js

@@ -0,0 +1,72 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-spy-on');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6
+ }
+});
+ruleTester.run('prefer-spy-on', rule, {
+ valid: ['Date.now = () => 10', 'window.fetch = jest.fn', 'Date.now = fn()', 'obj.mock = jest.something()', 'const mock = jest.fn()', 'mock = jest.fn()', 'const mockObj = { mock: jest.fn() }', 'mockObj = { mock: jest.fn() }', 'window[`${name}`] = jest[`fn${expression}`]()'],
+ invalid: [{
+ code: 'obj.a = jest.fn(); const test = 10;',
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: "jest.spyOn(obj, 'a'); const test = 10;"
+ }, {
+ code: "Date['now'] = jest['fn']()",
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: "jest.spyOn(Date, 'now')"
+ }, {
+ code: 'window[`${name}`] = jest[`fn`]()',
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: 'jest.spyOn(window, `${name}`)'
+ }, {
+ code: "obj['prop' + 1] = jest['fn']()",
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: "jest.spyOn(obj, 'prop' + 1)"
+ }, {
+ code: 'obj.one.two = jest.fn(); const test = 10;',
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: "jest.spyOn(obj.one, 'two'); const test = 10;"
+ }, {
+ code: 'obj.a = jest.fn(() => 10)',
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: "jest.spyOn(obj, 'a').mockImplementation(() => 10)"
+ }, {
+ code: "obj.a.b = jest.fn(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: "jest.spyOn(obj.a, 'b').mockImplementation(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();"
+ }, {
+ code: 'window.fetch = jest.fn(() => ({})).one.two().three().four',
+ errors: [{
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression'
+ }],
+ output: "jest.spyOn(window, 'fetch').mockImplementation(() => ({})).one.two().three().four"
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-strict-equal.test.js

@@ -0,0 +1,20 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-strict-equal');
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-strict-equal', rule, {
+ valid: ['expect(something).toStrictEqual(somethingElse);', "a().toEqual('b')"],
+ invalid: [{
+ code: 'expect(something).toEqual(somethingElse);',
+ errors: [{
+ message: 'Use toStrictEqual() instead',
+ column: 19,
+ line: 1
+ }],
+ output: 'expect(something).toStrictEqual(somethingElse);'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-to-be-null.test.js

@@ -0,0 +1,44 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-to-be-null');
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-to-be-null', rule, {
+ valid: ['expect(null).toBeNull();', 'expect(null).toEqual();', 'expect(null).not.toBeNull();', 'expect(null).not.toEqual();', 'expect(null).toBe(undefined);', 'expect(null).not.toBe(undefined);', 'expect(null).toBe();', 'expect(null).toMatchSnapshot();', 'expect("a string").toMatchSnapshot(null);', 'expect("a string").not.toMatchSnapshot();', "expect(something).toEqual('a string');", 'expect(null).toBe'],
+ invalid: [{
+ code: 'expect(null).toBe(null);',
+ errors: [{
+ message: 'Use toBeNull() instead',
+ column: 14,
+ line: 1
+ }],
+ output: 'expect(null).toBeNull();'
+ }, {
+ code: 'expect(null).toEqual(null);',
+ errors: [{
+ message: 'Use toBeNull() instead',
+ column: 14,
+ line: 1
+ }],
+ output: 'expect(null).toBeNull();'
+ }, {
+ code: 'expect("a string").not.toBe(null);',
+ errors: [{
+ message: 'Use toBeNull() instead',
+ column: 24,
+ line: 1
+ }],
+ output: 'expect("a string").not.toBeNull();'
+ }, {
+ code: 'expect("a string").not.toEqual(null);',
+ errors: [{
+ message: 'Use toBeNull() instead',
+ column: 24,
+ line: 1
+ }],
+ output: 'expect("a string").not.toBeNull();'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-to-be-undefined.test.js

@@ -0,0 +1,44 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-to-be-undefined');
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-to-be-undefined', rule, {
+ valid: ['expect(undefined).toBeUndefined();', 'expect(true).not.toBeUndefined();', 'expect({}).toEqual({});', 'expect(null).toEqual(null);', 'expect(something).toBe(somethingElse)', 'expect(something).toEqual(somethingElse)', 'expect(something).not.toBe(somethingElse)', 'expect(something).not.toEqual(somethingElse)', 'expect(undefined).toBe'],
+ invalid: [{
+ code: 'expect(undefined).toBe(undefined);',
+ errors: [{
+ message: 'Use toBeUndefined() instead',
+ column: 19,
+ line: 1
+ }],
+ output: 'expect(undefined).toBeUndefined();'
+ }, {
+ code: 'expect(undefined).toEqual(undefined);',
+ errors: [{
+ message: 'Use toBeUndefined() instead',
+ column: 19,
+ line: 1
+ }],
+ output: 'expect(undefined).toBeUndefined();'
+ }, {
+ code: 'expect("a string").not.toBe(undefined);',
+ errors: [{
+ message: 'Use toBeUndefined() instead',
+ column: 24,
+ line: 1
+ }],
+ output: 'expect("a string").not.toBeUndefined();'
+ }, {
+ code: 'expect("a string").not.toEqual(undefined);',
+ errors: [{
+ message: 'Use toBeUndefined() instead',
+ column: 24,
+ line: 1
+ }],
+ output: 'expect("a string").not.toBeUndefined();'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-to-contain.test.js

@@ -0,0 +1,140 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-to-contain');
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-to-contain', rule, {
+ valid: ['expect(a).toContain(b);', "expect(a.name).toBe('b');", 'expect(a).toBe(true);', `expect(a).toEqual(b)`, `expect(a.test(c)).toEqual(b)`, `expect(a.includes(b)).toEqual()`, `expect(a.includes(b)).toEqual("test")`, `expect(a.includes(b)).toBe("test")`, `expect(a.includes()).toEqual()`, `expect(a.includes()).toEqual(true)`, `expect(a.includes(b,c)).toBe(true)`, `expect([{a:1}]).toContain({a:1})`, `expect([1].includes(1)).toEqual`, `expect([1].includes).toEqual`, `expect([1].includes).not`, `expect(a.test(b)).resolves.toEqual(true)`, `expect(a.test(b)).resolves.not.toEqual(true)`, `expect(a).not.toContain(b)`],
+ invalid: [{
+ code: 'expect(a.includes(b)).toEqual(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).toContain(b);'
+ }, {
+ code: 'expect(a.includes(b)).toEqual(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).not.toContain(b);'
+ }, {
+ code: 'expect(a.includes(b)).not.toEqual(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).toContain(b);'
+ }, {
+ code: 'expect(a.includes(b)).not.toEqual(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).not.toContain(b);'
+ }, {
+ code: 'expect(a.includes(b)).toBe(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).toContain(b);'
+ }, {
+ code: 'expect(a.includes(b)).toBe(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).not.toContain(b);'
+ }, {
+ code: 'expect(a.includes(b)).not.toBe(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).toContain(b);'
+ }, {
+ code: 'expect(a.includes(b)).not.toBe(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1
+ }],
+ output: 'expect(a).not.toContain(b);'
+ }, {
+ code: 'expect(a.test(t).includes(b.test(p))).toEqual(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1
+ }],
+ output: 'expect(a.test(t)).toContain(b.test(p));'
+ }, {
+ code: 'expect(a.test(t).includes(b.test(p))).toEqual(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1
+ }],
+ output: 'expect(a.test(t)).not.toContain(b.test(p));'
+ }, {
+ code: 'expect(a.test(t).includes(b.test(p))).not.toEqual(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1
+ }],
+ output: 'expect(a.test(t)).not.toContain(b.test(p));'
+ }, {
+ code: 'expect(a.test(t).includes(b.test(p))).not.toEqual(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1
+ }],
+ output: 'expect(a.test(t)).toContain(b.test(p));'
+ }, {
+ code: 'expect([{a:1}].includes({a:1})).toBe(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1
+ }],
+ output: 'expect([{a:1}]).toContain({a:1});'
+ }, {
+ code: 'expect([{a:1}].includes({a:1})).toBe(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1
+ }],
+ output: 'expect([{a:1}]).not.toContain({a:1});'
+ }, {
+ code: 'expect([{a:1}].includes({a:1})).not.toBe(true);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1
+ }],
+ output: 'expect([{a:1}]).not.toContain({a:1});'
+ }, {
+ code: 'expect([{a:1}].includes({a:1})).not.toBe(false);',
+ errors: [{
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1
+ }],
+ output: 'expect([{a:1}]).toContain({a:1});'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-todo.test.js

@@ -0,0 +1,48 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-todo');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2015
+ }
+});
+ruleTester.run('prefer-todo', rule, {
+ valid: ['test.todo("i need to write this test");', 'test(obj)', 'fit("foo")', 'xit("foo")', 'test("stub", () => expect(1).toBe(1));', `
+ supportsDone && params.length < test.length
+ ? done => test(...params, done)
+ : () => test(...params);
+ `],
+ invalid: [{
+ code: `test("i need to write this test");`,
+ errors: [{
+ message: 'Prefer todo test case over unimplemented test case'
+ }],
+ output: 'test.todo("i need to write this test");'
+ }, {
+ code: 'test(`i need to write this test`);',
+ errors: [{
+ message: 'Prefer todo test case over unimplemented test case'
+ }],
+ output: 'test.todo(`i need to write this test`);'
+ }, {
+ code: 'it("foo", function () {})',
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'it.todo("foo")'
+ }, {
+ code: 'it("foo", () => {})',
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'it.todo("foo")'
+ }, {
+ code: `test.skip("i need to write this test", () => {});`,
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'test.todo("i need to write this test");'
+ }, {
+ code: `test.skip("i need to write this test", function() {});`,
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'test.todo("i need to write this test");'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/prefer-to-have-length.test.js

@@ -0,0 +1,28 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../prefer-to-have-length');
+
+const ruleTester = new RuleTester();
+ruleTester.run('prefer-to-have-length', rule, {
+ valid: ['expect(files).toHaveLength(1);', "expect(files.name).toBe('file');", 'expect(result).toBe(true);', `expect(user.getUserName(5)).resolves.toEqual('Paul')`, `expect(user.getUserName(5)).rejects.toEqual('Paul')`],
+ invalid: [{
+ code: 'expect(files.length).toBe(1);',
+ errors: [{
+ message: 'Use toHaveLength() instead',
+ column: 22,
+ line: 1
+ }],
+ output: 'expect(files).toHaveLength(1);'
+ }, {
+ code: 'expect(files.length).toEqual(1);',
+ errors: [{
+ message: 'Use toHaveLength() instead',
+ column: 22,
+ line: 1
+ }],
+ output: 'expect(files).toHaveLength(1);'
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/require-tothrow-message.test.js

@@ -0,0 +1,37 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../require-tothrow-message');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6
+ }
+});
+ruleTester.run('require-tothrow-message', rule, {
+ valid: [// String
+ "expect(() => { throw new Error('a'); }).toThrow('a');", "expect(() => { throw new Error('a'); }).toThrowError('a');", // Template literal
+ "const a = 'a'; expect(() => { throw new Error('a'); }).toThrow(`${a}`);", // Regex
+ "expect(() => { throw new Error('a'); }).toThrow(/^a$/);", // Function
+ "expect(() => { throw new Error('a'); })" + ".toThrow((() => { return 'a'; })());", // Allow no message for `not`.
+ "expect(() => { throw new Error('a'); }).not.toThrow();"],
+ invalid: [// Empty toThrow
+ {
+ code: "expect(() => { throw new Error('a'); }).toThrow();",
+ errors: [{
+ message: 'Add an error message to toThrow()',
+ column: 41,
+ line: 1
+ }]
+ }, // Empty toThrowError
+ {
+ code: "expect(() => { throw new Error('a'); }).toThrowError();",
+ errors: [{
+ message: 'Add an error message to toThrowError()',
+ column: 41,
+ line: 1
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/valid-describe.test.js

@@ -0,0 +1,222 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../valid-describe');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 8
+ }
+});
+ruleTester.run('valid-describe', rule, {
+ valid: ['describe("foo", function() {})', 'describe("foo", () => {})', 'describe(`foo`, () => {})', 'xdescribe("foo", () => {})', 'fdescribe("foo", () => {})', 'describe.only("foo", () => {})', 'describe.skip("foo", () => {})', `
+ describe('foo', () => {
+ it('bar', () => {
+ return Promise.resolve(42).then(value => {
+ expect(value).toBe(42)
+ })
+ })
+ })
+ `, `
+ describe('foo', () => {
+ it('bar', async () => {
+ expect(await Promise.resolve(42)).toBe(42)
+ })
+ })
+ `, `
+ describe('foo', () =>
+ test('bar', () => {})
+ )
+ `],
+ invalid: [{
+ code: 'describe(() => {})',
+ errors: [{
+ message: 'First argument must be name',
+ line: 1,
+ column: 10
+ }, {
+ message: 'Describe requires name and callback arguments',
+ line: 1,
+ column: 10
+ }]
+ }, {
+ code: 'describe("foo")',
+ errors: [{
+ message: 'Describe requires name and callback arguments',
+ line: 1,
+ column: 10
+ }]
+ }, {
+ code: 'describe("foo", "foo2")',
+ errors: [{
+ message: 'Second argument must be function',
+ line: 1,
+ column: 10
+ }]
+ }, {
+ code: 'describe()',
+ errors: [{
+ message: 'Describe requires name and callback arguments',
+ line: 1,
+ column: 1
+ }]
+ }, {
+ code: 'describe("foo", async () => {})',
+ errors: [{
+ message: 'No async describe callback',
+ line: 1,
+ column: 17
+ }]
+ }, {
+ code: 'describe("foo", async function () {})',
+ errors: [{
+ message: 'No async describe callback',
+ line: 1,
+ column: 17
+ }]
+ }, {
+ code: 'xdescribe("foo", async function () {})',
+ errors: [{
+ message: 'No async describe callback',
+ line: 1,
+ column: 18
+ }]
+ }, {
+ code: 'fdescribe("foo", async function () {})',
+ errors: [{
+ message: 'No async describe callback',
+ line: 1,
+ column: 18
+ }]
+ }, {
+ code: 'describe.only("foo", async function () {})',
+ errors: [{
+ message: 'No async describe callback',
+ line: 1,
+ column: 22
+ }]
+ }, {
+ code: 'describe.skip("foo", async function () {})',
+ errors: [{
+ message: 'No async describe callback',
+ line: 1,
+ column: 22
+ }]
+ }, {
+ code: `
+ describe('sample case', () => {
+ it('works', () => {
+ expect(true).toEqual(true);
+ });
+ describe('async', async () => {
+ await new Promise(setImmediate);
+ it('breaks', () => {
+ throw new Error('Fail');
+ });
+ });
+ });`,
+ errors: [{
+ message: 'No async describe callback',
+ line: 6,
+ column: 27
+ }]
+ }, {
+ code: `
+ describe('foo', function () {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ })
+ `,
+ errors: [{
+ message: 'Unexpected return statement in describe callback',
+ line: 3,
+ column: 9
+ }]
+ }, {
+ code: `
+ describe('foo', () => {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ describe('nested', () => {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ })
+ })
+ `,
+ errors: [{
+ message: 'Unexpected return statement in describe callback',
+ line: 3,
+ column: 9
+ }, {
+ message: 'Unexpected return statement in describe callback',
+ line: 9,
+ column: 11
+ }]
+ }, {
+ code: `
+ describe('foo', async () => {
+ await something()
+ it('does something')
+ describe('nested', () => {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ })
+ })
+ `,
+ errors: [{
+ message: 'No async describe callback',
+ line: 2,
+ column: 23
+ }, {
+ message: 'Unexpected return statement in describe callback',
+ line: 6,
+ column: 11
+ }]
+ }, {
+ code: 'describe("foo", done => {})',
+ errors: [{
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 17
+ }]
+ }, {
+ code: 'describe("foo", function (done) {})',
+ errors: [{
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 27
+ }]
+ }, {
+ code: 'describe("foo", function (one, two, three) {})',
+ errors: [{
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 27
+ }]
+ }, {
+ code: 'describe("foo", async function (done) {})',
+ errors: [{
+ message: 'No async describe callback',
+ line: 1,
+ column: 17
+ }, {
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 33
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/__tests__/valid-expect-in-promise.test.js

@@ -0,0 +1,319 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../valid-expect-in-promise');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 8
+ }
+});
+const expectedMsg = 'Promise should be returned to test its fulfillment or rejection';
+ruleTester.run('valid-expect-in-promise', rule, {
+ invalid: [{
+ code: `
+ it('it1', () => {
+ somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+ errors: [{
+ column: 12,
+ endColumn: 15,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('it1', function() {
+ getSomeThing().getPromise().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+ errors: [{
+ column: 13,
+ endColumn: 16,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('it1', function() {
+ Promise.resolve().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+ errors: [{
+ column: 13,
+ endColumn: 16,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('it1', function() {
+ somePromise.catch(function() {
+ expect(someThing).toEqual(true)
+ })
+ }
+ )
+ `,
+ errors: [{
+ column: 13,
+ endColumn: 15,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('it1', function() {
+ somePromise.then(function() {
+ expect(someThing).toEqual(true)
+ })
+ }
+ )
+ `,
+ errors: [{
+ column: 13,
+ endColumn: 15,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('it1', function () {
+ Promise.resolve().then(/*fulfillment*/ function () {
+ expect(someThing).toEqual(true);
+ }, /*rejection*/ function () {
+ expect(someThing).toEqual(true);
+ })
+ })
+ `,
+ errors: [{
+ column: 11,
+ endColumn: 13,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('it1', function () {
+ Promise.resolve().then(/*fulfillment*/ function () {
+ }, /*rejection*/ function () {
+ expect(someThing).toEqual(true)
+ })
+ });
+ `,
+ errors: [{
+ column: 11,
+ endColumn: 13,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('test function', () => {
+ Builder.getPromiseBuilder().get().build().then((data) => expect(data).toEqual('Hi'));
+ });
+ `,
+ errors: [{
+ column: 11,
+ endColumn: 96,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ it('it1', () => {
+ somePromise.then(() => {
+ doSomeOperation();
+ expect(someThing).toEqual(true);
+ })
+ });
+ `,
+ errors: [{
+ column: 13,
+ endColumn: 15,
+ message: expectedMsg
+ }]
+ }, {
+ code: `
+ test('invalid return', () => {
+ const promise = something().then(value => {
+ const foo = "foo";
+ return expect(value).toBe('red');
+ });
+ });
+ `,
+ errors: [{
+ column: 18,
+ endColumn: 14,
+ message: expectedMsg
+ }]
+ }],
+ valid: [`
+ it('it1', () => new Promise((done) => {
+ test()
+ .then(() => {
+ expect(someThing).toEqual(true);
+ done();
+ });
+ }));
+ `, `
+ it('it1', () => {
+ return somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `, `
+ it('it1', function() {
+ return somePromise.catch(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `, `
+ it('it1', function() {
+ return somePromise.then(function() {
+ doSomeThingButNotExpect();
+ });
+ });
+ `, `
+ it('it1', function() {
+ return getSomeThing().getPromise().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `, `
+ it('it1', function() {
+ return Promise.resolve().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `, `
+ it('it1', function () {
+ return Promise.resolve().then(function () {
+ /*fulfillment*/
+ expect(someThing).toEqual(true);
+ }, function () {
+ /*rejection*/
+ expect(someThing).toEqual(true);
+ });
+ });
+ `, `
+ it('it1', function () {
+ return Promise.resolve().then(function () {
+ /*fulfillment*/
+ }, function () {
+ /*rejection*/
+ expect(someThing).toEqual(true);
+ });
+ });
+ `, `
+ it('it1', function () {
+ return somePromise.then()
+ });
+ `, `
+ it('it1', async () => {
+ await Promise.resolve().then(function () {
+ expect(someThing).toEqual(true)
+ });
+ });
+ `, `
+ it('it1', async () => {
+ await somePromise.then(() => {
+ expect(someThing).toEqual(true)
+ });
+ });
+ `, `
+ it('it1', async () => {
+ await getSomeThing().getPromise().then(function () {
+ expect(someThing).toEqual(true)
+ });
+ });
+ `, `
+ it('it1', () => {
+ return somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ })
+ .then(() => {
+ expect(someThing).toEqual(true);
+ })
+ });
+ `, `
+ it('it1', () => {
+ return somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ })
+ .catch(() => {
+ expect(someThing).toEqual(false);
+ })
+ });
+ `, `
+ test('later return', () => {
+ const promise = something().then(value => {
+ expect(value).toBe('red');
+ });
+
+ return promise;
+ });
+ `, `
+ it('shorthand arrow', () =>
+ something().then(value => {
+ expect(() => {
+ value();
+ }).toThrow();
+ }));
+ `, `
+ it('promise test', () => {
+ const somePromise = getThatPromise();
+ somePromise.then((data) => {
+ expect(data).toEqual('foo');
+ });
+ expect(somePromise).toBeDefined();
+ return somePromise;
+ });
+ `, `
+ test('promise test', function () {
+ let somePromise = getThatPromise();
+ somePromise.then((data) => {
+ expect(data).toEqual('foo');
+ });
+ expect(somePromise).toBeDefined();
+ return somePromise;
+ });
+ `, `
+ it('crawls for files based on patterns', () => {
+ const promise = nodeCrawl({}).then(data => {
+ expect(childProcess.spawn).lastCalledWith('find');
+ });
+ return promise;
+ });
+ `, `
+ it('test function',
+ () => {
+ return Builder.getPromiseBuilder().get().build()
+ .then((data) => {
+ expect(data).toEqual('Hi');
+ });
+ });
+ `, `
+ notATestFunction('not a test function',
+ () => {
+ Builder.getPromiseBuilder().get().build()
+ .then((data) => {
+ expect(data).toEqual('Hi');
+ });
+ });
+ `, `
+ it("it1", () => somePromise.then(() => {
+ expect(someThing).toEqual(true)
+ }))
+ `, ` it("it1", () => somePromise.then(() => expect(someThing).toEqual(true)))`, `
+ it('promise test with done', (done) => {
+ const promise = getPromise();
+ promise.then(() => expect(someThing).toEqual(true));
+ });
+ `, `
+ it('name of done param does not matter', (nameDoesNotMatter) => {
+ const promise = getPromise();
+ promise.then(() => expect(someThing).toEqual(true));
+ });
+ `]
+});
\ No newline at end of file

lib/rules/__tests__/valid-expect.test.js

@@ -0,0 +1,93 @@
+'use strict';
+
+const _require = require('eslint'),
+ RuleTester = _require.RuleTester;
+
+const rule = require('../valid-expect');
+
+const ruleTester = new RuleTester();
+ruleTester.run('valid-expect', rule, {
+ valid: ['expect("something").toEqual("else");', 'expect(true).toBeDefined();', 'expect([1, 2, 3]).toEqual([1, 2, 3]);', 'expect(undefined).not.toBeDefined();', 'expect(Promise.resolve(2)).resolves.toBeDefined();', 'expect(Promise.reject(2)).rejects.toBeDefined();'],
+ invalid: [{
+ code: 'expect().toBe(true);',
+ errors: [{
+ endColumn: 8,
+ column: 7,
+ message: 'No arguments were passed to expect().'
+ }]
+ }, {
+ code: 'expect().toEqual("something");',
+ errors: [{
+ endColumn: 8,
+ column: 7,
+ message: 'No arguments were passed to expect().'
+ }]
+ }, {
+ code: 'expect("something", "else").toEqual("something");',
+ errors: [{
+ endColumn: 26,
+ column: 21,
+ message: 'More than one argument was passed to expect().'
+ }]
+ }, {
+ code: 'expect("something");',
+ errors: [{
+ endColumn: 20,
+ column: 1,
+ message: 'No assertion was called on expect().'
+ }]
+ }, {
+ code: 'expect();',
+ errors: [{
+ endColumn: 9,
+ column: 1,
+ message: 'No assertion was called on expect().'
+ }, {
+ endColumn: 8,
+ column: 7,
+ message: 'No arguments were passed to expect().'
+ }]
+ }, {
+ code: 'expect(true).toBeDefined;',
+ errors: [{
+ endColumn: 25,
+ column: 14,
+ message: '"toBeDefined" was not called.'
+ }]
+ }, {
+ code: 'expect(true).not.toBeDefined;',
+ errors: [{
+ endColumn: 29,
+ column: 18,
+ message: '"toBeDefined" was not called.'
+ }]
+ }, {
+ code: 'expect(true).nope.toBeDefined;',
+ errors: [{
+ endColumn: 18,
+ column: 14,
+ message: '"nope" is not a valid property of expect.'
+ }]
+ }, {
+ code: 'expect(true).resolves;',
+ errors: [{
+ endColumn: 22,
+ column: 14,
+ message: '"resolves" needs to call a matcher.'
+ }]
+ }, {
+ code: 'expect(true).rejects;',
+ errors: [{
+ endColumn: 21,
+ column: 14,
+ message: '"rejects" needs to call a matcher.'
+ }]
+ }, {
+ code: 'expect(true).not;',
+ errors: [{
+ endColumn: 17,
+ column: 14,
+ message: '"not" needs to call a matcher.'
+ }]
+ }]
+});
\ No newline at end of file

lib/rules/util.js

@@ -0,0 +1,218 @@
+'use strict';
+
+const path = require('path');
+
+const _require = require('../../package.json'),
+ version = _require.version;
+
+const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
+
+const expectCase = node => node.callee.name === 'expect' && node.arguments.length === 1 && node.parent && node.parent.type === 'MemberExpression' && node.parent.parent;
+
+const expectNotCase = node => expectCase(node) && node.parent.parent.type === 'MemberExpression' && methodName(node) === 'not';
+
+const expectResolveCase = node => expectCase(node) && node.parent.parent.type === 'MemberExpression' && methodName(node) === 'resolve';
+
+const expectRejectCase = node => expectCase(node) && node.parent.parent.type === 'MemberExpression' && methodName(node) === 'reject';
+
+const expectToBeCase = (node, arg) => !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) && expectCase(node) && methodName(node) === 'toBe' && argument(node) && (argument(node).type === 'Literal' && argument(node).value === null && arg === null || argument(node).name === 'undefined' && arg === undefined);
+
+const expectNotToBeCase = (node, arg) => expectNotCase(node) && methodName2(node) === 'toBe' && argument2(node) && (argument2(node).type === 'Literal' && argument2(node).value === null && arg === null || argument2(node).name === 'undefined' && arg === undefined);
+
+const expectToEqualCase = (node, arg) => !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) && expectCase(node) && methodName(node) === 'toEqual' && argument(node) && (argument(node).type === 'Literal' && argument(node).value === null && arg === null || argument(node).name === 'undefined' && arg === undefined);
+
+const expectNotToEqualCase = (node, arg) => expectNotCase(node) && methodName2(node) === 'toEqual' && argument2(node) && (argument2(node).type === 'Literal' && argument2(node).value === null && arg === null || argument2(node).name === 'undefined' && arg === undefined);
+
+const method = node => node.parent.property;
+
+const method2 = node => node.parent.parent.property;
+
+const methodName = node => method(node).name;
+
+const methodName2 = node => method2(node).name;
+
+const argument = node => node.parent.parent.arguments && node.parent.parent.arguments[0];
+
+const argument2 = node => node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0];
+
+const describeAliases = Object.assign(Object.create(null), {
+ describe: true,
+ 'describe.only': true,
+ 'describe.skip': true,
+ fdescribe: true,
+ xdescribe: true
+});
+const testCaseNames = Object.assign(Object.create(null), {
+ fit: true,
+ it: true,
+ 'it.only': true,
+ 'it.skip': true,
+ test: true,
+ 'test.only': true,
+ 'test.skip': true,
+ xit: true,
+ xtest: true
+});
+
+const getNodeName = node => {
+ function joinNames(a, b) {
+ return a && b ? `${a}.${b}` : null;
+ }
+
+ switch (node && node.type) {
+ case 'Identifier':
+ return node.name;
+
+ case 'Literal':
+ return node.value;
+
+ case 'TemplateLiteral':
+ if (node.expressions.length === 0) return node.quasis[0].value.cooked;
+ break;
+
+ case 'MemberExpression':
+ return joinNames(getNodeName(node.object), getNodeName(node.property));
+ }
+
+ return null;
+};
+
+const isTestCase = node => node && node.type === 'CallExpression' && testCaseNames[getNodeName(node.callee)];
+
+const isDescribe = node => node.type === 'CallExpression' && describeAliases[getNodeName(node.callee)];
+
+const isFunction = node => node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
+
+const isString = node => node.type === 'Literal' && typeof node.value === 'string' || isTemplateLiteral(node);
+
+const isTemplateLiteral = node => node.type === 'TemplateLiteral';
+
+const hasExpressions = node => node.expressions && node.expressions.length > 0;
+
+const getStringValue = arg => isTemplateLiteral(arg) ? arg.quasis[0].value.raw : arg.value;
+/**
+ * Generates the URL to documentation for the given rule name. It uses the
+ * package version to build the link to a tagged version of the
+ * documentation file.
+ *
+ * @param {string} filename - Name of the eslint rule
+ * @returns {string} URL to the documentation for the given rule
+ */
+
+
+const getDocsUrl = filename => {
+ const ruleName = path.basename(filename, '.js');
+ return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
+};
+
+const collectReferences = scope => {
+ const locals = new Set();
+ const unresolved = new Set();
+ let currentScope = scope;
+
+ while (currentScope !== null) {
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = currentScope.variables[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ const ref = _step.value;
+ const isReferenceDefined = ref.defs.some(def => {
+ return def.type !== 'ImplicitGlobalVariable';
+ });
+
+ if (isReferenceDefined) {
+ locals.add(ref.name);
+ }
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+
+ var _iteratorNormalCompletion2 = true;
+ var _didIteratorError2 = false;
+ var _iteratorError2 = undefined;
+
+ try {
+ for (var _iterator2 = currentScope.through[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+ const ref = _step2.value;
+ unresolved.add(ref.identifier.name);
+ }
+ } catch (err) {
+ _didIteratorError2 = true;
+ _iteratorError2 = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
+ _iterator2.return();
+ }
+ } finally {
+ if (_didIteratorError2) {
+ throw _iteratorError2;
+ }
+ }
+ }
+
+ currentScope = currentScope.upper;
+ }
+
+ return {
+ locals,
+ unresolved
+ };
+};
+
+const scopeHasLocalReference = (scope, referenceName) => {
+ const references = collectReferences(scope);
+ return (// referenceName was found as a local variable or function declaration.
+ references.locals.has(referenceName) || // referenceName was not found as an unresolved reference,
+ // meaning it is likely not an implicit global reference.
+ !references.unresolved.has(referenceName)
+ );
+};
+
+function composeFixers(node) {
+ return (...fixers) => {
+ return fixerApi => {
+ return fixers.reduce((all, fixer) => [...all, fixer(node, fixerApi)], []);
+ };
+ };
+}
+
+module.exports = {
+ method,
+ method2,
+ argument,
+ argument2,
+ expectCase,
+ expectNotCase,
+ expectResolveCase,
+ expectRejectCase,
+ expectToBeCase,
+ expectNotToBeCase,
+ expectToEqualCase,
+ expectNotToEqualCase,
+ getNodeName,
+ getStringValue,
+ isDescribe,
+ isFunction,
+ isTemplateLiteral,
+ isTestCase,
+ isString,
+ hasExpressions,
+ getDocsUrl,
+ scopeHasLocalReference,
+ composeFixers
+};
\ No newline at end of file

lib/rules/valid-describe.js

@@ -0,0 +1,114 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ isDescribe = _require.isDescribe,
+ isFunction = _require.isFunction;
+
+const isAsync = node => node.async;
+
+const isString = node => node.type === 'Literal' && typeof node.value === 'string' || node.type === 'TemplateLiteral';
+
+const hasParams = node => node.params.length > 0;
+
+const paramsLocation = params => {
+ const _params = _slicedToArray(params, 1),
+ first = _params[0];
+
+ const last = params[params.length - 1];
+ return {
+ start: {
+ line: first.loc.start.line,
+ column: first.loc.start.column
+ },
+ end: {
+ line: last.loc.end.line,
+ column: last.loc.end.column
+ }
+ };
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (isDescribe(node)) {
+ if (node.arguments.length === 0) {
+ return context.report({
+ message: 'Describe requires name and callback arguments',
+ loc: node.loc
+ });
+ }
+
+ const _node$arguments = _slicedToArray(node.arguments, 1),
+ name = _node$arguments[0];
+
+ const _node$arguments2 = _slicedToArray(node.arguments, 2),
+ callbackFunction = _node$arguments2[1];
+
+ if (!isString(name)) {
+ context.report({
+ message: 'First argument must be name',
+ loc: paramsLocation(node.arguments)
+ });
+ }
+
+ if (callbackFunction === undefined) {
+ return context.report({
+ message: 'Describe requires name and callback arguments',
+ loc: paramsLocation(node.arguments)
+ });
+ }
+
+ if (!isFunction(callbackFunction)) {
+ return context.report({
+ message: 'Second argument must be function',
+ loc: paramsLocation(node.arguments)
+ });
+ }
+
+ if (isAsync(callbackFunction)) {
+ context.report({
+ message: 'No async describe callback',
+ node: callbackFunction
+ });
+ }
+
+ if (hasParams(callbackFunction)) {
+ context.report({
+ message: 'Unexpected argument(s) in describe callback',
+ loc: paramsLocation(callbackFunction.params)
+ });
+ }
+
+ if (callbackFunction.body.type === 'BlockStatement') {
+ callbackFunction.body.body.forEach(node => {
+ if (node.type === 'ReturnStatement') {
+ context.report({
+ message: 'Unexpected return statement in describe callback',
+ node
+ });
+ }
+ });
+ }
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/valid-expect-in-promise.js

@@ -0,0 +1,143 @@
+'use strict';
+
+function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
+
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
+
+function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
+
+function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl,
+ isFunction = _require.isFunction;
+
+const reportMsg = 'Promise should be returned to test its fulfillment or rejection';
+
+const isThenOrCatch = node => {
+ return node.property && (node.property.name === 'then' || node.property.name === 'catch');
+};
+
+const isExpectCallPresentInFunction = body => {
+ if (body.type === 'BlockStatement') {
+ return body.body.find(line => {
+ if (line.type === 'ExpressionStatement') return isExpectCall(line.expression);
+ if (line.type === 'ReturnStatement') return isExpectCall(line.argument);
+ });
+ } else {
+ return isExpectCall(body);
+ }
+};
+
+const isExpectCall = expression => {
+ return expression && expression.type === 'CallExpression' && expression.callee.type === 'MemberExpression' && expression.callee.object.type === 'CallExpression' && expression.callee.object.callee.name === 'expect';
+};
+
+const reportReturnRequired = (context, node) => {
+ context.report({
+ loc: {
+ end: {
+ column: node.parent.parent.loc.end.column,
+ line: node.parent.parent.loc.end.line
+ },
+ start: node.parent.parent.loc.start
+ },
+ message: reportMsg,
+ node
+ });
+};
+
+const isPromiseReturnedLater = (node, testFunctionBody) => {
+ let promiseName;
+
+ if (node.parent.parent.type === 'ExpressionStatement') {
+ promiseName = node.parent.parent.expression.callee.object.name;
+ } else if (node.parent.parent.type === 'VariableDeclarator') {
+ promiseName = node.parent.parent.id.name;
+ }
+
+ const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1];
+ return lastLineInTestFunc.type === 'ReturnStatement' && lastLineInTestFunc.argument.name === promiseName;
+};
+
+const isTestFunc = node => {
+ return node.type === 'CallExpression' && (node.callee.name === 'it' || node.callee.name === 'test');
+};
+
+const getFunctionBody = func => {
+ if (func.body.type === 'BlockStatement') return func.body.body;
+ return func.body; //arrow-short-hand-fn
+};
+
+const getTestFunction = node => {
+ let parent = node.parent;
+
+ while (parent) {
+ if (isFunction(parent) && isTestFunc(parent.parent)) {
+ return parent;
+ }
+
+ parent = parent.parent;
+ }
+};
+
+const isParentThenOrPromiseReturned = (node, testFunctionBody) => {
+ return testFunctionBody.type === 'CallExpression' || testFunctionBody.type === 'NewExpression' || node.parent.parent.type === 'ReturnStatement' || isPromiseReturnedLater(node, testFunctionBody) || isThenOrCatch(node.parent.parent);
+};
+
+const verifyExpectWithReturn = (promiseCallbacks, node, context, testFunctionBody) => {
+ promiseCallbacks.some(promiseCallback => {
+ if (promiseCallback && isFunction(promiseCallback)) {
+ if (isExpectCallPresentInFunction(promiseCallback.body) && !isParentThenOrPromiseReturned(node, testFunctionBody)) {
+ reportReturnRequired(context, node);
+ return true;
+ }
+ }
+ });
+};
+
+const isAwaitExpression = node => {
+ return node.parent.parent && node.parent.parent.type === 'AwaitExpression';
+};
+
+const isHavingAsyncCallBackParam = testFunction => {
+ try {
+ return testFunction.params[0].type === 'Identifier';
+ } catch (e) {
+ return false;
+ }
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ MemberExpression(node) {
+ if (node.type === 'MemberExpression' && isThenOrCatch(node) && node.parent.type === 'CallExpression' && !isAwaitExpression(node)) {
+ const testFunction = getTestFunction(node);
+
+ if (testFunction && !isHavingAsyncCallBackParam(testFunction)) {
+ const testFunctionBody = getFunctionBody(testFunction);
+
+ const _node$parent$argument = _slicedToArray(node.parent.arguments, 2),
+ fulfillmentCallback = _node$parent$argument[0],
+ rejectionCallback = _node$parent$argument[1]; // then block can have two args, fulfillment & rejection
+ // then block can have one args, fulfillment
+ // catch block can have one args, rejection
+ // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
+
+
+ verifyExpectWithReturn([fulfillmentCallback, rejectionCallback], node, context, testFunctionBody);
+ }
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/rules/valid-expect.js

@@ -0,0 +1,123 @@
+'use strict';
+/*
+ * This implementation is ported from from eslint-plugin-jasmine.
+ * MIT license, Tom Vincent.
+ */
+
+const _require = require('./util'),
+ getDocsUrl = _require.getDocsUrl;
+
+const expectProperties = ['not', 'resolves', 'rejects'];
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename)
+ }
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const calleeName = node.callee.name;
+
+ if (calleeName === 'expect') {
+ // checking "expect()" arguments
+ if (node.arguments.length > 1) {
+ const secondArgumentLocStart = node.arguments[1].loc.start;
+ const lastArgumentLocEnd = node.arguments[node.arguments.length - 1].loc.end;
+ context.report({
+ loc: {
+ end: {
+ column: lastArgumentLocEnd.column - 1,
+ line: lastArgumentLocEnd.line
+ },
+ start: secondArgumentLocStart
+ },
+ message: 'More than one argument was passed to expect().',
+ node
+ });
+ } else if (node.arguments.length === 0) {
+ const expectLength = calleeName.length;
+ context.report({
+ loc: {
+ end: {
+ column: node.loc.start.column + expectLength + 1,
+ line: node.loc.start.line
+ },
+ start: {
+ column: node.loc.start.column + expectLength,
+ line: node.loc.start.line
+ }
+ },
+ message: 'No arguments were passed to expect().',
+ node
+ });
+ } // something was called on `expect()`
+
+
+ if (node.parent && node.parent.type === 'MemberExpression' && node.parent.parent) {
+ let parentNode = node.parent;
+ let parentProperty = parentNode.property;
+ let propertyName = parentProperty.name;
+ let grandParent = parentNode.parent; // a property is accessed, get the next node
+
+ if (grandParent.type === 'MemberExpression') {
+ // a modifier is used, just get the next one
+ if (expectProperties.indexOf(propertyName) > -1) {
+ grandParent = grandParent.parent;
+ } else {
+ // only a few properties are allowed
+ context.report({
+ // For some reason `endColumn` isn't set in tests if `loc` is
+ // not added
+ loc: parentProperty.loc,
+ message: `"${propertyName}" is not a valid property of expect.`,
+ node: parentProperty
+ });
+ } // this next one should be the matcher
+
+
+ parentNode = parentNode.parent;
+ parentProperty = parentNode.property;
+ propertyName = parentProperty.name;
+ } // matcher was not called
+
+
+ if (grandParent.type === 'ExpressionStatement') {
+ let message;
+
+ if (expectProperties.indexOf(propertyName) > -1) {
+ message = `"${propertyName}" needs to call a matcher.`;
+ } else {
+ message = `"${propertyName}" was not called.`;
+ }
+
+ context.report({
+ // For some reason `endColumn` isn't set in tests if `loc` is not
+ // added
+ loc: parentProperty.loc,
+ message,
+ node: parentProperty
+ });
+ }
+ }
+ }
+ },
+
+ // nothing called on "expect()"
+ 'CallExpression:exit'(node) {
+ if (node.callee.name === 'expect' && node.parent.type === 'ExpressionStatement') {
+ context.report({
+ // For some reason `endColumn` isn't set in tests if `loc` is not
+ // added
+ loc: node.loc,
+ message: 'No assertion was called on expect().',
+ node
+ });
+ }
+ }
+
+ };
+ }
+
+};
\ No newline at end of file

lib/__tests__/rules.test.js

@@ -0,0 +1,29 @@
+'use strict';
+
+const fs = require('fs');
+
+const path = require('path');
+
+const _require = require('../'),
+ rules = _require.rules;
+
+const ruleNames = Object.keys(rules);
+const numberOfRules = 32;
+describe('rules', () => {
+ it('should have a corresponding doc for each rule', () => {
+ ruleNames.forEach(rule => {
+ const docPath = path.resolve(__dirname, '../../docs/rules', `${rule}.md`);
+
+ if (!fs.existsSync(docPath)) {
+ throw new Error(`Could not find documentation file for rule "${rule}" in path "${docPath}"`);
+ }
+ });
+ });
+ it('should have the correct amount of rules', () => {
+ const length = ruleNames.length;
+
+ if (length !== numberOfRules) {
+ throw new Error(`There should be exactly ${numberOfRules} rules, but there are ${length}. If you've added a new rule, please update this number.`);
+ }
+ });
+});
\ No newline at end of file

package.json

@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-jest",
- "version": "22.5.1",
+ "version": "22.6.4",
"description": "Eslint rules for Jest",
"repository": "jest-community/eslint-plugin-jest",
"license": "MIT",
@@ -16,10 +16,10 @@
},
"files": [
"docs/",
- "rules/",
- "processors/",
- "index.js"
+ "src/",
+ "lib/"
],
+ "main": "lib/",
"engines": {
"node": ">=6"
},
@@ -27,13 +27,20 @@
"eslint": ">=5"
},
"scripts": {
+ "prepare": "yarn build",
"lint": "eslint . --ignore-pattern '!.eslintrc.js'",
"prettylint": "prettylint docs/**/*.md README.md package.json",
- "test": "jest"
+ "prepublishOnly": "yarn build",
+ "test": "jest",
+ "build": "babel src --out-dir lib"
},
"devDependencies": {
+ "@babel/cli": "^7.4.4",
+ "@babel/core": "^7.4.4",
+ "@babel/preset-env": "^7.4.4",
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.1",
+ "babel-jest": "^24.8.0",
"eslint": "^5.1.0",
"eslint-config-prettier": "^4.1.0",
"eslint-plugin-eslint-plugin": "^2.0.0",
@@ -49,7 +56,7 @@
"prettier": {
"proseWrap": "always",
"singleQuote": true,
- "trailingComma": "es5"
+ "trailingComma": "all"
},
"lint-staged": {
"*.js": [
@@ -73,13 +80,19 @@
"projects": [
{
"displayName": "test",
- "testEnvironment": "node"
+ "testEnvironment": "node",
+ "testPathIgnorePatterns": [
+ "<rootDir>/lib/.*"
+ ]
},
{
"displayName": "lint",
"runner": "jest-runner-eslint",
"testMatch": [
"<rootDir>/**/*.js"
+ ],
+ "testPathIgnorePatterns": [
+ "<rootDir>/lib/.*"
]
}
]

processors/snapshot-processor.js

@@ -1,15 +0,0 @@
-'use strict';
-
-// https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
-const preprocess = source => [source];
-
-const postprocess = messages =>
- messages[0].filter(
- // snapshot files should only be linted with snapshot specific rules
- message => message.ruleId === 'jest/no-large-snapshots'
- );
-
-module.exports = {
- preprocess,
- postprocess,
-};

processors/__tests__/snapshot-processor.test.js

@@ -1,37 +0,0 @@
-'use strict';
-
-const snapshotProcessor = require('../snapshot-processor');
-
-describe('snapshot-processor', () => {
- it('exports an object with preprocess and postprocess functions', () => {
- expect(snapshotProcessor).toMatchObject({
- preprocess: expect.any(Function),
- postprocess: expect.any(Function),
- });
- });
-
- describe('preprocess function', () => {
- it('should pass on untouched source code to source array', () => {
- const { preprocess } = snapshotProcessor;
- const sourceCode = "const name = 'johnny bravo';";
- const result = preprocess(sourceCode);
-
- expect(result).toEqual([sourceCode]);
- });
- });
-
- describe('postprocess function', () => {
- it('should only return messages about snapshot specific rules', () => {
- const { postprocess } = snapshotProcessor;
- const result = postprocess([
- [
- { ruleId: 'no-console' },
- { ruleId: 'global-require' },
- { ruleId: 'jest/no-large-snapshots' },
- ],
- ]);
-
- expect(result).toEqual([{ ruleId: 'jest/no-large-snapshots' }]);
- });
- });
-});

README.md

@@ -96,6 +96,7 @@
| [lowercase-name][] | Disallow capitalized test names | | ![fixable-green][] |
| [no-alias-methods][] | Disallow alias methods | ![recommended][] | ![fixable-green][] |
| [no-disabled-tests][] | Disallow disabled tests | ![recommended][] | |
+| [no-commented-out-tests][] | Disallow commented out tests | | |
| [no-empty-title][] | Disallow empty titles | | |
| [no-focused-tests][] | Disallow focused tests | ![recommended][] | |
| [no-hooks][] | Disallow setup and teardown hooks | | |
@@ -128,11 +129,21 @@
- [eslint-plugin-mocha](https://github.com/lo1tuma/eslint-plugin-mocha)
- [eslint-plugin-jasmine](https://github.com/tlvince/eslint-plugin-jasmine)
+## Related Projects
+
+### eslint-plugin-jest-formatting
+
+This project aims to provide formatting rules (auto-fixable where possible) to
+ensure consistency and readability in jest test suites.
+
+https://github.com/dangreenisrael/eslint-plugin-jest-formatting
+
[consistent-test-it]: docs/rules/consistent-test-it.md
[expect-expect]: docs/rules/expect-expect.md
[lowercase-name]: docs/rules/lowercase-name.md
[no-alias-methods]: docs/rules/no-alias-methods.md
[no-disabled-tests]: docs/rules/no-disabled-tests.md
+[no-commented-out-tests]: docs/rules/no-commented-out-tests.md
[no-empty-title]: docs/rules/no-empty-title.md
[no-focused-tests]: docs/rules/no-focused-tests.md
[no-hooks]: docs/rules/no-hooks.md

rules/consistent-test-it.js

@@ -1,121 +0,0 @@
-'use strict';
-
-const { getDocsUrl, getNodeName, isTestCase, isDescribe } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- schema: [
- {
- type: 'object',
- properties: {
- fn: {
- enum: ['it', 'test'],
- },
- withinDescribe: {
- enum: ['it', 'test'],
- },
- },
- additionalProperties: false,
- },
- ],
- },
- create(context) {
- const configObj = context.options[0] || {};
- const testKeyword = configObj.fn || 'test';
- const testKeywordWithinDescribe =
- configObj.withinDescribe || configObj.fn || 'it';
-
- let describeNestingLevel = 0;
-
- return {
- CallExpression(node) {
- const nodeName = getNodeName(node.callee);
-
- if (isDescribe(node)) {
- describeNestingLevel++;
- }
-
- if (
- isTestCase(node) &&
- describeNestingLevel === 0 &&
- nodeName.indexOf(testKeyword) === -1
- ) {
- const oppositeTestKeyword = getOppositeTestKeyword(testKeyword);
-
- context.report({
- message:
- "Prefer using '{{ testKeyword }}' instead of '{{ oppositeTestKeyword }}'",
- node: node.callee,
- data: { testKeyword, oppositeTestKeyword },
- fix(fixer) {
- const nodeToReplace =
- node.callee.type === 'MemberExpression'
- ? node.callee.object
- : node.callee;
-
- const fixedNodeName = getPreferredNodeName(nodeName, testKeyword);
- return [fixer.replaceText(nodeToReplace, fixedNodeName)];
- },
- });
- }
-
- if (
- isTestCase(node) &&
- describeNestingLevel > 0 &&
- nodeName.indexOf(testKeywordWithinDescribe) === -1
- ) {
- const oppositeTestKeyword = getOppositeTestKeyword(
- testKeywordWithinDescribe
- );
-
- context.report({
- message:
- "Prefer using '{{ testKeywordWithinDescribe }}' instead of '{{ oppositeTestKeyword }}' within describe",
- node: node.callee,
- data: { testKeywordWithinDescribe, oppositeTestKeyword },
- fix(fixer) {
- const nodeToReplace =
- node.callee.type === 'MemberExpression'
- ? node.callee.object
- : node.callee;
-
- const fixedNodeName = getPreferredNodeName(
- nodeName,
- testKeywordWithinDescribe
- );
- return [fixer.replaceText(nodeToReplace, fixedNodeName)];
- },
- });
- }
- },
- 'CallExpression:exit'(node) {
- if (isDescribe(node)) {
- describeNestingLevel--;
- }
- },
- };
- },
-};
-
-function getPreferredNodeName(nodeName, preferredTestKeyword) {
- switch (nodeName) {
- case 'fit':
- return 'test.only';
- default:
- return nodeName.startsWith('f') || nodeName.startsWith('x')
- ? nodeName.charAt(0) + preferredTestKeyword
- : preferredTestKeyword;
- }
-}
-
-function getOppositeTestKeyword(test) {
- if (test === 'test') {
- return 'it';
- }
-
- return 'test';
-}

rules/expect-expect.js

@@ -1,62 +0,0 @@
-'use strict';
-
-/*
- * This implementation is adapted from eslint-plugin-jasmine.
- * MIT license, Remco Haszing.
- */
-
-const { getDocsUrl, getNodeName } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- schema: [
- {
- type: 'object',
- properties: {
- assertFunctionNames: {
- type: 'array',
- items: [{ type: 'string' }],
- },
- },
- additionalProperties: false,
- },
- ],
- },
- create(context) {
- const unchecked = [];
- const assertFunctionNames = new Set(
- context.options[0] && context.options[0].assertFunctionNames
- ? context.options[0].assertFunctionNames
- : ['expect']
- );
-
- return {
- CallExpression(node) {
- const name = getNodeName(node.callee);
- if (name === 'it' || name === 'test') {
- unchecked.push(node);
- } else if (assertFunctionNames.has(name)) {
- // Return early in case of nested `it` statements.
- for (const ancestor of context.getAncestors()) {
- const index = unchecked.indexOf(ancestor);
- if (index !== -1) {
- unchecked.splice(index, 1);
- break;
- }
- }
- }
- },
- 'Program:exit'() {
- unchecked.forEach(node =>
- context.report({
- message: 'Test has no assertions',
- node,
- })
- );
- },
- };
- },
-};

rules/lowercase-name.js

@@ -1,97 +0,0 @@
-'use strict';
-
-const { getDocsUrl } = require('./util');
-
-const isItTestOrDescribeFunction = node => {
- return (
- node.type === 'CallExpression' &&
- node.callee &&
- (node.callee.name === 'it' ||
- node.callee.name === 'test' ||
- node.callee.name === 'describe')
- );
-};
-
-const isItDescription = node => {
- return (
- node.arguments &&
- node.arguments[0] &&
- (node.arguments[0].type === 'Literal' ||
- node.arguments[0].type === 'TemplateLiteral')
- );
-};
-
-const testDescription = node => {
- const [firstArgument] = node.arguments;
- const { type } = firstArgument;
-
- if (type === 'Literal') {
- return firstArgument.value;
- }
-
- // `isItDescription` guarantees this is `type === 'TemplateLiteral'`
- return firstArgument.quasis[0].value.raw;
-};
-
-const descriptionBeginsWithLowerCase = node => {
- if (isItTestOrDescribeFunction(node) && isItDescription(node)) {
- const description = testDescription(node);
- if (!description[0]) {
- return false;
- }
-
- if (description[0] !== description[0].toLowerCase()) {
- return node.callee.name;
- }
- }
- return false;
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- const ignore = (context.options[0] && context.options[0].ignore) || [];
- const ignoredFunctionNames = ignore.reduce((accumulator, value) => {
- accumulator[value] = true;
- return accumulator;
- }, Object.create(null));
-
- const isIgnoredFunctionName = node =>
- ignoredFunctionNames[node.callee.name];
-
- return {
- CallExpression(node) {
- const erroneousMethod = descriptionBeginsWithLowerCase(node);
-
- if (erroneousMethod && !isIgnoredFunctionName(node)) {
- context.report({
- message: '`{{ method }}`s should begin with lowercase',
- data: { method: erroneousMethod },
- node,
- fix(fixer) {
- const [firstArg] = node.arguments;
- const description = testDescription(node);
-
- const rangeIgnoringQuotes = [
- firstArg.range[0] + 1,
- firstArg.range[1] - 1,
- ];
- const newDescription =
- description.substring(0, 1).toLowerCase() +
- description.substring(1);
-
- return [
- fixer.replaceTextRange(rangeIgnoringQuotes, newDescription),
- ];
- },
- });
- }
- },
- };
- },
-};

rules/no-alias-methods.js

@@ -1,69 +0,0 @@
-'use strict';
-
-const { expectCase, getDocsUrl, method } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- // The Jest methods which have aliases. The canonical name is the first
- // index of each item.
- const methodNames = [
- ['toHaveBeenCalled', 'toBeCalled'],
- ['toHaveBeenCalledTimes', 'toBeCalledTimes'],
- ['toHaveBeenCalledWith', 'toBeCalledWith'],
- ['toHaveBeenLastCalledWith', 'lastCalledWith'],
- ['toHaveBeenNthCalledWith', 'nthCalledWith'],
- ['toHaveReturned', 'toReturn'],
- ['toHaveReturnedTimes', 'toReturnTimes'],
- ['toHaveReturnedWith', 'toReturnWith'],
- ['toHaveLastReturnedWith', 'lastReturnedWith'],
- ['toHaveNthReturnedWith', 'nthReturnedWith'],
- ['toThrow', 'toThrowError'],
- ];
-
- return {
- CallExpression(node) {
- if (!expectCase(node)) {
- return;
- }
-
- let targetNode = method(node);
- if (
- targetNode.name === 'resolves' ||
- targetNode.name === 'rejects' ||
- targetNode.name === 'not'
- ) {
- targetNode = method(node.parent);
- }
-
- if (!targetNode) {
- return;
- }
-
- // Check if the method used matches any of ours
- const methodItem = methodNames.find(
- item => item[1] === targetNode.name
- );
-
- if (methodItem) {
- context.report({
- message: `Replace {{ replace }}() with its canonical name of {{ canonical }}()`,
- data: {
- replace: methodItem[1],
- canonical: methodItem[0],
- },
- node: targetNode,
- fix(fixer) {
- return [fixer.replaceText(targetNode, methodItem[0])];
- },
- });
- }
- },
- };
- },
-};

rules/no-disabled-tests.js

@@ -1,78 +0,0 @@
-'use strict';
-
-const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- let suiteDepth = 0;
- let testDepth = 0;
-
- return {
- 'CallExpression[callee.name="describe"]'() {
- suiteDepth++;
- },
- 'CallExpression[callee.name=/^(it|test)$/]'() {
- testDepth++;
- },
- 'CallExpression[callee.name=/^(it|test)$/][arguments.length<2]'(node) {
- context.report({
- message: 'Test is missing function argument',
- node,
- });
- },
- CallExpression(node) {
- const functionName = getNodeName(node.callee);
-
- switch (functionName) {
- case 'describe.skip':
- context.report({ message: 'Skipped test suite', node });
- break;
-
- case 'it.skip':
- case 'test.skip':
- context.report({ message: 'Skipped test', node });
- break;
- }
- },
- 'CallExpression[callee.name="pending"]'(node) {
- if (scopeHasLocalReference(context.getScope(), 'pending')) {
- return;
- }
-
- if (testDepth > 0) {
- context.report({
- message: 'Call to pending() within test',
- node,
- });
- } else if (suiteDepth > 0) {
- context.report({
- message: 'Call to pending() within test suite',
- node,
- });
- } else {
- context.report({
- message: 'Call to pending()',
- node,
- });
- }
- },
- 'CallExpression[callee.name="xdescribe"]'(node) {
- context.report({ message: 'Disabled test suite', node });
- },
- 'CallExpression[callee.name=/^xit|xtest$/]'(node) {
- context.report({ message: 'Disabled test', node });
- },
- 'CallExpression[callee.name="describe"]:exit'() {
- suiteDepth--;
- },
- 'CallExpression[callee.name=/^it|test$/]:exit'() {
- testDepth--;
- },
- };
- },
-};

rules/no-empty-title.js

@@ -1,54 +0,0 @@
-'use strict';
-
-const {
- getDocsUrl,
- hasExpressions,
- isDescribe,
- isTestCase,
- isTemplateLiteral,
- isString,
- getStringValue,
-} = require('./util');
-
-const errorMessages = {
- describe: 'describe should not have an empty title',
- test: 'test should not have an empty title',
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- const is = {
- describe: isDescribe(node),
- testCase: isTestCase(node),
- };
- if (!is.describe && !is.testCase) {
- return;
- }
- const [firstArgument] = node.arguments;
- if (!isString(firstArgument)) {
- return;
- }
- if (isTemplateLiteral(firstArgument) && hasExpressions(firstArgument)) {
- return;
- }
- if (getStringValue(firstArgument) === '') {
- const message = is.describe
- ? errorMessages.describe
- : errorMessages.test;
- context.report({
- message,
- node,
- });
- }
- },
- };
- },
- errorMessages,
-};

rules/no-focused-tests.js

@@ -1,73 +0,0 @@
-'use strict';
-
-const { getDocsUrl } = require('./util');
-
-const testFunctions = Object.assign(Object.create(null), {
- describe: true,
- it: true,
- test: true,
-});
-
-const matchesTestFunction = object => object && testFunctions[object.name];
-
-const isCallToFocusedTestFunction = object =>
- object && object.name[0] === 'f' && testFunctions[object.name.substring(1)];
-
-const isPropertyNamedOnly = property =>
- property && (property.name === 'only' || property.value === 'only');
-
-const isCallToTestOnlyFunction = callee =>
- matchesTestFunction(callee.object) && isPropertyNamedOnly(callee.property);
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create: context => ({
- CallExpression(node) {
- const { callee } = node;
-
- if (callee.type === 'MemberExpression') {
- if (
- callee.object.type === 'Identifier' &&
- isCallToFocusedTestFunction(callee.object)
- ) {
- context.report({
- message: 'Unexpected focused test.',
- node: callee.object,
- });
- return;
- }
-
- if (
- callee.object.type === 'MemberExpression' &&
- isCallToTestOnlyFunction(callee.object)
- ) {
- context.report({
- message: 'Unexpected focused test.',
- node: callee.object.property,
- });
- return;
- }
-
- if (isCallToTestOnlyFunction(callee)) {
- context.report({
- message: 'Unexpected focused test.',
- node: callee.property,
- });
- return;
- }
- }
-
- if (callee.type === 'Identifier' && isCallToFocusedTestFunction(callee)) {
- context.report({
- message: 'Unexpected focused test.',
- node: callee,
- });
- return;
- }
- },
- }),
-};

rules/no-hooks.js

@@ -1,53 +0,0 @@
-'use strict';
-
-const { getDocsUrl } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- schema: [
- {
- type: 'object',
- properties: {
- allow: {
- type: 'array',
- contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'],
- },
- },
- additionalProperties: false,
- },
- ],
- create(context) {
- const testHookNames = Object.assign(Object.create(null), {
- beforeAll: true,
- beforeEach: true,
- afterAll: true,
- afterEach: true,
- });
-
- const whitelistedHookNames = (
- context.options[0] || { allow: [] }
- ).allow.reduce((hashMap, value) => {
- hashMap[value] = true;
- return hashMap;
- }, Object.create(null));
-
- const isHook = node => testHookNames[node.callee.name];
- const isWhitelisted = node => whitelistedHookNames[node.callee.name];
-
- return {
- CallExpression(node) {
- if (isHook(node) && !isWhitelisted(node)) {
- context.report({
- node,
- message: "Unexpected '{{ hookName }}' hook",
- data: { hookName: node.callee.name },
- });
- }
- },
- };
- },
-};

rules/no-identical-title.js

@@ -1,88 +0,0 @@
-'use strict';
-
-const {
- getDocsUrl,
- isDescribe,
- isTestCase,
- isString,
- hasExpressions,
- getStringValue,
-} = require('./util');
-
-const newDescribeContext = () => ({
- describeTitles: [],
- testTitles: [],
-});
-
-const handleTestCaseTitles = (context, titles, node, title) => {
- if (isTestCase(node)) {
- if (titles.indexOf(title) !== -1) {
- context.report({
- message:
- 'Test title is used multiple times in the same describe block.',
- node,
- });
- }
- titles.push(title);
- }
-};
-
-const handleDescribeBlockTitles = (context, titles, node, title) => {
- if (!isDescribe(node)) {
- return;
- }
- if (titles.indexOf(title) !== -1) {
- context.report({
- message:
- 'Describe block title is used multiple times in the same describe block.',
- node,
- });
- }
- titles.push(title);
-};
-
-const isFirstArgValid = arg => {
- if (!arg || !isString(arg)) {
- return false;
- }
- if (arg.type === 'TemplateLiteral' && hasExpressions(arg)) {
- return false;
- }
- return true;
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- const contexts = [newDescribeContext()];
- return {
- CallExpression(node) {
- const currentLayer = contexts[contexts.length - 1];
- if (isDescribe(node)) {
- contexts.push(newDescribeContext());
- }
- const [firstArgument] = node.arguments;
- if (!isFirstArgValid(firstArgument)) {
- return;
- }
- const title = getStringValue(firstArgument);
- handleTestCaseTitles(context, currentLayer.testTitles, node, title);
- handleDescribeBlockTitles(
- context,
- currentLayer.describeTitles,
- node,
- title
- );
- },
- 'CallExpression:exit'(node) {
- if (isDescribe(node)) {
- contexts.pop();
- }
- },
- };
- },
-};

rules/no-jasmine-globals.js

@@ -1,149 +0,0 @@
-'use strict';
-
-const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- messages: {
- illegalGlobal:
- 'Illegal usage of global `{{ global }}`, prefer `{{ replacement }}`',
- illegalMethod:
- 'Illegal usage of `{{ method }}`, prefer `{{ replacement }}`',
- illegalFail:
- 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
- illegalPending:
- 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
- illegalJasmine: 'Illegal usage of jasmine global',
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- const calleeName = getNodeName(node.callee);
-
- if (!calleeName) {
- return;
- }
- if (
- calleeName === 'spyOn' ||
- calleeName === 'spyOnProperty' ||
- calleeName === 'fail' ||
- calleeName === 'pending'
- ) {
- if (scopeHasLocalReference(context.getScope(), calleeName)) {
- // It's a local variable, not a jasmine global.
- return;
- }
-
- switch (calleeName) {
- case 'spyOn':
- case 'spyOnProperty':
- context.report({
- node,
- messageId: 'illegalGlobal',
- data: { global: calleeName, replacement: 'jest.spyOn' },
- });
- break;
- case 'fail':
- context.report({
- node,
- messageId: 'illegalFail',
- });
- break;
- case 'pending':
- context.report({
- node,
- messageId: 'illegalPending',
- });
- break;
- }
- return;
- }
-
- if (calleeName.startsWith('jasmine.')) {
- const functionName = calleeName.replace('jasmine.', '');
-
- if (
- functionName === 'any' ||
- functionName === 'anything' ||
- functionName === 'arrayContaining' ||
- functionName === 'objectContaining' ||
- functionName === 'stringMatching'
- ) {
- context.report({
- fix(fixer) {
- return [fixer.replaceText(node.callee.object, 'expect')];
- },
- node,
- messageId: 'illegalMethod',
- data: {
- method: calleeName,
- replacement: `expect.${functionName}`,
- },
- });
- return;
- }
-
- if (functionName === 'addMatchers') {
- context.report({
- node,
- messageId: 'illegalMethod',
- data: {
- method: calleeName,
- replacement: `expect.extend`,
- },
- });
- return;
- }
-
- if (functionName === 'createSpy') {
- context.report({
- node,
- messageId: 'illegalMethod',
- data: {
- method: calleeName,
- replacement: 'jest.fn',
- },
- });
- return;
- }
-
- context.report({
- node,
- messageId: 'illegalJasmine',
- });
- }
- },
- MemberExpression(node) {
- if (node.object.name === 'jasmine') {
- if (node.parent.type === 'AssignmentExpression') {
- if (node.property.name === 'DEFAULT_TIMEOUT_INTERVAL') {
- context.report({
- fix(fixer) {
- return [
- fixer.replaceText(
- node.parent,
- `jest.setTimeout(${node.parent.right.value})`
- ),
- ];
- },
- node,
- message: 'Illegal usage of jasmine global',
- });
- return;
- }
-
- context.report({
- node,
- message: 'Illegal usage of jasmine global',
- });
- }
- }
- },
- };
- },
-};

rules/no-jest-import.js

@@ -1,26 +0,0 @@
-'use strict';
-
-const { getDocsUrl } = require('./util');
-
-const message = `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`;
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- 'ImportDeclaration[source.value="jest"]'(node) {
- context.report({ node, message });
- },
- 'CallExpression[callee.name="require"][arguments.0.value="jest"]'(node) {
- context.report({
- loc: node.arguments[0].loc,
- message,
- });
- },
- };
- },
-};

rules/no-large-snapshots.js

@@ -1,56 +0,0 @@
-'use strict';
-
-const { getDocsUrl } = require('./util');
-
-const reportOnViolation = (context, node) => {
- const lineLimit =
- context.options[0] && Number.isFinite(context.options[0].maxSize)
- ? context.options[0].maxSize
- : 50;
- const startLine = node.loc.start.line;
- const endLine = node.loc.end.line;
- const lineCount = endLine - startLine;
-
- if (lineCount > lineLimit) {
- context.report({
- message:
- lineLimit === 0
- ? 'Expected to not encounter a Jest snapshot but was found with {{ lineCount }} lines long'
- : 'Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long',
- data: { lineLimit, lineCount },
- node,
- });
- }
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- if (context.getFilename().endsWith('.snap')) {
- return {
- ExpressionStatement(node) {
- reportOnViolation(context, node);
- },
- };
- } else if (context.getFilename().endsWith('.js')) {
- return {
- CallExpression(node) {
- const propertyName =
- node.callee.property && node.callee.property.name;
- if (
- propertyName === 'toMatchInlineSnapshot' ||
- propertyName === 'toThrowErrorMatchingInlineSnapshot'
- ) {
- reportOnViolation(context, node);
- }
- },
- };
- }
-
- return {};
- },
-};

rules/no-mocks-import.js

@@ -1,38 +0,0 @@
-'use strict';
-
-const { posix } = require('path');
-const { getDocsUrl } = require('./util');
-
-const mocksDirName = '__mocks__';
-const message = `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use jest.mock and import from the original module path.`;
-
-const isMockPath = path => path.split(posix.sep).includes(mocksDirName);
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- ImportDeclaration(node) {
- if (isMockPath(node.source.value)) {
- context.report({ node, message });
- }
- },
- 'CallExpression[callee.name="require"]'(node) {
- if (
- node.arguments.length &&
- node.arguments[0].value &&
- isMockPath(node.arguments[0].value)
- ) {
- context.report({
- loc: node.arguments[0].loc,
- message,
- });
- }
- },
- };
- },
-};

rules/no-test-callback.js

@@ -1,79 +0,0 @@
-'use strict';
-
-const { getDocsUrl, isTestCase } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- if (!isTestCase(node) || node.arguments.length !== 2) {
- return;
- }
-
- const [, callback] = node.arguments;
-
- if (
- !/^(Arrow)?FunctionExpression$/.test(callback.type) ||
- callback.params.length !== 1
- ) {
- return;
- }
-
- const [argument] = callback.params;
- context.report({
- node: argument,
- message: 'Illegal usage of test callback',
- fix(fixer) {
- const sourceCode = context.getSourceCode();
- const { body } = callback;
- const firstBodyToken = sourceCode.getFirstToken(body);
- const lastBodyToken = sourceCode.getLastToken(body);
- const tokenBeforeArgument = sourceCode.getTokenBefore(argument);
- const tokenAfterArgument = sourceCode.getTokenAfter(argument);
- const argumentInParens =
- tokenBeforeArgument.value === '(' &&
- tokenAfterArgument.value === ')';
-
- let argumentFix = fixer.replaceText(argument, '()');
-
- if (argumentInParens) {
- argumentFix = fixer.remove(argument);
- }
-
- let newCallback = argument.name;
-
- if (argumentInParens) {
- newCallback = `(${newCallback})`;
- }
-
- let beforeReplacement = `new Promise(${newCallback} => `;
- let afterReplacement = ')';
- let replaceBefore = true;
-
- if (body.type === 'BlockStatement') {
- const keyword = callback.async ? 'await' : 'return';
-
- beforeReplacement = `${keyword} ${beforeReplacement}{`;
- afterReplacement += '}';
- replaceBefore = false;
- }
-
- return [
- argumentFix,
- replaceBefore
- ? fixer.insertTextBefore(firstBodyToken, beforeReplacement)
- : fixer.insertTextAfter(firstBodyToken, beforeReplacement),
- fixer.insertTextAfter(lastBodyToken, afterReplacement),
- ];
- },
- });
- },
- };
- },
-};

rules/no-test-prefixes.js

@@ -1,46 +0,0 @@
-'use strict';
-
-const { getDocsUrl, getNodeName, isTestCase, isDescribe } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- const nodeName = getNodeName(node.callee);
-
- if (!isDescribe(node) && !isTestCase(node)) return;
-
- const preferredNodeName = getPreferredNodeName(nodeName);
-
- if (!preferredNodeName) return;
-
- context.report({
- message: 'Use "{{ preferredNodeName }}" instead',
- node: node.callee,
- data: { preferredNodeName },
- fix(fixer) {
- return [fixer.replaceText(node.callee, preferredNodeName)];
- },
- });
- },
- };
- },
-};
-
-function getPreferredNodeName(nodeName) {
- const firstChar = nodeName.charAt(0);
-
- if (firstChar === 'f') {
- return `${nodeName.slice(1)}.only`;
- }
-
- if (firstChar === 'x') {
- return `${nodeName.slice(1)}.skip`;
- }
-}

rules/no-test-return-statement.js

@@ -1,41 +0,0 @@
-'use strict';
-
-const { getDocsUrl, isFunction, isTestCase } = require('./util');
-
-const MESSAGE = 'Jest tests should not return a value.';
-const RETURN_STATEMENT = 'ReturnStatement';
-const BLOCK_STATEMENT = 'BlockStatement';
-
-const getBody = args => {
- if (
- args.length > 1 &&
- isFunction(args[1]) &&
- args[1].body.type === BLOCK_STATEMENT
- ) {
- return args[1].body.body;
- }
- return [];
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- if (!isTestCase(node)) return;
- const body = getBody(node.arguments);
- const returnStmt = body.find(t => t.type === RETURN_STATEMENT);
- if (!returnStmt) return;
-
- context.report({
- message: MESSAGE,
- node: returnStmt,
- });
- },
- };
- },
-};

rules/no-truthy-falsy.js

@@ -1,44 +0,0 @@
-'use strict';
-
-const {
- getDocsUrl,
- expectCase,
- expectNotCase,
- expectResolveCase,
- expectRejectCase,
- method,
-} = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- if (
- expectCase(node) ||
- expectNotCase(node) ||
- expectResolveCase(node) ||
- expectRejectCase(node)
- ) {
- const targetNode =
- node.parent.parent.type === 'MemberExpression' ? node.parent : node;
-
- const methodNode = method(targetNode);
- const { name: methodName } = methodNode;
-
- if (methodName === 'toBeTruthy' || methodName === 'toBeFalsy') {
- context.report({
- data: { methodName },
- message: 'Avoid {{methodName}}',
- node: methodNode,
- });
- }
- }
- },
- };
- },
-};

rules/prefer-called-with.js

@@ -1,29 +0,0 @@
-'use strict';
-
-const { getDocsUrl, expectCase, expectNotCase, method } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- // Could check resolves/rejects here but not a likely idiom.
- if (expectCase(node) && !expectNotCase(node)) {
- const methodNode = method(node);
- const { name } = methodNode;
- if (name === 'toBeCalled' || name === 'toHaveBeenCalled') {
- context.report({
- data: { name },
- message: 'Prefer {{name}}With(/* expected args */)',
- node: methodNode,
- });
- }
- }
- },
- };
- },
-};

rules/prefer-expect-assertions.js

@@ -1,71 +0,0 @@
-'use strict';
-
-const { getDocsUrl } = require('./util');
-
-const ruleMsg =
- 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';
-
-const validateArguments = expression => {
- return (
- expression.arguments &&
- expression.arguments.length === 1 &&
- Number.isInteger(expression.arguments[0].value)
- );
-};
-
-const isExpectAssertionsOrHasAssertionsCall = expression => {
- try {
- const expectAssertionOrHasAssertionCall =
- expression.type === 'CallExpression' &&
- expression.callee.type === 'MemberExpression' &&
- expression.callee.object.name === 'expect' &&
- (expression.callee.property.name === 'assertions' ||
- expression.callee.property.name === 'hasAssertions');
-
- if (expression.callee.property.name === 'assertions') {
- return expectAssertionOrHasAssertionCall && validateArguments(expression);
- }
- return expectAssertionOrHasAssertionCall;
- } catch (e) {
- return false;
- }
-};
-
-const getFunctionFirstLine = functionBody => {
- return functionBody[0] && functionBody[0].expression;
-};
-
-const isFirstLineExprStmt = functionBody => {
- return functionBody[0] && functionBody[0].type === 'ExpressionStatement';
-};
-
-const reportMsg = (context, node) => {
- context.report({
- message: ruleMsg,
- node,
- });
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- 'CallExpression[callee.name=/^(it|test)$/][arguments.1.body.body]'(node) {
- const testFuncBody = node.arguments[1].body.body;
-
- if (!isFirstLineExprStmt(testFuncBody)) {
- reportMsg(context, node);
- } else {
- const testFuncFirstLine = getFunctionFirstLine(testFuncBody);
- if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) {
- reportMsg(context, node);
- }
- }
- },
- };
- },
-};

rules/prefer-inline-snapshots.js

@@ -1,46 +0,0 @@
-'use strict';
-
-const { getDocsUrl } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- const propertyName = node.callee.property && node.callee.property.name;
- if (propertyName === 'toMatchSnapshot') {
- context.report({
- fix(fixer) {
- return [
- fixer.replaceText(
- node.callee.property,
- 'toMatchInlineSnapshot'
- ),
- ];
- },
- message: 'Use toMatchInlineSnapshot() instead',
- node: node.callee.property,
- });
- } else if (propertyName === 'toThrowErrorMatchingSnapshot') {
- context.report({
- fix(fixer) {
- return [
- fixer.replaceText(
- node.callee.property,
- 'toThrowErrorMatchingInlineSnapshot'
- ),
- ];
- },
- message: 'Use toThrowErrorMatchingInlineSnapshot() instead',
- node: node.callee.property,
- });
- }
- },
- };
- },
-};

rules/prefer-spy-on.js

@@ -1,69 +0,0 @@
-'use strict';
-
-const { getDocsUrl, getNodeName } = require('./util');
-
-const getJestFnCall = node => {
- if (
- (node.type !== 'CallExpression' && node.type !== 'MemberExpression') ||
- (node.callee && node.callee.type !== 'MemberExpression')
- ) {
- return null;
- }
-
- const obj = node.callee ? node.callee.object : node.object;
-
- if (obj.type === 'Identifier') {
- return node.type === 'CallExpression' &&
- getNodeName(node.callee) === 'jest.fn'
- ? node
- : null;
- }
-
- return getJestFnCall(obj);
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- AssignmentExpression(node) {
- if (node.left.type !== 'MemberExpression') return;
-
- const jestFnCall = getJestFnCall(node.right);
-
- if (!jestFnCall) return;
-
- context.report({
- node,
- message: 'Use jest.spyOn() instead.',
- fix(fixer) {
- const leftPropQuote =
- node.left.property.type === 'Identifier' ? "'" : '';
- const [arg] = jestFnCall.arguments;
- const argSource = arg && context.getSourceCode().getText(arg);
- const mockImplementation = argSource
- ? `.mockImplementation(${argSource})`
- : '';
-
- return [
- fixer.insertTextBefore(node.left, `jest.spyOn(`),
- fixer.replaceTextRange(
- [node.left.object.range[1], node.left.property.range[0]],
- `, ${leftPropQuote}`
- ),
- fixer.replaceTextRange(
- [node.left.property.range[1], jestFnCall.range[1]],
- `${leftPropQuote})${mockImplementation}`
- ),
- ];
- },
- });
- },
- };
- },
-};

rules/prefer-strict-equal.js

@@ -1,33 +0,0 @@
-'use strict';
-
-const { expectCase, getDocsUrl, method } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- if (!expectCase(node)) {
- return;
- }
-
- const propertyName = method(node) && method(node).name;
-
- if (propertyName === 'toEqual') {
- context.report({
- fix(fixer) {
- return [fixer.replaceText(method(node), 'toStrictEqual')];
- },
- message: 'Use toStrictEqual() instead',
- node: method(node),
- });
- }
- },
- };
- },
-};

rules/prefer-to-be-null.js

@@ -1,50 +0,0 @@
-'use strict';
-
-const {
- getDocsUrl,
- argument,
- argument2,
- expectToBeCase,
- expectToEqualCase,
- expectNotToEqualCase,
- expectNotToBeCase,
- method,
- method2,
-} = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- const is = expectToBeCase(node, null) || expectToEqualCase(node, null);
- const isNot =
- expectNotToEqualCase(node, null) || expectNotToBeCase(node, null);
-
- if (is || isNot) {
- context.report({
- fix(fixer) {
- if (is) {
- return [
- fixer.replaceText(method(node), 'toBeNull'),
- fixer.remove(argument(node)),
- ];
- }
- return [
- fixer.replaceText(method2(node), 'toBeNull'),
- fixer.remove(argument2(node)),
- ];
- },
- message: 'Use toBeNull() instead',
- node: is ? method(node) : method2(node),
- });
- }
- },
- };
- },
-};

rules/prefer-to-be-undefined.js

@@ -1,52 +0,0 @@
-'use strict';
-
-const {
- argument,
- argument2,
- expectToBeCase,
- expectNotToBeCase,
- expectToEqualCase,
- expectNotToEqualCase,
- getDocsUrl,
- method,
- method2,
-} = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- const is =
- expectToBeCase(node, undefined) || expectToEqualCase(node, undefined);
- const isNot =
- expectNotToEqualCase(node, undefined) ||
- expectNotToBeCase(node, undefined);
-
- if (is || isNot) {
- context.report({
- fix(fixer) {
- if (is) {
- return [
- fixer.replaceText(method(node), 'toBeUndefined'),
- fixer.remove(argument(node)),
- ];
- }
- return [
- fixer.replaceText(method2(node), 'toBeUndefined'),
- fixer.remove(argument2(node)),
- ];
- },
- message: 'Use toBeUndefined() instead',
- node: is ? method(node) : method2(node),
- });
- }
- },
- };
- },
-};

rules/prefer-to-contain.js

@@ -1,129 +0,0 @@
-'use strict';
-
-const {
- getDocsUrl,
- expectCase,
- expectResolveCase,
- expectRejectCase,
- method,
- argument,
-} = require('./util');
-
-const isEqualityCheck = node =>
- method(node) &&
- (method(node).name === 'toBe' || method(node).name === 'toEqual');
-
-const isArgumentValid = node =>
- argument(node).value === true || argument(node).value === false;
-
-const hasOneArgument = node => node.arguments && node.arguments.length === 1;
-
-const isValidEqualityCheck = node =>
- isEqualityCheck(node) &&
- hasOneArgument(node.parent.parent) &&
- isArgumentValid(node);
-
-const isEqualityNegation = node =>
- method(node).name === 'not' && isValidEqualityCheck(node.parent);
-
-const hasIncludesMethod = node =>
- node.arguments[0] &&
- node.arguments[0].callee &&
- node.arguments[0].callee.property &&
- node.arguments[0].callee.property.name === 'includes';
-
-const isValidIncludesMethod = node =>
- hasIncludesMethod(node) && hasOneArgument(node.arguments[0]);
-
-const getNegationFixes = (node, sourceCode, fixer) => {
- const negationPropertyDot = sourceCode.getFirstTokenBetween(
- node.parent.object,
- node.parent.property,
- token => token.value === '.'
- );
- const toContainFunc =
- isEqualityNegation(node) && argument(node.parent).value
- ? 'not.toContain'
- : 'toContain';
-
- //.includes function argument
- const [containArg] = node.arguments[0].arguments;
- return [
- fixer.remove(negationPropertyDot),
- fixer.remove(method(node)),
- fixer.replaceText(method(node.parent), toContainFunc),
- fixer.replaceText(argument(node.parent), sourceCode.getText(containArg)),
- ];
-};
-
-const getCommonFixes = (node, sourceCode, fixer) => {
- const [containArg] = node.arguments[0].arguments;
- const includesCaller = node.arguments[0].callee;
-
- const propertyDot = sourceCode.getFirstTokenBetween(
- includesCaller.object,
- includesCaller.property,
- token => token.value === '.'
- );
-
- const closingParenthesis = sourceCode.getTokenAfter(containArg);
- const openParenthesis = sourceCode.getTokenBefore(containArg);
-
- return [
- fixer.remove(containArg),
- fixer.remove(includesCaller.property),
- fixer.remove(propertyDot),
- fixer.remove(closingParenthesis),
- fixer.remove(openParenthesis),
- ];
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- if (
- !(expectResolveCase(node) || expectRejectCase(node)) &&
- expectCase(node) &&
- (isEqualityNegation(node) || isValidEqualityCheck(node)) &&
- isValidIncludesMethod(node)
- ) {
- context.report({
- fix(fixer) {
- const sourceCode = context.getSourceCode();
-
- let fixArr = getCommonFixes(node, sourceCode, fixer);
- if (isEqualityNegation(node)) {
- return getNegationFixes(node, sourceCode, fixer).concat(fixArr);
- }
-
- const toContainFunc = argument(node).value
- ? 'toContain'
- : 'not.toContain';
-
- //.includes function argument
- const [containArg] = node.arguments[0].arguments;
-
- fixArr.push(fixer.replaceText(method(node), toContainFunc));
- fixArr.push(
- fixer.replaceText(
- argument(node),
- sourceCode.getText(containArg)
- )
- );
- return fixArr;
- },
- message: 'Use toContain() instead',
- node: method(node),
- });
- }
- },
- };
- },
-};

rules/prefer-todo.js

@@ -1,78 +0,0 @@
-'use strict';
-
-const {
- getDocsUrl,
- isFunction,
- composeFixers,
- getNodeName,
- isString,
-} = require('./util');
-
-function isOnlyTestTitle(node) {
- return node.arguments.length === 1;
-}
-
-function isFunctionBodyEmpty(node) {
- return node.body.body && !node.body.body.length;
-}
-
-function isTestBodyEmpty(node) {
- const fn = node.arguments[1]; // eslint-disable-line prefer-destructuring
- return fn && isFunction(fn) && isFunctionBodyEmpty(fn);
-}
-
-function addTodo(node, fixer) {
- const testName = getNodeName(node.callee)
- .split('.')
- .shift();
- return fixer.replaceText(node.callee, `${testName}.todo`);
-}
-
-function removeSecondArg({ arguments: [first, second] }, fixer) {
- return fixer.removeRange([first.range[1], second.range[1]]);
-}
-
-function isFirstArgString({ arguments: [firstArg] }) {
- return firstArg && isString(firstArg);
-}
-
-const isTestCase = node =>
- node &&
- node.type === 'CallExpression' &&
- ['it', 'test', 'it.skip', 'test.skip'].includes(getNodeName(node.callee));
-
-function create(context) {
- return {
- CallExpression(node) {
- if (isTestCase(node) && isFirstArgString(node)) {
- const combineFixers = composeFixers(node);
-
- if (isTestBodyEmpty(node)) {
- context.report({
- message: 'Prefer todo test case over empty test case',
- node,
- fix: combineFixers(removeSecondArg, addTodo),
- });
- }
-
- if (isOnlyTestTitle(node)) {
- context.report({
- message: 'Prefer todo test case over unimplemented test case',
- node,
- fix: combineFixers(addTodo),
- });
- }
- }
- },
- };
-}
-
-module.exports = {
- create,
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
-};

rules/prefer-to-have-length.js

@@ -1,55 +0,0 @@
-'use strict';
-
-const {
- getDocsUrl,
- expectCase,
- expectNotCase,
- expectResolveCase,
- expectRejectCase,
- method,
-} = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- fixable: 'code',
- },
- create(context) {
- return {
- CallExpression(node) {
- if (
- !(
- expectNotCase(node) ||
- expectResolveCase(node) ||
- expectRejectCase(node)
- ) &&
- expectCase(node) &&
- (method(node).name === 'toBe' || method(node).name === 'toEqual') &&
- node.arguments[0].property &&
- node.arguments[0].property.name === 'length'
- ) {
- const propertyDot = context
- .getSourceCode()
- .getFirstTokenBetween(
- node.arguments[0].object,
- node.arguments[0].property,
- token => token.value === '.'
- );
- context.report({
- fix(fixer) {
- return [
- fixer.remove(propertyDot),
- fixer.remove(node.arguments[0].property),
- fixer.replaceText(method(node), 'toHaveLength'),
- ];
- },
- message: 'Use toHaveLength() instead',
- node: method(node),
- });
- }
- },
- };
- },
-};

rules/require-tothrow-message.js

@@ -1,36 +0,0 @@
-'use strict';
-
-const { argument, expectCase, getDocsUrl, method } = require('./util');
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- if (!expectCase(node)) {
- return;
- }
-
- const propertyName = method(node) && method(node).name;
-
- // Look for `toThrow` calls with no arguments.
- if (
- ['toThrow', 'toThrowError'].includes(propertyName) &&
- !argument(node)
- ) {
- context.report({
- message: `Add an error message to {{ propertyName }}()`,
- data: {
- propertyName,
- },
- node: method(node),
- });
- }
- },
- };
- },
-};

rules/__tests__/consistent-test-it.test.js

@@ -1,393 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../consistent-test-it');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- },
-});
-
-ruleTester.run('consistent-test-it with fn=test', rule, {
- valid: [
- {
- code: 'test("foo")',
- options: [{ fn: 'test' }],
- },
- {
- code: 'test.only("foo")',
- options: [{ fn: 'test' }],
- },
- {
- code: 'test.skip("foo")',
- options: [{ fn: 'test' }],
- },
- {
- code: 'xtest("foo")',
- options: [{ fn: 'test' }],
- },
- {
- code: 'describe("suite", () => { test("foo") })',
- options: [{ fn: 'test' }],
- },
- ],
- invalid: [
- {
- code: 'it("foo")',
- options: [{ fn: 'test' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'test("foo")',
- },
- {
- code: 'xit("foo")',
- options: [{ fn: 'test' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'xtest("foo")',
- },
- {
- code: 'fit("foo")',
- options: [{ fn: 'test' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'test.only("foo")',
- },
- {
- code: 'it.skip("foo")',
- options: [{ fn: 'test' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'test.skip("foo")',
- },
- {
- code: 'it.only("foo")',
- options: [{ fn: 'test' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'test.only("foo")',
- },
- {
- code: 'describe("suite", () => { it("foo") })',
- options: [{ fn: 'test' }],
- errors: [
- { message: "Prefer using 'test' instead of 'it' within describe" },
- ],
- output: 'describe("suite", () => { test("foo") })',
- },
- ],
-});
-
-ruleTester.run('consistent-test-it with fn=it', rule, {
- valid: [
- {
- code: 'it("foo")',
- options: [{ fn: 'it' }],
- },
- {
- code: 'fit("foo")',
- options: [{ fn: 'it' }],
- },
- {
- code: 'xit("foo")',
- options: [{ fn: 'it' }],
- },
- {
- code: 'it.only("foo")',
- options: [{ fn: 'it' }],
- },
- {
- code: 'it.skip("foo")',
- options: [{ fn: 'it' }],
- },
- {
- code: 'describe("suite", () => { it("foo") })',
- options: [{ fn: 'it' }],
- },
- ],
- invalid: [
- {
- code: 'test("foo")',
- options: [{ fn: 'it' }],
- errors: [{ message: "Prefer using 'it' instead of 'test'" }],
- output: 'it("foo")',
- },
- {
- code: 'xtest("foo")',
- options: [{ fn: 'it' }],
- errors: [{ message: "Prefer using 'it' instead of 'test'" }],
- output: 'xit("foo")',
- },
- {
- code: 'test.skip("foo")',
- options: [{ fn: 'it' }],
- errors: [{ message: "Prefer using 'it' instead of 'test'" }],
- output: 'it.skip("foo")',
- },
- {
- code: 'test.only("foo")',
- options: [{ fn: 'it' }],
- errors: [{ message: "Prefer using 'it' instead of 'test'" }],
- output: 'it.only("foo")',
- },
- {
- code: 'describe("suite", () => { test("foo") })',
- options: [{ fn: 'it' }],
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { it("foo") })',
- },
- ],
-});
-
-ruleTester.run('consistent-test-it with fn=test and withinDescribe=it ', rule, {
- valid: [
- {
- code: 'test("foo")',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- },
- {
- code: 'test.only("foo")',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- },
- {
- code: 'test.skip("foo")',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- },
- {
- code: 'xtest("foo")',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- },
- {
- code: '[1,2,3].forEach(() => { test("foo") })',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- },
- ],
- invalid: [
- {
- code: 'describe("suite", () => { test("foo") })',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { it("foo") })',
- },
- {
- code: 'describe("suite", () => { test.only("foo") })',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { it.only("foo") })',
- },
- {
- code: 'describe("suite", () => { xtest("foo") })',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { xit("foo") })',
- },
- {
- code: 'describe("suite", () => { test.skip("foo") })',
- options: [{ fn: 'test', withinDescribe: 'it' }],
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { it.skip("foo") })',
- },
- ],
-});
-
-ruleTester.run('consistent-test-it with fn=it and withinDescribe=test ', rule, {
- valid: [
- {
- code: 'it("foo")',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- },
- {
- code: 'it.only("foo")',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- },
- {
- code: 'it.skip("foo")',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- },
- {
- code: 'xit("foo")',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- },
- {
- code: '[1,2,3].forEach(() => { it("foo") })',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- },
- ],
- invalid: [
- {
- code: 'describe("suite", () => { it("foo") })',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- errors: [
- { message: "Prefer using 'test' instead of 'it' within describe" },
- ],
- output: 'describe("suite", () => { test("foo") })',
- },
- {
- code: 'describe("suite", () => { it.only("foo") })',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- errors: [
- { message: "Prefer using 'test' instead of 'it' within describe" },
- ],
- output: 'describe("suite", () => { test.only("foo") })',
- },
- {
- code: 'describe("suite", () => { xit("foo") })',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- errors: [
- { message: "Prefer using 'test' instead of 'it' within describe" },
- ],
- output: 'describe("suite", () => { xtest("foo") })',
- },
- {
- code: 'describe("suite", () => { it.skip("foo") })',
- options: [{ fn: 'it', withinDescribe: 'test' }],
- errors: [
- { message: "Prefer using 'test' instead of 'it' within describe" },
- ],
- output: 'describe("suite", () => { test.skip("foo") })',
- },
- ],
-});
-
-ruleTester.run(
- 'consistent-test-it with fn=test and withinDescribe=test ',
- rule,
- {
- valid: [
- {
- code: 'describe("suite", () => { test("foo") })',
- options: [{ fn: 'test', withinDescribe: 'test' }],
- },
- {
- code: 'test("foo");',
- options: [{ fn: 'test', withinDescribe: 'test' }],
- },
- ],
- invalid: [
- {
- code: 'describe("suite", () => { it("foo") })',
- options: [{ fn: 'test', withinDescribe: 'test' }],
- errors: [
- { message: "Prefer using 'test' instead of 'it' within describe" },
- ],
- output: 'describe("suite", () => { test("foo") })',
- },
- {
- code: 'it("foo")',
- options: [{ fn: 'test', withinDescribe: 'test' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'test("foo")',
- },
- ],
- }
-);
-
-ruleTester.run('consistent-test-it with fn=it and withinDescribe=it ', rule, {
- valid: [
- {
- code: 'describe("suite", () => { it("foo") })',
- options: [{ fn: 'it', withinDescribe: 'it' }],
- },
- {
- code: 'it("foo")',
- options: [{ fn: 'it', withinDescribe: 'it' }],
- },
- ],
- invalid: [
- {
- code: 'describe("suite", () => { test("foo") })',
- options: [{ fn: 'it', withinDescribe: 'it' }],
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { it("foo") })',
- },
- {
- code: 'test("foo")',
- options: [{ fn: 'it', withinDescribe: 'it' }],
- errors: [{ message: "Prefer using 'it' instead of 'test'" }],
- output: 'it("foo")',
- },
- ],
-});
-
-ruleTester.run('consistent-test-it defaults without config object', rule, {
- valid: [
- {
- code: 'test("foo")',
- },
- ],
- invalid: [
- {
- code: 'describe("suite", () => { test("foo") })',
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { it("foo") })',
- },
- ],
-});
-
-ruleTester.run('consistent-test-it with withinDescribe=it', rule, {
- valid: [
- {
- code: 'test("foo")',
- options: [{ withinDescribe: 'it' }],
- },
- {
- code: 'describe("suite", () => { it("foo") })',
- options: [{ withinDescribe: 'it' }],
- },
- ],
- invalid: [
- {
- code: 'it("foo")',
- options: [{ withinDescribe: 'it' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'test("foo")',
- },
- {
- code: 'describe("suite", () => { test("foo") })',
- options: [{ withinDescribe: 'it' }],
- errors: [
- { message: "Prefer using 'it' instead of 'test' within describe" },
- ],
- output: 'describe("suite", () => { it("foo") })',
- },
- ],
-});
-
-ruleTester.run('consistent-test-it with withinDescribe=test', rule, {
- valid: [
- {
- code: 'test("foo")',
- options: [{ withinDescribe: 'test' }],
- },
- {
- code: 'describe("suite", () => { test("foo") })',
- options: [{ withinDescribe: 'test' }],
- },
- ],
- invalid: [
- {
- code: 'it("foo")',
- options: [{ withinDescribe: 'test' }],
- errors: [{ message: "Prefer using 'test' instead of 'it'" }],
- output: 'test("foo")',
- },
- {
- code: 'describe("suite", () => { it("foo") })',
- options: [{ withinDescribe: 'test' }],
- errors: [
- { message: "Prefer using 'test' instead of 'it' within describe" },
- ],
- output: 'describe("suite", () => { test("foo") })',
- },
- ],
-});

rules/__tests__/expect-expect.test.js

@@ -1,85 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../expect-expect');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- },
-});
-
-ruleTester.run('expect-expect', rule, {
- valid: [
- 'it("should pass", () => expect(true).toBeDefined())',
- 'test("should pass", () => expect(true).toBeDefined())',
- 'it("should pass", () => somePromise().then(() => expect(true).toBeDefined()))',
- {
- code:
- 'test("should pass", () => { expect(true).toBeDefined(); foo(true).toBe(true); })',
- options: [{ assertFunctionNames: ['expect', 'foo'] }],
- },
- {
- code: 'it("should return undefined",() => expectSaga(mySaga).returns());',
- options: [{ assertFunctionNames: ['expectSaga'] }],
- },
- {
- code: [
- 'test("verifies the function call", () => {',
- ' td.verify(someFunctionCall())',
- '})',
- ].join('\n'),
- options: [{ assertFunctionNames: ['td.verify'] }],
- },
- ],
-
- invalid: [
- {
- code: 'it("should fail", () => {});',
- errors: [
- {
- message: 'Test has no assertions',
- type: 'CallExpression',
- },
- ],
- },
- {
- code: 'test("should fail", () => {});',
- errors: [
- {
- message: 'Test has no assertions',
- type: 'CallExpression',
- },
- ],
- },
- {
- code: 'it("should fail", () => { somePromise.then(() => {}); });',
- errors: [
- {
- message: 'Test has no assertions',
- type: 'CallExpression',
- },
- ],
- },
- {
- code: 'test("should fail", () => { foo(true).toBe(true); })',
- options: [{ assertFunctionNames: ['expect'] }],
- errors: [
- {
- message: 'Test has no assertions',
- type: 'CallExpression',
- },
- ],
- },
- {
- code: 'it("should also fail",() => expectSaga(mySaga).returns());',
- options: [{ assertFunctionNames: ['expect'] }],
- errors: [
- {
- message: 'Test has no assertions',
- type: 'CallExpression',
- },
- ],
- },
- ],
-});

rules/__tests__/lowercase-name.test.js

@@ -1,210 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../lowercase-name');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- },
-});
-
-ruleTester.run('lowercase-name', rule, {
- valid: [
- 'it()',
- "it(' ', function () {})",
- 'it(" ", function () {})',
- 'it(` `, function () {})',
- "it('foo', function () {})",
- 'it("foo", function () {})',
- 'it(`foo`, function () {})',
- 'it("<Foo/>", function () {})',
- 'it("123 foo", function () {})',
- 'it(42, function () {})',
- 'it(``)',
- 'test()',
- "test('foo', function () {})",
- 'test("foo", function () {})',
- 'test(`foo`, function () {})',
- 'test("<Foo/>", function () {})',
- 'test("123 foo", function () {})',
- 'test("42", function () {})',
- 'test(``)',
- 'describe()',
- "describe('foo', function () {})",
- 'describe("foo", function () {})',
- 'describe(`foo`, function () {})',
- 'describe("<Foo/>", function () {})',
- 'describe("123 foo", function () {})',
- 'describe("42", function () {})',
- 'describe(function () {})',
- 'describe(``)',
- ],
-
- invalid: [
- {
- code: "it('Foo', function () {})",
- output: "it('foo', function () {})",
- errors: [
- {
- message: '`it`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'it("Foo", function () {})',
- output: 'it("foo", function () {})',
- errors: [
- {
- message: '`it`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'it(`Foo`, function () {})',
- output: 'it(`foo`, function () {})',
- errors: [
- {
- message: '`it`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: "test('Foo', function () {})",
- output: "test('foo', function () {})",
- errors: [
- {
- message: '`test`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'test("Foo", function () {})',
- output: 'test("foo", function () {})',
- errors: [
- {
- message: '`test`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'test(`Foo`, function () {})',
- output: 'test(`foo`, function () {})',
- errors: [
- {
- message: '`test`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: "describe('Foo', function () {})",
- output: "describe('foo', function () {})",
- errors: [
- {
- message: '`describe`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'describe("Foo", function () {})',
- output: 'describe("foo", function () {})',
- errors: [
- {
- message: '`describe`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'describe(`Foo`, function () {})',
- output: 'describe(`foo`, function () {})',
- errors: [
- {
- message: '`describe`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'describe(`Some longer description`, function () {})',
- output: 'describe(`some longer description`, function () {})',
- errors: [
- {
- message: '`describe`s should begin with lowercase',
- column: 1,
- line: 1,
- },
- ],
- },
- ],
-});
-
-ruleTester.run('lowercase-name with ignore=describe', rule, {
- valid: [
- {
- code: "describe('Foo', function () {})",
- options: [{ ignore: ['describe'] }],
- },
- {
- code: 'describe("Foo", function () {})',
- options: [{ ignore: ['describe'] }],
- },
- {
- code: 'describe(`Foo`, function () {})',
- options: [{ ignore: ['describe'] }],
- },
- ],
- invalid: [],
-});
-
-ruleTester.run('lowercase-name with ignore=test', rule, {
- valid: [
- {
- code: "test('Foo', function () {})",
- options: [{ ignore: ['test'] }],
- },
- {
- code: 'test("Foo", function () {})',
- options: [{ ignore: ['test'] }],
- },
- {
- code: 'test(`Foo`, function () {})',
- options: [{ ignore: ['test'] }],
- },
- ],
- invalid: [],
-});
-
-ruleTester.run('lowercase-name with ignore=it', rule, {
- valid: [
- {
- code: "it('Foo', function () {})",
- options: [{ ignore: ['it'] }],
- },
- {
- code: 'it("Foo", function () {})',
- options: [{ ignore: ['it'] }],
- },
- {
- code: 'it(`Foo`, function () {})',
- options: [{ ignore: ['it'] }],
- },
- ],
- invalid: [],
-});

rules/__tests__/no-alias-methods.test.js

@@ -1,194 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-alias-methods');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('no-alias-methods', rule, {
- valid: [
- 'expect(a).toHaveBeenCalled()',
- 'expect(a).toHaveBeenCalledTimes()',
- 'expect(a).toHaveBeenCalledWith()',
- 'expect(a).toHaveBeenLastCalledWith()',
- 'expect(a).toHaveBeenNthCalledWith()',
- 'expect(a).toHaveReturned()',
- 'expect(a).toHaveReturnedTimes()',
- 'expect(a).toHaveReturnedWith()',
- 'expect(a).toHaveLastReturnedWith()',
- 'expect(a).toHaveNthReturnedWith()',
- 'expect(a).toThrow()',
- 'expect(a).rejects;',
- ],
-
- invalid: [
- {
- code: 'expect(a).toBeCalled()',
- errors: [
- {
- message:
- 'Replace toBeCalled() with its canonical name of toHaveBeenCalled()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveBeenCalled()',
- },
- {
- code: 'expect(a).toBeCalledTimes()',
- errors: [
- {
- message:
- 'Replace toBeCalledTimes() with its canonical name of toHaveBeenCalledTimes()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveBeenCalledTimes()',
- },
- {
- code: 'expect(a).toBeCalledWith()',
- errors: [
- {
- message:
- 'Replace toBeCalledWith() with its canonical name of toHaveBeenCalledWith()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveBeenCalledWith()',
- },
- {
- code: 'expect(a).lastCalledWith()',
- errors: [
- {
- message:
- 'Replace lastCalledWith() with its canonical name of toHaveBeenLastCalledWith()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveBeenLastCalledWith()',
- },
- {
- code: 'expect(a).nthCalledWith()',
- errors: [
- {
- message:
- 'Replace nthCalledWith() with its canonical name of toHaveBeenNthCalledWith()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveBeenNthCalledWith()',
- },
- {
- code: 'expect(a).toReturn()',
- errors: [
- {
- message:
- 'Replace toReturn() with its canonical name of toHaveReturned()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveReturned()',
- },
- {
- code: 'expect(a).toReturnTimes()',
- errors: [
- {
- message:
- 'Replace toReturnTimes() with its canonical name of toHaveReturnedTimes()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveReturnedTimes()',
- },
- {
- code: 'expect(a).toReturnWith()',
- errors: [
- {
- message:
- 'Replace toReturnWith() with its canonical name of toHaveReturnedWith()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveReturnedWith()',
- },
- {
- code: 'expect(a).lastReturnedWith()',
- errors: [
- {
- message:
- 'Replace lastReturnedWith() with its canonical name of toHaveLastReturnedWith()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveLastReturnedWith()',
- },
- {
- code: 'expect(a).nthReturnedWith()',
- errors: [
- {
- message:
- 'Replace nthReturnedWith() with its canonical name of toHaveNthReturnedWith()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toHaveNthReturnedWith()',
- },
- {
- code: 'expect(a).toThrowError()',
- errors: [
- {
- message:
- 'Replace toThrowError() with its canonical name of toThrow()',
- column: 11,
- line: 1,
- },
- ],
- output: 'expect(a).toThrow()',
- },
- {
- code: 'expect(a).resolves.toThrowError()',
- errors: [
- {
- message:
- 'Replace toThrowError() with its canonical name of toThrow()',
- column: 20,
- line: 1,
- },
- ],
- output: 'expect(a).resolves.toThrow()',
- },
- {
- code: 'expect(a).rejects.toThrowError()',
- errors: [
- {
- message:
- 'Replace toThrowError() with its canonical name of toThrow()',
- column: 19,
- line: 1,
- },
- ],
- output: 'expect(a).rejects.toThrow()',
- },
- {
- code: 'expect(a).not.toThrowError()',
- errors: [
- {
- message:
- 'Replace toThrowError() with its canonical name of toThrow()',
- column: 15,
- line: 1,
- },
- ],
- output: 'expect(a).not.toThrow()',
- },
- ],
-});

rules/__tests__/no-disabled-tests.test.js

@@ -1,135 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-disabled-tests');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- sourceType: 'module',
- },
-});
-
-ruleTester.run('no-disabled-tests', rule, {
- valid: [
- 'describe("foo", function () {})',
- 'it("foo", function () {})',
- 'describe.only("foo", function () {})',
- 'it.only("foo", function () {})',
- 'test("foo", function () {})',
- 'test.only("foo", function () {})',
- 'var appliedSkip = describe.skip; appliedSkip.apply(describe)',
- 'var calledSkip = it.skip; calledSkip.call(it)',
- '({ f: function () {} }).f()',
- '(a || b).f()',
- 'itHappensToStartWithIt()',
- 'testSomething()',
- [
- 'import { pending } from "actions"',
- '',
- 'test("foo", () => {',
- ' expect(pending()).toEqual({})',
- '})',
- ].join('\n'),
- [
- 'const { pending } = require("actions")',
- '',
- 'test("foo", () => {',
- ' expect(pending()).toEqual({})',
- '})',
- ].join('\n'),
- [
- 'test("foo", () => {',
- ' const pending = getPending()',
- ' expect(pending()).toEqual({})',
- '})',
- ].join('\n'),
- [
- 'test("foo", () => {',
- ' expect(pending()).toEqual({})',
- '})',
- '',
- 'function pending() {',
- ' return {}',
- '}',
- ].join('\n'),
- ],
-
- invalid: [
- {
- code: 'describe.skip("foo", function () {})',
- errors: [{ message: 'Skipped test suite', column: 1, line: 1 }],
- },
- {
- code: 'describe["skip"]("foo", function () {})',
- errors: [{ message: 'Skipped test suite', column: 1, line: 1 }],
- },
- {
- code: 'it.skip("foo", function () {})',
- errors: [{ message: 'Skipped test', column: 1, line: 1 }],
- },
- {
- code: 'it["skip"]("foo", function () {})',
- errors: [{ message: 'Skipped test', column: 1, line: 1 }],
- },
- {
- code: 'test.skip("foo", function () {})',
- errors: [{ message: 'Skipped test', column: 1, line: 1 }],
- },
- {
- code: 'test["skip"]("foo", function () {})',
- errors: [{ message: 'Skipped test', column: 1, line: 1 }],
- },
- {
- code: 'xdescribe("foo", function () {})',
- errors: [{ message: 'Disabled test suite', column: 1, line: 1 }],
- },
- {
- code: 'xit("foo", function () {})',
- errors: [{ message: 'Disabled test', column: 1, line: 1 }],
- },
- {
- code: 'xtest("foo", function () {})',
- errors: [{ message: 'Disabled test', column: 1, line: 1 }],
- },
- {
- code: 'it("has title but no callback")',
- errors: [
- {
- message: 'Test is missing function argument',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'test("has title but no callback")',
- errors: [
- {
- message: 'Test is missing function argument',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'it("contains a call to pending", function () { pending() })',
- errors: [
- { message: 'Call to pending() within test', column: 48, line: 1 },
- ],
- },
- {
- code: 'pending();',
- errors: [{ message: 'Call to pending()', column: 1, line: 1 }],
- },
- {
- code: 'describe("contains a call to pending", function () { pending() })',
- errors: [
- {
- message: 'Call to pending() within test suite',
- column: 54,
- line: 1,
- },
- ],
- },
- ],
-});

rules/__tests__/no-empty-title.js

@@ -1,108 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-empty-title');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- sourceType: 'module',
- },
-});
-
-ruleTester.run('no-empty-title', rule, {
- valid: [
- 'someFn("", function () {})',
- 'describe(1, function () {})',
- 'describe("foo", function () {})',
- 'describe("foo", function () { it("bar", function () {}) })',
- 'test("foo", function () {})',
- 'test(`foo`, function () {})',
- 'test(`${foo}`, function () {})',
- "it('foo', function () {})",
- "xdescribe('foo', function () {})",
- "xit('foo', function () {})",
- "xtest('foo', function () {})",
- ],
- invalid: [
- {
- code: 'describe("", function () {})',
- errors: [
- {
- message: rule.errorMessages.describe,
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: ["describe('foo', () => {", "it('', () => {})", '})'].join('\n'),
- errors: [
- {
- message: rule.errorMessages.test,
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: 'it("", function () {})',
- errors: [
- {
- message: rule.errorMessages.test,
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'test("", function () {})',
- errors: [
- {
- message: rule.errorMessages.test,
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'test(``, function () {})',
- errors: [
- {
- message: rule.errorMessages.test,
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: "xdescribe('', () => {})",
- errors: [
- {
- message: rule.errorMessages.describe,
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: "xit('', () => {})",
- errors: [
- {
- message: rule.errorMessages.test,
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: "xtest('', () => {})",
- errors: [
- {
- message: rule.errorMessages.test,
- column: 1,
- line: 1,
- },
- ],
- },
- ],
-});

rules/__tests__/no-focused-tests.test.js

@@ -1,71 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-focused-tests');
-
-const ruleTester = new RuleTester();
-const expectedErrorMessage = 'Unexpected focused test.';
-
-ruleTester.run('no-focused-tests', rule, {
- valid: [
- 'describe()',
- 'it()',
- 'describe.skip()',
- 'it.skip()',
- 'test()',
- 'test.skip()',
- 'var appliedOnly = describe.only; appliedOnly.apply(describe)',
- 'var calledOnly = it.only; calledOnly.call(it)',
- ],
-
- invalid: [
- {
- code: 'describe.only()',
- errors: [{ message: expectedErrorMessage, column: 10, line: 1 }],
- },
- {
- code: 'describe.only.each()',
- errors: [{ message: expectedErrorMessage, column: 10, line: 1 }],
- },
- {
- code: 'describe["only"]()',
- errors: [{ message: expectedErrorMessage, column: 10, line: 1 }],
- },
- {
- code: 'it.only()',
- errors: [{ message: expectedErrorMessage, column: 4, line: 1 }],
- },
- {
- code: 'it.only.each()',
- errors: [{ message: expectedErrorMessage, column: 4, line: 1 }],
- },
- {
- code: 'it["only"]()',
- errors: [{ message: expectedErrorMessage, column: 4, line: 1 }],
- },
- {
- code: 'test.only()',
- errors: [{ message: expectedErrorMessage, column: 6, line: 1 }],
- },
- {
- code: 'test.only.each()',
- errors: [{ message: expectedErrorMessage, column: 6, line: 1 }],
- },
- {
- code: 'test["only"]()',
- errors: [{ message: expectedErrorMessage, column: 6, line: 1 }],
- },
- {
- code: 'fdescribe()',
- errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
- },
- {
- code: 'fit()',
- errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
- },
- {
- code: 'fit.each()',
- errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
- },
- ],
-});

rules/__tests__/no-hooks.test.js

@@ -1,45 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-hooks');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- },
-});
-
-ruleTester.run('no-hooks', rule, {
- valid: [
- 'test("foo")',
- 'describe("foo", () => { it("bar") })',
- 'test("foo", () => { expect(subject.beforeEach()).toBe(true) })',
- {
- code: 'afterEach(() => {}); afterAll(() => {});',
- options: [{ allow: ['afterEach', 'afterAll'] }],
- },
- ],
- invalid: [
- {
- code: 'beforeAll(() => {})',
- errors: [{ message: "Unexpected 'beforeAll' hook" }],
- },
- {
- code: 'beforeEach(() => {})',
- errors: [{ message: "Unexpected 'beforeEach' hook" }],
- },
- {
- code: 'afterAll(() => {})',
- errors: [{ message: "Unexpected 'afterAll' hook" }],
- },
- {
- code: 'afterEach(() => {})',
- errors: [{ message: "Unexpected 'afterEach' hook" }],
- },
- {
- code: 'beforeEach(() => {}); afterEach(() => { jest.resetModules() });',
- options: [{ allow: ['afterEach'] }],
- errors: [{ message: "Unexpected 'beforeEach' hook" }],
- },
- ],
-});

rules/__tests__/no-identical-title.test.js

@@ -1,269 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-identical-title');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('no-identical-title', rule, {
- valid: [
- [
- 'describe("describe", function() {',
- ' it("it", function() {});',
- '});',
- ].join('\n'),
- ['describe();describe();'].join('\n'),
- ['it();it();'].join('\n'),
- [
- 'describe("describe1", function() {',
- ' it("it1", function() {});',
- ' it("it2", function() {});',
- '});',
- ].join('\n'),
- ['it("it1", function() {});', 'it("it2", function() {});'].join('\n'),
- ['it.only("it1", function() {});', 'it("it2", function() {});'].join('\n'),
- ['it.only("it1", function() {});', 'it.only("it2", function() {});'].join(
- '\n'
- ),
- ['describe("title", function() {});', 'it("title", function() {});'].join(
- '\n'
- ),
- [
- 'describe("describe1", function() {',
- ' it("it1", function() {});',
- ' describe("describe2", function() {',
- ' it("it1", function() {});',
- ' });',
- '});',
- ].join('\n'),
- [
- 'describe("describe1", function() {',
- ' describe("describe2", function() {',
- ' it("it1", function() {});',
- ' });',
- ' it("it1", function() {});',
- '});',
- ].join('\n'),
- [
- 'describe("describe1", function() {',
- ' describe("describe2", function() {});',
- '});',
- ].join('\n'),
- [
- 'describe("describe1", function() {',
- ' describe("describe2", function() {});',
- '});',
- 'describe("describe2", function() {});',
- ].join('\n'),
- [
- 'describe("describe1", function() {});',
- 'describe("describe2", function() {});',
- ].join('\n'),
- ['it("it" + n, function() {});', 'it("it" + n, function() {});'].join('\n'),
- {
- code: [
- 'it(`it${n}`, function() {});',
- 'it(`it${n}`, function() {});',
- ].join('\n'),
- env: {
- es6: true,
- },
- },
- {
- code: 'it(`${n}`, function() {});',
- env: {
- es6: true,
- },
- },
- [
- 'describe("title " + foo, function() {',
- ' describe("describe1", function() {});',
- '});',
- 'describe("describe1", function() {});',
- ].join('\n'),
- [
- 'describe("describe1", function() {',
- ' describe("describe2", function() {});',
- ' describe("title " + foo, function() {',
- ' describe("describe2", function() {});',
- ' });',
- '});',
- ].join('\n'),
- {
- code: [
- 'describe("describe", () => {',
- ' it(`testing ${someVar} with the same title`, () => {});',
- ' it(`testing ${someVar} with the same title`, () => {});',
- '});',
- ].join('\n'),
- env: {
- es6: true,
- },
- },
- {
- code: ['it(`it1`, () => {});', 'it(`it2`, () => {});'].join('\n'),
- env: {
- es6: true,
- },
- },
- {
- code: [
- 'describe(`describe1`, () => {});',
- 'describe(`describe2`, () => {});',
- ].join('\n'),
- env: {
- es6: true,
- },
- },
- ],
-
- invalid: [
- {
- code: [
- 'describe("describe1", function() {',
- ' it("it1", function() {});',
- ' it("it1", function() {});',
- '});',
- ].join('\n'),
- errors: [
- {
- message:
- 'Test title is used multiple times in the same describe block.',
- column: 4,
- line: 3,
- },
- ],
- },
- {
- code: ['it("it1", function() {});', 'it("it1", function() {});'].join(
- '\n'
- ),
- errors: [
- {
- message:
- 'Test title is used multiple times in the same describe block.',
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: [
- 'it.only("it1", function() {});',
- 'it("it1", function() {});',
- ].join('\n'),
- errors: [
- {
- message:
- 'Test title is used multiple times in the same describe block.',
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: ['fit("it1", function() {});', 'it("it1", function() {});'].join(
- '\n'
- ),
- errors: [
- {
- message:
- 'Test title is used multiple times in the same describe block.',
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: [
- 'it.only("it1", function() {});',
- 'it.only("it1", function() {});',
- ].join('\n'),
- errors: [
- {
- message:
- 'Test title is used multiple times in the same describe block.',
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: [
- 'describe("describe1", function() {});',
- 'describe("describe1", function() {});',
- ].join('\n'),
- errors: [
- {
- message:
- 'Describe block title is used multiple times in the same describe block.',
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: [
- 'describe("describe1", function() {});',
- 'xdescribe("describe1", function() {});',
- ].join('\n'),
- errors: [
- {
- message:
- 'Describe block title is used multiple times in the same describe block.',
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: [
- 'fdescribe("describe1", function() {});',
- 'describe("describe1", function() {});',
- ].join('\n'),
- errors: [
- {
- message:
- 'Describe block title is used multiple times in the same describe block.',
- column: 1,
- line: 2,
- },
- ],
- },
- {
- code: [
- 'describe("describe1", function() {',
- ' describe("describe2", function() {});',
- '});',
- 'describe("describe1", function() {});',
- ].join('\n'),
- errors: [
- {
- message:
- 'Describe block title is used multiple times in the same describe block.',
- column: 1,
- line: 4,
- },
- ],
- },
- {
- code: [
- 'describe("describe", () => {',
- ' it(`testing backticks with the same title`, () => {});',
- ' it(`testing backticks with the same title`, () => {});',
- '});',
- ].join('\n'),
- env: {
- es6: true,
- },
- errors: [
- {
- message:
- 'Test title is used multiple times in the same describe block.',
- column: 5,
- line: 3,
- },
- ],
- },
- ],
-});

rules/__tests__/no-jasmine-globals.test.js

@@ -1,228 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-jasmine-globals');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('no-jasmine-globals', rule, {
- valid: [
- 'jest.spyOn()',
- 'jest.fn()',
- 'expect.extend()',
- 'expect.any()',
- 'it("foo", function () {})',
- 'test("foo", function () {})',
- 'foo()',
- `require('foo')('bar')`,
- 'function callback(fail) { fail() }',
- 'var spyOn = require("actions"); spyOn("foo")',
- 'function callback(pending) { pending() }',
- ],
- invalid: [
- {
- code: 'spyOn(some, "object")',
- errors: [
- {
- message: 'Illegal usage of global `spyOn`, prefer `jest.spyOn`',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'spyOnProperty(some, "object")',
- errors: [
- {
- message:
- 'Illegal usage of global `spyOnProperty`, prefer `jest.spyOn`',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'fail()',
- errors: [
- {
- message:
- 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'pending()',
- errors: [
- {
- message:
- 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- output: 'jest.setTimeout(5000);',
- },
- {
- code: 'jasmine.addMatchers(matchers)',
- errors: [
- {
- message:
- 'Illegal usage of `jasmine.addMatchers`, prefer `expect.extend`',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.createSpy()',
- errors: [
- {
- message: 'Illegal usage of `jasmine.createSpy`, prefer `jest.fn`',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.any()',
- errors: [
- {
- message: 'Illegal usage of `jasmine.any`, prefer `expect.any`',
- column: 1,
- line: 1,
- },
- ],
- output: 'expect.any()',
- },
- {
- code: 'jasmine.anything()',
- errors: [
- {
- message:
- 'Illegal usage of `jasmine.anything`, prefer `expect.anything`',
- column: 1,
- line: 1,
- },
- ],
- output: 'expect.anything()',
- },
- {
- code: 'jasmine.arrayContaining()',
- errors: [
- {
- message:
- 'Illegal usage of `jasmine.arrayContaining`, prefer `expect.arrayContaining`',
- column: 1,
- line: 1,
- },
- ],
- output: 'expect.arrayContaining()',
- },
- {
- code: 'jasmine.objectContaining()',
- errors: [
- {
- message:
- 'Illegal usage of `jasmine.objectContaining`, prefer `expect.objectContaining`',
- column: 1,
- line: 1,
- },
- ],
- output: 'expect.objectContaining()',
- },
- {
- code: 'jasmine.stringMatching()',
- errors: [
- {
- message:
- 'Illegal usage of `jasmine.stringMatching`, prefer `expect.stringMatching`',
- column: 1,
- line: 1,
- },
- ],
- output: 'expect.stringMatching()',
- },
- {
- code: 'jasmine.getEnv()',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.empty()',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.falsy()',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.truthy()',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.arrayWithExactContents()',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.clock()',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- },
- {
- code: 'jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH = 42',
- errors: [
- {
- message: 'Illegal usage of jasmine global',
- column: 1,
- line: 1,
- },
- ],
- },
- ],
-});

rules/__tests__/no-jest-import.test.js

@@ -1,73 +0,0 @@
-'use strict';
-
-const rule = require('../no-jest-import.js');
-const { RuleTester } = require('eslint');
-const ruleTester = new RuleTester();
-const message = `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`;
-
-ruleTester.run('no-jest-import', rule, {
- valid: [
- {
- code: 'import something from "something"',
- parserOptions: { sourceType: 'module' },
- },
- 'require("somethingElse")',
- 'require()',
- 'entirelyDifferent(fn)',
- ],
- invalid: [
- {
- code: 'require("jest")',
- errors: [
- {
- endColumn: 15,
- column: 9,
- message,
- },
- ],
- },
- {
- code: 'import jest from "jest"',
- parserOptions: { sourceType: 'module' },
- errors: [
- {
- endColumn: 24,
- column: 1,
- message,
- },
- ],
- },
- {
- code: 'var jest = require("jest")',
- errors: [
- {
- endColumn: 26,
- column: 20,
- message,
- },
- ],
- },
- {
- code: 'import {jest as test} from "jest"',
- parserOptions: { sourceType: 'module' },
- errors: [
- {
- endColumn: 34,
- column: 1,
- message,
- },
- ],
- },
- {
- code: 'const jest = require("jest")',
- parserOptions: { sourceType: 'module' },
- errors: [
- {
- endColumn: 28,
- column: 22,
- message,
- },
- ],
- },
- ],
-});

rules/__tests__/no-large-snapshots.test.js

@@ -1,173 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-large-snapshots');
-const noLargeSnapshots = rule.create;
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 2015,
- },
-});
-
-ruleTester.run('no-large-snapshots', rule, {
- valid: [
- {
- filename: 'mock.js',
- code: `expect(something).toMatchInlineSnapshot(\`\n${'line\n'.repeat(
- 2
- )}\`);`,
- },
- {
- filename: 'mock.js',
- code: `expect(something).toThrowErrorMatchingInlineSnapshot(\`\n${'line\n'.repeat(
- 2
- )}\`);`,
- },
- ],
- invalid: [
- {
- filename: 'mock.js',
- code: `expect(something).toMatchInlineSnapshot(\`\n${'line\n'.repeat(
- 50
- )}\`);`,
- errors: [
- {
- message:
- 'Expected Jest snapshot to be smaller than 50 lines but was 51 lines long',
- },
- ],
- },
- {
- filename: 'mock.js',
- code: `expect(something).toThrowErrorMatchingInlineSnapshot(\`\n${'line\n'.repeat(
- 50
- )}\`);`,
- errors: [
- {
- message:
- 'Expected Jest snapshot to be smaller than 50 lines but was 51 lines long',
- },
- ],
- },
- ],
-});
-
-// was not able to use https://eslint.org/docs/developer-guide/nodejs-api#ruletester for these because there is no way to configure RuleTester to run non .js files
-describe('no-large-snapshots', () => {
- it('should return an empty object for non snapshot files', () => {
- const mockContext = {
- getFilename: () => 'mock-component.jsx',
- options: [],
- };
- const result = noLargeSnapshots(mockContext);
-
- expect(result).toEqual({});
- });
-
- it('should return an object with an ExpressionStatement function for snapshot files', () => {
- const mockContext = {
- getFilename: () => 'mock-component.jsx.snap',
- options: [],
- };
-
- const result = noLargeSnapshots(mockContext);
-
- expect(result).toMatchObject({
- ExpressionStatement: expect.any(Function),
- });
- });
-
- describe('ExpressionStatement function', () => {
- it('should report if node has more than 50 lines of code and no sizeThreshold option is passed', () => {
- const mockReport = jest.fn();
- const mockContext = {
- getFilename: () => 'mock-component.jsx.snap',
- options: [],
- report: mockReport,
- };
- const mockNode = {
- loc: {
- start: {
- line: 1,
- },
- end: {
- line: 53,
- },
- },
- };
- noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
-
- expect(mockReport).toHaveBeenCalledTimes(1);
- expect(mockReport.mock.calls[0]).toMatchSnapshot();
- });
-
- it('should report if node has more lines of code than number given in sizeThreshold option', () => {
- const mockReport = jest.fn();
- const mockContext = {
- getFilename: () => 'mock-component.jsx.snap',
- options: [{ maxSize: 70 }],
- report: mockReport,
- };
- const mockNode = {
- loc: {
- start: {
- line: 20,
- },
- end: {
- line: 103,
- },
- },
- };
- noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
-
- expect(mockReport).toHaveBeenCalledTimes(1);
- expect(mockReport.mock.calls[0]).toMatchSnapshot();
- });
-
- it('should report if maxSize is zero', () => {
- const mockReport = jest.fn();
- const mockContext = {
- getFilename: () => 'mock-component.jsx.snap',
- options: [{ maxSize: 0 }],
- report: mockReport,
- };
- const mockNode = {
- loc: {
- start: {
- line: 1,
- },
- end: {
- line: 2,
- },
- },
- };
- noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
-
- expect(mockReport).toHaveBeenCalledTimes(1);
- expect(mockReport.mock.calls[0]).toMatchSnapshot();
- });
-
- it('should not report if node has fewer lines of code than limit', () => {
- const mockReport = jest.fn();
- const mockContext = {
- getFilename: () => 'mock-component.jsx.snap',
- options: [],
- report: mockReport,
- };
- const mockNode = {
- loc: {
- start: {
- line: 1,
- },
- end: {
- line: 18,
- },
- },
- };
- noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
-
- expect(mockReport).not.toHaveBeenCalled();
- });
- });
-});

rules/__tests__/no-mocks-import.test.js

@@ -1,97 +0,0 @@
-'use strict';
-
-const rule = require('../no-mocks-import.js');
-const { RuleTester } = require('eslint');
-const ruleTester = new RuleTester();
-const message = `Mocks should not be manually imported from a __mocks__ directory. Instead use jest.mock and import from the original module path.`;
-
-ruleTester.run('no-mocks-import', rule, {
- valid: [
- {
- code: 'import something from "something"',
- parserOptions: { sourceType: 'module' },
- },
- 'require("somethingElse")',
- 'require("./__mocks__.js")',
- 'require("./__mocks__x")',
- 'require("./__mocks__x/x")',
- 'require("./x__mocks__")',
- 'require("./x__mocks__/x")',
- 'require()',
- 'var path = "./__mocks__.js"; require(path)',
- 'entirelyDifferent(fn)',
- ],
- invalid: [
- {
- code: 'require("./__mocks__")',
- errors: [
- {
- endColumn: 22,
- column: 9,
- message,
- },
- ],
- },
- {
- code: 'require("./__mocks__/")',
- errors: [
- {
- endColumn: 23,
- column: 9,
- message,
- },
- ],
- },
- {
- code: 'require("./__mocks__/index")',
- errors: [
- {
- endColumn: 28,
- column: 9,
- message,
- },
- ],
- },
- {
- code: 'require("__mocks__")',
- errors: [
- {
- endColumn: 20,
- column: 9,
- message,
- },
- ],
- },
- {
- code: 'require("__mocks__/")',
- errors: [
- {
- endColumn: 21,
- column: 9,
- message,
- },
- ],
- },
- {
- code: 'require("__mocks__/index")',
- errors: [
- {
- endColumn: 26,
- column: 9,
- message,
- },
- ],
- },
- {
- code: 'import thing from "./__mocks__/index"',
- parserOptions: { sourceType: 'module' },
- errors: [
- {
- endColumn: 38,
- column: 1,
- message,
- },
- ],
- },
- ],
-});

rules/__tests__/no-test-callback.test.js

@@ -1,127 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-test-callback');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 8,
- },
-});
-
-ruleTester.run('no-test-callback', rule, {
- valid: [
- 'test("something", () => {})',
- 'test("something", async () => {})',
- 'test("something", function() {})',
- 'test("something", async function () {})',
- 'test("something", someArg)',
- ],
- invalid: [
- {
- code: 'test("something", done => {done();})',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 19,
- },
- ],
- output:
- 'test("something", () => {return new Promise(done => {done();})})',
- },
- {
- code: 'test("something", (done) => {done();})',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 20,
- },
- ],
- output:
- 'test("something", () => {return new Promise((done) => {done();})})',
- },
- {
- code: 'test("something", done => done())',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 19,
- },
- ],
- output: 'test("something", () => new Promise(done => done()))',
- },
- {
- code: 'test("something", (done) => done())',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 20,
- },
- ],
- output: 'test("something", () => new Promise((done) => done()))',
- },
- {
- code: 'test("something", function(done) {done();})',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 28,
- },
- ],
- output:
- 'test("something", function() {return new Promise((done) => {done();})})',
- },
- {
- code: 'test("something", function (done) {done();})',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 29,
- },
- ],
- output:
- 'test("something", function () {return new Promise((done) => {done();})})',
- },
- {
- code: 'test("something", async done => {done();})',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 25,
- },
- ],
- output:
- 'test("something", async () => {await new Promise(done => {done();})})',
- },
- {
- code: 'test("something", async done => done())',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 25,
- },
- ],
- output: 'test("something", async () => new Promise(done => done()))',
- },
- {
- code: 'test("something", async function (done) {done();})',
- errors: [
- {
- message: 'Illegal usage of test callback',
- line: 1,
- column: 35,
- },
- ],
- output:
- 'test("something", async function () {await new Promise((done) => {done();})})',
- },
- ],
-});

rules/__tests__/no-test-prefixes.test.js

@@ -1,48 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-test-prefixes');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('no-test-prefixes', rule, {
- valid: [
- 'describe("foo", function () {})',
- 'it("foo", function () {})',
- 'test("foo", function () {})',
- 'describe.only("foo", function () {})',
- 'it.only("foo", function () {})',
- 'test.only("foo", function () {})',
- 'describe.skip("foo", function () {})',
- 'it.skip("foo", function () {})',
- 'test.skip("foo", function () {})',
- 'foo()',
- ],
- invalid: [
- {
- code: 'fdescribe("foo", function () {})',
- errors: [{ message: 'Use "describe.only" instead', column: 1, line: 1 }],
- output: 'describe.only("foo", function () {})',
- },
- {
- code: 'fit("foo", function () {})',
- errors: [{ message: 'Use "it.only" instead', column: 1, line: 1 }],
- output: 'it.only("foo", function () {})',
- },
- {
- code: 'xdescribe("foo", function () {})',
- errors: [{ message: 'Use "describe.skip" instead', column: 1, line: 1 }],
- output: 'describe.skip("foo", function () {})',
- },
- {
- code: 'xit("foo", function () {})',
- errors: [{ message: 'Use "it.skip" instead', column: 1, line: 1 }],
- output: 'it.skip("foo", function () {})',
- },
- {
- code: 'xtest("foo", function () {})',
- errors: [{ message: 'Use "test.skip" instead', column: 1, line: 1 }],
- output: 'test.skip("foo", function () {})',
- },
- ],
-});

rules/__tests__/no-test-return-statement.test.js

@@ -1,55 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-test-return-statement');
-
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
-
-ruleTester.run('no-test-prefixes', rule, {
- valid: [
- 'it("noop", function () {});',
- 'test("noop", () => {});',
- 'test("one", () => expect(1).toBe(1));',
- 'test("empty")',
- `
- test("one", () => {
- expect(1).toBe(1);
- });
- `,
- `
- it("one", function () {
- expect(1).toBe(1);
- });
- `,
- ],
- invalid: [
- {
- code: `
- test("one", () => {
- return expect(1).toBe(1);
- });
- `,
- errors: [
- {
- message: 'Jest tests should not return a value.',
- column: 9,
- line: 3,
- },
- ],
- },
- {
- code: `
- it("one", function () {
- return expect(1).toBe(1);
- });
- `,
- errors: [
- {
- message: 'Jest tests should not return a value.',
- column: 9,
- line: 3,
- },
- ],
- },
- ],
-});

rules/__tests__/no-truthy-falsy.test.js

@@ -1,102 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../no-truthy-falsy');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('no-truthy-falsy', rule, {
- valid: [
- 'expect(true).toBe(true);',
- 'expect(false).toBe(false);',
- 'expect("anything").toBe(true);',
- 'expect("anything").toEqual(false);',
- 'expect("anything").not.toBe(true);',
- 'expect("anything").not.toEqual(true);',
- 'expect(Promise.resolve({})).resolves.toBe(true);',
- 'expect(Promise.reject({})).rejects.toBe(true);',
- ],
-
- invalid: [
- {
- code: 'expect(true).toBeTruthy();',
- errors: [
- {
- message: 'Avoid toBeTruthy',
- column: 14,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(false).not.toBeTruthy();',
- errors: [
- {
- message: 'Avoid toBeTruthy',
- column: 19,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(Promise.resolve({})).resolves.toBeTruthy()',
- errors: [
- {
- message: 'Avoid toBeTruthy',
- column: 38,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(Promise.resolve({})).rejects.toBeTruthy()',
- errors: [
- {
- message: 'Avoid toBeTruthy',
- column: 37,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(false).toBeFalsy();',
- errors: [
- {
- message: 'Avoid toBeFalsy',
- column: 15,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(true).not.toBeFalsy();',
- errors: [
- {
- message: 'Avoid toBeFalsy',
- column: 18,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(Promise.resolve({})).resolves.toBeFalsy()',
- errors: [
- {
- message: 'Avoid toBeFalsy',
- column: 38,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(Promise.resolve({})).rejects.toBeFalsy()',
- errors: [
- {
- message: 'Avoid toBeFalsy',
- column: 37,
- line: 1,
- },
- ],
- },
- ],
-});

rules/__tests__/prefer-called-with.js

@@ -1,44 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-called-with');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('prefer-called-with', rule, {
- valid: [
- 'expect(fn).toBeCalledWith();',
- 'expect(fn).toHaveBeenCalledWith();',
- 'expect(fn).toBeCalledWith(expect.anything());',
- 'expect(fn).toHaveBeenCalledWith(expect.anything());',
- 'expect(fn).not.toBeCalled();',
- 'expect(fn).not.toHaveBeenCalled();',
- 'expect(fn).not.toBeCalledWith();',
- 'expect(fn).not.toHaveBeenCalledWith();',
- 'expect(fn).toBeCalledTimes(0);',
- 'expect(fn).toHaveBeenCalledTimes(0);',
- ],
-
- invalid: [
- {
- code: 'expect(fn).toBeCalled();',
- errors: [
- {
- message: 'Prefer toBeCalledWith(/* expected args */)',
- column: 12,
- line: 1,
- },
- ],
- },
- {
- code: 'expect(fn).toHaveBeenCalled();',
- errors: [
- {
- message: 'Prefer toHaveBeenCalledWith(/* expected args */)',
- column: 12,
- line: 1,
- },
- ],
- },
- ],
-});

rules/__tests__/prefer-expect-assertions.test.js

@@ -1,93 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-expect-assertions');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- },
-});
-
-const expectedMsg =
- 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';
-
-ruleTester.run('prefer-expect-assertions', rule, {
- invalid: [
- {
- code: 'it("it1", () => {})',
- errors: [
- {
- message: expectedMsg,
- },
- ],
- },
- {
- code: 'it("it1", () => { foo()})',
- errors: [
- {
- message: expectedMsg,
- },
- ],
- },
- {
- code:
- 'it("it1", function() {' +
- '\n\t\t\tsomeFunctionToDo();' +
- '\n\t\t\tsomeFunctionToDo2();\n' +
- '\t\t\t})',
- errors: [
- {
- message: expectedMsg,
- },
- ],
- },
- {
- code: 'it("it1", function() {var a = 2;})',
- errors: [
- {
- message: expectedMsg,
- },
- ],
- },
- {
- code: 'it("it1", function() {expect.assertions();})',
- errors: [
- {
- message: expectedMsg,
- },
- ],
- },
- {
- code: 'it("it1", function() {expect.assertions(1,2);})',
- errors: [
- {
- message: expectedMsg,
- },
- ],
- },
- {
- code: 'it("it1", function() {expect.assertions("1");})',
- errors: [
- {
- message: expectedMsg,
- },
- ],
- },
- ],
-
- valid: [
- {
- code: 'test("it1", () => {expect.assertions(0);})',
- },
- 'test("it1", function() {expect.assertions(0);})',
- 'test("it1", function() {expect.hasAssertions();})',
- 'it("it1", function() {expect.assertions(0);})',
- 'it("it1", function() {\n\t\t\texpect.assertions(1);' +
- '\n\t\t\texpect(someValue).toBe(true)\n' +
- '\t\t\t})',
- 'test("it1")',
- 'itHappensToStartWithIt("foo", function() {})',
- 'testSomething("bar", function() {})',
- ],
-});

rules/__tests__/prefer-inline-snapshots.test.js

@@ -1,37 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-inline-snapshots');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('prefer-inline-snapshots', rule, {
- valid: [
- 'expect(something).toMatchInlineSnapshot();',
- 'expect(something).toThrowErrorMatchingInlineSnapshot();',
- ],
- invalid: [
- {
- code: 'expect(something).toMatchSnapshot();',
- errors: [
- {
- message: 'Use toMatchInlineSnapshot() instead',
- column: 19,
- line: 1,
- },
- ],
- output: 'expect(something).toMatchInlineSnapshot();',
- },
- {
- code: 'expect(something).toThrowErrorMatchingSnapshot();',
- errors: [
- {
- message: 'Use toThrowErrorMatchingInlineSnapshot() instead',
- column: 19,
- line: 1,
- },
- ],
- output: 'expect(something).toThrowErrorMatchingInlineSnapshot();',
- },
- ],
-});

rules/__tests__/prefer-spy-on.test.js

@@ -1,109 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-spy-on');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- },
-});
-
-ruleTester.run('prefer-spy-on', rule, {
- valid: [
- 'Date.now = () => 10',
- 'window.fetch = jest.fn',
- 'Date.now = fn()',
- 'obj.mock = jest.something()',
- 'const mock = jest.fn()',
- 'mock = jest.fn()',
- 'const mockObj = { mock: jest.fn() }',
- 'mockObj = { mock: jest.fn() }',
- 'window[`${name}`] = jest[`fn${expression}`]()',
- ],
- invalid: [
- {
- code: 'obj.a = jest.fn(); const test = 10;',
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output: "jest.spyOn(obj, 'a'); const test = 10;",
- },
- {
- code: "Date['now'] = jest['fn']()",
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output: "jest.spyOn(Date, 'now')",
- },
- {
- code: 'window[`${name}`] = jest[`fn`]()',
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output: 'jest.spyOn(window, `${name}`)',
- },
- {
- code: "obj['prop' + 1] = jest['fn']()",
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output: "jest.spyOn(obj, 'prop' + 1)",
- },
- {
- code: 'obj.one.two = jest.fn(); const test = 10;',
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output: "jest.spyOn(obj.one, 'two'); const test = 10;",
- },
- {
- code: 'obj.a = jest.fn(() => 10)',
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output: "jest.spyOn(obj, 'a').mockImplementation(() => 10)",
- },
- {
- code:
- "obj.a.b = jest.fn(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output:
- "jest.spyOn(obj.a, 'b').mockImplementation(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
- },
- {
- code: 'window.fetch = jest.fn(() => ({})).one.two().three().four',
- errors: [
- {
- message: 'Use jest.spyOn() instead.',
- type: 'AssignmentExpression',
- },
- ],
- output:
- "jest.spyOn(window, 'fetch').mockImplementation(() => ({})).one.two().three().four",
- },
- ],
-});

rules/__tests__/prefer-strict-equal.test.js

@@ -1,26 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-strict-equal');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('prefer-strict-equal', rule, {
- valid: [
- 'expect(something).toStrictEqual(somethingElse);',
- "a().toEqual('b')",
- ],
- invalid: [
- {
- code: 'expect(something).toEqual(somethingElse);',
- errors: [
- {
- message: 'Use toStrictEqual() instead',
- column: 19,
- line: 1,
- },
- ],
- output: 'expect(something).toStrictEqual(somethingElse);',
- },
- ],
-});

rules/__tests__/prefer-to-be-null.test.js

@@ -1,70 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-to-be-null');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('prefer-to-be-null', rule, {
- valid: [
- 'expect(null).toBeNull();',
- 'expect(null).toEqual();',
- 'expect(null).not.toBeNull();',
- 'expect(null).not.toEqual();',
- 'expect(null).toBe(undefined);',
- 'expect(null).not.toBe(undefined);',
- 'expect(null).toBe();',
- 'expect(null).toMatchSnapshot();',
- 'expect("a string").toMatchSnapshot(null);',
- 'expect("a string").not.toMatchSnapshot();',
- "expect(something).toEqual('a string');",
- 'expect(null).toBe',
- ],
-
- invalid: [
- {
- code: 'expect(null).toBe(null);',
- errors: [
- {
- message: 'Use toBeNull() instead',
- column: 14,
- line: 1,
- },
- ],
- output: 'expect(null).toBeNull();',
- },
- {
- code: 'expect(null).toEqual(null);',
- errors: [
- {
- message: 'Use toBeNull() instead',
- column: 14,
- line: 1,
- },
- ],
- output: 'expect(null).toBeNull();',
- },
- {
- code: 'expect("a string").not.toBe(null);',
- errors: [
- {
- message: 'Use toBeNull() instead',
- column: 24,
- line: 1,
- },
- ],
- output: 'expect("a string").not.toBeNull();',
- },
- {
- code: 'expect("a string").not.toEqual(null);',
- errors: [
- {
- message: 'Use toBeNull() instead',
- column: 24,
- line: 1,
- },
- ],
- output: 'expect("a string").not.toBeNull();',
- },
- ],
-});

rules/__tests__/prefer-to-be-undefined.test.js

@@ -1,67 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-to-be-undefined');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('prefer-to-be-undefined', rule, {
- valid: [
- 'expect(undefined).toBeUndefined();',
- 'expect(true).not.toBeUndefined();',
- 'expect({}).toEqual({});',
- 'expect(null).toEqual(null);',
- 'expect(something).toBe(somethingElse)',
- 'expect(something).toEqual(somethingElse)',
- 'expect(something).not.toBe(somethingElse)',
- 'expect(something).not.toEqual(somethingElse)',
- 'expect(undefined).toBe',
- ],
-
- invalid: [
- {
- code: 'expect(undefined).toBe(undefined);',
- errors: [
- {
- message: 'Use toBeUndefined() instead',
- column: 19,
- line: 1,
- },
- ],
- output: 'expect(undefined).toBeUndefined();',
- },
- {
- code: 'expect(undefined).toEqual(undefined);',
- errors: [
- {
- message: 'Use toBeUndefined() instead',
- column: 19,
- line: 1,
- },
- ],
- output: 'expect(undefined).toBeUndefined();',
- },
- {
- code: 'expect("a string").not.toBe(undefined);',
- errors: [
- {
- message: 'Use toBeUndefined() instead',
- column: 24,
- line: 1,
- },
- ],
- output: 'expect("a string").not.toBeUndefined();',
- },
- {
- code: 'expect("a string").not.toEqual(undefined);',
- errors: [
- {
- message: 'Use toBeUndefined() instead',
- column: 24,
- line: 1,
- },
- ],
- output: 'expect("a string").not.toBeUndefined();',
- },
- ],
-});

rules/__tests__/prefer-to-contain.test.js

@@ -1,207 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-to-contain');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('prefer-to-contain', rule, {
- valid: [
- 'expect(a).toContain(b);',
- "expect(a.name).toBe('b');",
- 'expect(a).toBe(true);',
- `expect(a).toEqual(b)`,
- `expect(a.test(c)).toEqual(b)`,
- `expect(a.includes(b)).toEqual()`,
- `expect(a.includes(b)).toEqual("test")`,
- `expect(a.includes(b)).toBe("test")`,
- `expect(a.includes()).toEqual()`,
- `expect(a.includes()).toEqual(true)`,
- `expect(a.includes(b,c)).toBe(true)`,
- `expect([{a:1}]).toContain({a:1})`,
- `expect([1].includes(1)).toEqual`,
- `expect([1].includes).toEqual`,
- `expect([1].includes).not`,
- `expect(a.test(b)).resolves.toEqual(true)`,
- `expect(a.test(b)).resolves.not.toEqual(true)`,
- `expect(a).not.toContain(b)`,
- ],
- invalid: [
- {
- code: 'expect(a.includes(b)).toEqual(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).toContain(b);',
- },
- {
- code: 'expect(a.includes(b)).toEqual(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).not.toContain(b);',
- },
- {
- code: 'expect(a.includes(b)).not.toEqual(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).toContain(b);',
- },
- {
- code: 'expect(a.includes(b)).not.toEqual(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).not.toContain(b);',
- },
- {
- code: 'expect(a.includes(b)).toBe(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).toContain(b);',
- },
- {
- code: 'expect(a.includes(b)).toBe(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).not.toContain(b);',
- },
- {
- code: 'expect(a.includes(b)).not.toBe(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).toContain(b);',
- },
- {
- code: 'expect(a.includes(b)).not.toBe(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 23,
- line: 1,
- },
- ],
- output: 'expect(a).not.toContain(b);',
- },
- {
- code: 'expect(a.test(t).includes(b.test(p))).toEqual(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 39,
- line: 1,
- },
- ],
- output: 'expect(a.test(t)).toContain(b.test(p));',
- },
- {
- code: 'expect(a.test(t).includes(b.test(p))).toEqual(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 39,
- line: 1,
- },
- ],
- output: 'expect(a.test(t)).not.toContain(b.test(p));',
- },
- {
- code: 'expect(a.test(t).includes(b.test(p))).not.toEqual(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 39,
- line: 1,
- },
- ],
- output: 'expect(a.test(t)).not.toContain(b.test(p));',
- },
- {
- code: 'expect(a.test(t).includes(b.test(p))).not.toEqual(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 39,
- line: 1,
- },
- ],
- output: 'expect(a.test(t)).toContain(b.test(p));',
- },
- {
- code: 'expect([{a:1}].includes({a:1})).toBe(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 33,
- line: 1,
- },
- ],
- output: 'expect([{a:1}]).toContain({a:1});',
- },
- {
- code: 'expect([{a:1}].includes({a:1})).toBe(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 33,
- line: 1,
- },
- ],
- output: 'expect([{a:1}]).not.toContain({a:1});',
- },
- {
- code: 'expect([{a:1}].includes({a:1})).not.toBe(true);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 33,
- line: 1,
- },
- ],
- output: 'expect([{a:1}]).not.toContain({a:1});',
- },
- {
- code: 'expect([{a:1}].includes({a:1})).not.toBe(false);',
- errors: [
- {
- message: 'Use toContain() instead',
- column: 33,
- line: 1,
- },
- ],
- output: 'expect([{a:1}]).toContain({a:1});',
- },
- ],
-});

rules/__tests__/prefer-todo.test.js

@@ -1,59 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-todo');
-
-const ruleTester = new RuleTester({
- parserOptions: { ecmaVersion: 2015 },
-});
-
-ruleTester.run('prefer-todo', rule, {
- valid: [
- 'test.todo("i need to write this test");',
- 'test(obj)',
- 'fit("foo")',
- 'xit("foo")',
- 'test("stub", () => expect(1).toBe(1));',
- `
- supportsDone && params.length < test.length
- ? done => test(...params, done)
- : () => test(...params);
- `,
- ],
- invalid: [
- {
- code: `test("i need to write this test");`,
- errors: [
- { message: 'Prefer todo test case over unimplemented test case' },
- ],
- output: 'test.todo("i need to write this test");',
- },
- {
- code: 'test(`i need to write this test`);',
- errors: [
- { message: 'Prefer todo test case over unimplemented test case' },
- ],
- output: 'test.todo(`i need to write this test`);',
- },
- {
- code: 'it("foo", function () {})',
- errors: ['Prefer todo test case over empty test case'],
- output: 'it.todo("foo")',
- },
- {
- code: 'it("foo", () => {})',
- errors: ['Prefer todo test case over empty test case'],
- output: 'it.todo("foo")',
- },
- {
- code: `test.skip("i need to write this test", () => {});`,
- errors: ['Prefer todo test case over empty test case'],
- output: 'test.todo("i need to write this test");',
- },
- {
- code: `test.skip("i need to write this test", function() {});`,
- errors: ['Prefer todo test case over empty test case'],
- output: 'test.todo("i need to write this test");',
- },
- ],
-});

rules/__tests__/prefer-to-have-length.test.js

@@ -1,41 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../prefer-to-have-length');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('prefer-to-have-length', rule, {
- valid: [
- 'expect(files).toHaveLength(1);',
- "expect(files.name).toBe('file');",
- 'expect(result).toBe(true);',
- `expect(user.getUserName(5)).resolves.toEqual('Paul')`,
- `expect(user.getUserName(5)).rejects.toEqual('Paul')`,
- ],
-
- invalid: [
- {
- code: 'expect(files.length).toBe(1);',
- errors: [
- {
- message: 'Use toHaveLength() instead',
- column: 22,
- line: 1,
- },
- ],
- output: 'expect(files).toHaveLength(1);',
- },
- {
- code: 'expect(files.length).toEqual(1);',
- errors: [
- {
- message: 'Use toHaveLength() instead',
- column: 22,
- line: 1,
- },
- ],
- output: 'expect(files).toHaveLength(1);',
- },
- ],
-});

rules/__tests__/require-tothrow-message.test.js

@@ -1,52 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../require-tothrow-message');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- },
-});
-
-ruleTester.run('require-tothrow-message', rule, {
- valid: [
- // String
- "expect(() => { throw new Error('a'); }).toThrow('a');",
- "expect(() => { throw new Error('a'); }).toThrowError('a');",
-
- // Template literal
- "const a = 'a'; expect(() => { throw new Error('a'); }).toThrow(`${a}`);",
-
- // Regex
- "expect(() => { throw new Error('a'); }).toThrow(/^a$/);",
-
- // Function
- "expect(() => { throw new Error('a'); })" +
- ".toThrow((() => { return 'a'; })());",
-
- // Allow no message for `not`.
- "expect(() => { throw new Error('a'); }).not.toThrow();",
- ],
-
- invalid: [
- // Empty toThrow
- {
- code: "expect(() => { throw new Error('a'); }).toThrow();",
- errors: [
- { message: 'Add an error message to toThrow()', column: 41, line: 1 },
- ],
- },
- // Empty toThrowError
- {
- code: "expect(() => { throw new Error('a'); }).toThrowError();",
- errors: [
- {
- message: 'Add an error message to toThrowError()',
- column: 41,
- line: 1,
- },
- ],
- },
- ],
-});

rules/__tests__/__snapshots__/no-large-snapshots.test.js.snap

@@ -1,67 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`no-large-snapshots ExpressionStatement function should report if maxSize is zero 1`] = `
-Array [
- Object {
- "data": Object {
- "lineCount": 1,
- "lineLimit": 0,
- },
- "message": "Expected to not encounter a Jest snapshot but was found with {{ lineCount }} lines long",
- "node": Object {
- "loc": Object {
- "end": Object {
- "line": 2,
- },
- "start": Object {
- "line": 1,
- },
- },
- },
- },
-]
-`;
-
-exports[`no-large-snapshots ExpressionStatement function should report if node has more lines of code than number given in sizeThreshold option 1`] = `
-Array [
- Object {
- "data": Object {
- "lineCount": 83,
- "lineLimit": 70,
- },
- "message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
- "node": Object {
- "loc": Object {
- "end": Object {
- "line": 103,
- },
- "start": Object {
- "line": 20,
- },
- },
- },
- },
-]
-`;
-
-exports[`no-large-snapshots ExpressionStatement function should report if node has more than 50 lines of code and no sizeThreshold option is passed 1`] = `
-Array [
- Object {
- "data": Object {
- "lineCount": 52,
- "lineLimit": 50,
- },
- "message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
- "node": Object {
- "loc": Object {
- "end": Object {
- "line": 53,
- },
- "start": Object {
- "line": 1,
- },
- },
- },
- },
-]
-`;

rules/__tests__/valid-describe.test.js

@@ -1,245 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../valid-describe');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 8,
- },
-});
-
-ruleTester.run('valid-describe', rule, {
- valid: [
- 'describe("foo", function() {})',
- 'describe("foo", () => {})',
- 'describe(`foo`, () => {})',
- 'xdescribe("foo", () => {})',
- 'fdescribe("foo", () => {})',
- 'describe.only("foo", () => {})',
- 'describe.skip("foo", () => {})',
- `
- describe('foo', () => {
- it('bar', () => {
- return Promise.resolve(42).then(value => {
- expect(value).toBe(42)
- })
- })
- })
- `,
- `
- describe('foo', () => {
- it('bar', async () => {
- expect(await Promise.resolve(42)).toBe(42)
- })
- })
- `,
- `
- describe('foo', () =>
- test('bar', () => {})
- )
- `,
- ],
- invalid: [
- {
- code: 'describe(() => {})',
- errors: [
- {
- message: 'First argument must be name',
- line: 1,
- column: 10,
- },
- {
- message: 'Describe requires name and callback arguments',
- line: 1,
- column: 10,
- },
- ],
- },
- {
- code: 'describe("foo")',
- errors: [
- {
- message: 'Describe requires name and callback arguments',
- line: 1,
- column: 10,
- },
- ],
- },
- {
- code: 'describe("foo", "foo2")',
- errors: [
- {
- message: 'Second argument must be function',
- line: 1,
- column: 10,
- },
- ],
- },
- {
- code: 'describe()',
- errors: [
- {
- message: 'Describe requires name and callback arguments',
- line: 1,
- column: 1,
- },
- ],
- },
- {
- code: 'describe("foo", async () => {})',
- errors: [{ message: 'No async describe callback', line: 1, column: 17 }],
- },
- {
- code: 'describe("foo", async function () {})',
- errors: [{ message: 'No async describe callback', line: 1, column: 17 }],
- },
- {
- code: 'xdescribe("foo", async function () {})',
- errors: [{ message: 'No async describe callback', line: 1, column: 18 }],
- },
- {
- code: 'fdescribe("foo", async function () {})',
- errors: [{ message: 'No async describe callback', line: 1, column: 18 }],
- },
- {
- code: 'describe.only("foo", async function () {})',
- errors: [{ message: 'No async describe callback', line: 1, column: 22 }],
- },
- {
- code: 'describe.skip("foo", async function () {})',
- errors: [{ message: 'No async describe callback', line: 1, column: 22 }],
- },
- {
- code: `
- describe('sample case', () => {
- it('works', () => {
- expect(true).toEqual(true);
- });
- describe('async', async () => {
- await new Promise(setImmediate);
- it('breaks', () => {
- throw new Error('Fail');
- });
- });
- });`,
- errors: [{ message: 'No async describe callback', line: 6, column: 27 }],
- },
- {
- code: `
- describe('foo', function () {
- return Promise.resolve().then(() => {
- it('breaks', () => {
- throw new Error('Fail')
- })
- })
- })
- `,
- errors: [
- {
- message: 'Unexpected return statement in describe callback',
- line: 3,
- column: 9,
- },
- ],
- },
- {
- code: `
- describe('foo', () => {
- return Promise.resolve().then(() => {
- it('breaks', () => {
- throw new Error('Fail')
- })
- })
- describe('nested', () => {
- return Promise.resolve().then(() => {
- it('breaks', () => {
- throw new Error('Fail')
- })
- })
- })
- })
- `,
- errors: [
- {
- message: 'Unexpected return statement in describe callback',
- line: 3,
- column: 9,
- },
- {
- message: 'Unexpected return statement in describe callback',
- line: 9,
- column: 11,
- },
- ],
- },
- {
- code: `
- describe('foo', async () => {
- await something()
- it('does something')
- describe('nested', () => {
- return Promise.resolve().then(() => {
- it('breaks', () => {
- throw new Error('Fail')
- })
- })
- })
- })
- `,
- errors: [
- {
- message: 'No async describe callback',
- line: 2,
- column: 23,
- },
- {
- message: 'Unexpected return statement in describe callback',
- line: 6,
- column: 11,
- },
- ],
- },
- {
- code: 'describe("foo", done => {})',
- errors: [
- {
- message: 'Unexpected argument(s) in describe callback',
- line: 1,
- column: 17,
- },
- ],
- },
- {
- code: 'describe("foo", function (done) {})',
- errors: [
- {
- message: 'Unexpected argument(s) in describe callback',
- line: 1,
- column: 27,
- },
- ],
- },
- {
- code: 'describe("foo", function (one, two, three) {})',
- errors: [
- {
- message: 'Unexpected argument(s) in describe callback',
- line: 1,
- column: 27,
- },
- ],
- },
- {
- code: 'describe("foo", async function (done) {})',
- errors: [
- { message: 'No async describe callback', line: 1, column: 17 },
- {
- message: 'Unexpected argument(s) in describe callback',
- line: 1,
- column: 33,
- },
- ],
- },
- ],
-});

rules/__tests__/valid-expect-in-promise.test.js

@@ -1,401 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../valid-expect-in-promise');
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 8,
- },
-});
-
-const expectedMsg =
- 'Promise should be returned to test its fulfillment or rejection';
-
-ruleTester.run('valid-expect-in-promise', rule, {
- invalid: [
- {
- code: `
- it('it1', () => {
- somePromise.then(() => {
- expect(someThing).toEqual(true);
- });
- });
- `,
- errors: [
- {
- column: 12,
- endColumn: 15,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('it1', function() {
- getSomeThing().getPromise().then(function() {
- expect(someThing).toEqual(true);
- });
- });
- `,
- errors: [
- {
- column: 13,
- endColumn: 16,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('it1', function() {
- Promise.resolve().then(function() {
- expect(someThing).toEqual(true);
- });
- });
- `,
- errors: [
- {
- column: 13,
- endColumn: 16,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('it1', function() {
- somePromise.catch(function() {
- expect(someThing).toEqual(true)
- })
- }
- )
- `,
- errors: [
- {
- column: 13,
- endColumn: 15,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('it1', function() {
- somePromise.then(function() {
- expect(someThing).toEqual(true)
- })
- }
- )
- `,
- errors: [
- {
- column: 13,
- endColumn: 15,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('it1', function () {
- Promise.resolve().then(/*fulfillment*/ function () {
- expect(someThing).toEqual(true);
- }, /*rejection*/ function () {
- expect(someThing).toEqual(true);
- })
- })
- `,
- errors: [
- {
- column: 11,
- endColumn: 13,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('it1', function () {
- Promise.resolve().then(/*fulfillment*/ function () {
- }, /*rejection*/ function () {
- expect(someThing).toEqual(true)
- })
- });
- `,
- errors: [
- {
- column: 11,
- endColumn: 13,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('test function', () => {
- Builder.getPromiseBuilder().get().build().then((data) => expect(data).toEqual('Hi'));
- });
- `,
- errors: [
- {
- column: 11,
- endColumn: 96,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- it('it1', () => {
- somePromise.then(() => {
- doSomeOperation();
- expect(someThing).toEqual(true);
- })
- });
- `,
- errors: [
- {
- column: 13,
- endColumn: 15,
- message: expectedMsg,
- },
- ],
- },
- {
- code: `
- test('invalid return', () => {
- const promise = something().then(value => {
- const foo = "foo";
- return expect(value).toBe('red');
- });
- });
- `,
- errors: [
- {
- column: 18,
- endColumn: 14,
- message: expectedMsg,
- },
- ],
- },
- ],
-
- valid: [
- `
- it('it1', () => new Promise((done) => {
- test()
- .then(() => {
- expect(someThing).toEqual(true);
- done();
- });
- }));
- `,
- `
- it('it1', () => {
- return somePromise.then(() => {
- expect(someThing).toEqual(true);
- });
- });
- `,
-
- `
- it('it1', function() {
- return somePromise.catch(function() {
- expect(someThing).toEqual(true);
- });
- });
- `,
-
- `
- it('it1', function() {
- return somePromise.then(function() {
- doSomeThingButNotExpect();
- });
- });
- `,
-
- `
- it('it1', function() {
- return getSomeThing().getPromise().then(function() {
- expect(someThing).toEqual(true);
- });
- });
- `,
-
- `
- it('it1', function() {
- return Promise.resolve().then(function() {
- expect(someThing).toEqual(true);
- });
- });
- `,
-
- `
- it('it1', function () {
- return Promise.resolve().then(function () {
- /*fulfillment*/
- expect(someThing).toEqual(true);
- }, function () {
- /*rejection*/
- expect(someThing).toEqual(true);
- });
- });
- `,
-
- `
- it('it1', function () {
- return Promise.resolve().then(function () {
- /*fulfillment*/
- }, function () {
- /*rejection*/
- expect(someThing).toEqual(true);
- });
- });
- `,
-
- `
- it('it1', function () {
- return somePromise.then()
- });
- `,
-
- `
- it('it1', async () => {
- await Promise.resolve().then(function () {
- expect(someThing).toEqual(true)
- });
- });
- `,
-
- `
- it('it1', async () => {
- await somePromise.then(() => {
- expect(someThing).toEqual(true)
- });
- });
- `,
-
- `
- it('it1', async () => {
- await getSomeThing().getPromise().then(function () {
- expect(someThing).toEqual(true)
- });
- });
- `,
-
- `
- it('it1', () => {
- return somePromise.then(() => {
- expect(someThing).toEqual(true);
- })
- .then(() => {
- expect(someThing).toEqual(true);
- })
- });
- `,
-
- `
- it('it1', () => {
- return somePromise.then(() => {
- expect(someThing).toEqual(true);
- })
- .catch(() => {
- expect(someThing).toEqual(false);
- })
- });
- `,
-
- `
- test('later return', () => {
- const promise = something().then(value => {
- expect(value).toBe('red');
- });
-
- return promise;
- });
- `,
-
- `
- it('shorthand arrow', () =>
- something().then(value => {
- expect(() => {
- value();
- }).toThrow();
- }));
- `,
-
- `
- it('promise test', () => {
- const somePromise = getThatPromise();
- somePromise.then((data) => {
- expect(data).toEqual('foo');
- });
- expect(somePromise).toBeDefined();
- return somePromise;
- });
- `,
-
- `
- test('promise test', function () {
- let somePromise = getThatPromise();
- somePromise.then((data) => {
- expect(data).toEqual('foo');
- });
- expect(somePromise).toBeDefined();
- return somePromise;
- });
- `,
-
- `
- it('crawls for files based on patterns', () => {
- const promise = nodeCrawl({}).then(data => {
- expect(childProcess.spawn).lastCalledWith('find');
- });
- return promise;
- });
- `,
-
- `
- it('test function',
- () => {
- return Builder.getPromiseBuilder().get().build()
- .then((data) => {
- expect(data).toEqual('Hi');
- });
- });
- `,
-
- `
- notATestFunction('not a test function',
- () => {
- Builder.getPromiseBuilder().get().build()
- .then((data) => {
- expect(data).toEqual('Hi');
- });
- });
- `,
-
- `
- it("it1", () => somePromise.then(() => {
- expect(someThing).toEqual(true)
- }))
- `,
-
- ` it("it1", () => somePromise.then(() => expect(someThing).toEqual(true)))`,
-
- `
- it('promise test with done', (done) => {
- const promise = getPromise();
- promise.then(() => expect(someThing).toEqual(true));
- });
- `,
-
- `
- it('name of done param does not matter', (nameDoesNotMatter) => {
- const promise = getPromise();
- promise.then(() => expect(someThing).toEqual(true));
- });
- `,
- ],
-});

rules/__tests__/valid-expect.test.js

@@ -1,135 +0,0 @@
-'use strict';
-
-const { RuleTester } = require('eslint');
-const rule = require('../valid-expect');
-
-const ruleTester = new RuleTester();
-
-ruleTester.run('valid-expect', rule, {
- valid: [
- 'expect("something").toEqual("else");',
- 'expect(true).toBeDefined();',
- 'expect([1, 2, 3]).toEqual([1, 2, 3]);',
- 'expect(undefined).not.toBeDefined();',
- 'expect(Promise.resolve(2)).resolves.toBeDefined();',
- 'expect(Promise.reject(2)).rejects.toBeDefined();',
- ],
-
- invalid: [
- {
- code: 'expect().toBe(true);',
- errors: [
- {
- endColumn: 8,
- column: 7,
- message: 'No arguments were passed to expect().',
- },
- ],
- },
- {
- code: 'expect().toEqual("something");',
- errors: [
- {
- endColumn: 8,
- column: 7,
- message: 'No arguments were passed to expect().',
- },
- ],
- },
- {
- code: 'expect("something", "else").toEqual("something");',
- errors: [
- {
- endColumn: 26,
- column: 21,
- message: 'More than one argument was passed to expect().',
- },
- ],
- },
- {
- code: 'expect("something");',
- errors: [
- {
- endColumn: 20,
- column: 1,
- message: 'No assertion was called on expect().',
- },
- ],
- },
- {
- code: 'expect();',
- errors: [
- {
- endColumn: 9,
- column: 1,
- message: 'No assertion was called on expect().',
- },
- {
- endColumn: 8,
- column: 7,
- message: 'No arguments were passed to expect().',
- },
- ],
- },
- {
- code: 'expect(true).toBeDefined;',
- errors: [
- {
- endColumn: 25,
- column: 14,
- message: '"toBeDefined" was not called.',
- },
- ],
- },
- {
- code: 'expect(true).not.toBeDefined;',
- errors: [
- {
- endColumn: 29,
- column: 18,
- message: '"toBeDefined" was not called.',
- },
- ],
- },
- {
- code: 'expect(true).nope.toBeDefined;',
- errors: [
- {
- endColumn: 18,
- column: 14,
- message: '"nope" is not a valid property of expect.',
- },
- ],
- },
- {
- code: 'expect(true).resolves;',
- errors: [
- {
- endColumn: 22,
- column: 14,
- message: '"resolves" needs to call a matcher.',
- },
- ],
- },
- {
- code: 'expect(true).rejects;',
- errors: [
- {
- endColumn: 21,
- column: 14,
- message: '"rejects" needs to call a matcher.',
- },
- ],
- },
- {
- code: 'expect(true).not;',
- errors: [
- {
- endColumn: 17,
- column: 14,
- message: '"not" needs to call a matcher.',
- },
- ],
- },
- ],
-});

rules/util.js

@@ -1,228 +0,0 @@
-'use strict';
-
-const path = require('path');
-const { version } = require('../package.json');
-
-const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
-
-const expectCase = node =>
- node.callee.name === 'expect' &&
- node.arguments.length === 1 &&
- node.parent &&
- node.parent.type === 'MemberExpression' &&
- node.parent.parent;
-
-const expectNotCase = node =>
- expectCase(node) &&
- node.parent.parent.type === 'MemberExpression' &&
- methodName(node) === 'not';
-
-const expectResolveCase = node =>
- expectCase(node) &&
- node.parent.parent.type === 'MemberExpression' &&
- methodName(node) === 'resolve';
-
-const expectRejectCase = node =>
- expectCase(node) &&
- node.parent.parent.type === 'MemberExpression' &&
- methodName(node) === 'reject';
-
-const expectToBeCase = (node, arg) =>
- !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) &&
- expectCase(node) &&
- methodName(node) === 'toBe' &&
- argument(node) &&
- ((argument(node).type === 'Literal' &&
- argument(node).value === null &&
- arg === null) ||
- (argument(node).name === 'undefined' && arg === undefined));
-
-const expectNotToBeCase = (node, arg) =>
- expectNotCase(node) &&
- methodName2(node) === 'toBe' &&
- argument2(node) &&
- ((argument2(node).type === 'Literal' &&
- argument2(node).value === null &&
- arg === null) ||
- (argument2(node).name === 'undefined' && arg === undefined));
-
-const expectToEqualCase = (node, arg) =>
- !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) &&
- expectCase(node) &&
- methodName(node) === 'toEqual' &&
- argument(node) &&
- ((argument(node).type === 'Literal' &&
- argument(node).value === null &&
- arg === null) ||
- (argument(node).name === 'undefined' && arg === undefined));
-
-const expectNotToEqualCase = (node, arg) =>
- expectNotCase(node) &&
- methodName2(node) === 'toEqual' &&
- argument2(node) &&
- ((argument2(node).type === 'Literal' &&
- argument2(node).value === null &&
- arg === null) ||
- (argument2(node).name === 'undefined' && arg === undefined));
-
-const method = node => node.parent.property;
-
-const method2 = node => node.parent.parent.property;
-
-const methodName = node => method(node).name;
-
-const methodName2 = node => method2(node).name;
-
-const argument = node =>
- node.parent.parent.arguments && node.parent.parent.arguments[0];
-
-const argument2 = node =>
- node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0];
-
-const describeAliases = Object.assign(Object.create(null), {
- describe: true,
- 'describe.only': true,
- 'describe.skip': true,
- fdescribe: true,
- xdescribe: true,
-});
-
-const testCaseNames = Object.assign(Object.create(null), {
- fit: true,
- it: true,
- 'it.only': true,
- 'it.skip': true,
- test: true,
- 'test.only': true,
- 'test.skip': true,
- xit: true,
- xtest: true,
-});
-
-const getNodeName = node => {
- function joinNames(a, b) {
- return a && b ? `${a}.${b}` : null;
- }
-
- switch (node && node.type) {
- case 'Identifier':
- return node.name;
- case 'Literal':
- return node.value;
- case 'TemplateLiteral':
- if (node.expressions.length === 0) return node.quasis[0].value.cooked;
- break;
- case 'MemberExpression':
- return joinNames(getNodeName(node.object), getNodeName(node.property));
- }
-
- return null;
-};
-
-const isTestCase = node =>
- node &&
- node.type === 'CallExpression' &&
- testCaseNames[getNodeName(node.callee)];
-
-const isDescribe = node =>
- node.type === 'CallExpression' && describeAliases[getNodeName(node.callee)];
-
-const isFunction = node =>
- node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
-
-const isString = node =>
- (node.type === 'Literal' && typeof node.value === 'string') ||
- isTemplateLiteral(node);
-
-const isTemplateLiteral = node => node.type === 'TemplateLiteral';
-
-const hasExpressions = node => node.expressions && node.expressions.length > 0;
-
-const getStringValue = arg =>
- isTemplateLiteral(arg) ? arg.quasis[0].value.raw : arg.value;
-
-/**
- * Generates the URL to documentation for the given rule name. It uses the
- * package version to build the link to a tagged version of the
- * documentation file.
- *
- * @param {string} filename - Name of the eslint rule
- * @returns {string} URL to the documentation for the given rule
- */
-const getDocsUrl = filename => {
- const ruleName = path.basename(filename, '.js');
-
- return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
-};
-
-const collectReferences = scope => {
- const locals = new Set();
- const unresolved = new Set();
-
- let currentScope = scope;
-
- while (currentScope !== null) {
- for (const ref of currentScope.variables) {
- const isReferenceDefined = ref.defs.some(def => {
- return def.type !== 'ImplicitGlobalVariable';
- });
-
- if (isReferenceDefined) {
- locals.add(ref.name);
- }
- }
-
- for (const ref of currentScope.through) {
- unresolved.add(ref.identifier.name);
- }
-
- currentScope = currentScope.upper;
- }
-
- return { locals, unresolved };
-};
-
-const scopeHasLocalReference = (scope, referenceName) => {
- const references = collectReferences(scope);
- return (
- // referenceName was found as a local variable or function declaration.
- references.locals.has(referenceName) ||
- // referenceName was not found as an unresolved reference,
- // meaning it is likely not an implicit global reference.
- !references.unresolved.has(referenceName)
- );
-};
-
-function composeFixers(node) {
- return (...fixers) => {
- return fixerApi => {
- return fixers.reduce((all, fixer) => [...all, fixer(node, fixerApi)], []);
- };
- };
-}
-
-module.exports = {
- method,
- method2,
- argument,
- argument2,
- expectCase,
- expectNotCase,
- expectResolveCase,
- expectRejectCase,
- expectToBeCase,
- expectNotToBeCase,
- expectToEqualCase,
- expectNotToEqualCase,
- getNodeName,
- getStringValue,
- isDescribe,
- isFunction,
- isTemplateLiteral,
- isTestCase,
- isString,
- hasExpressions,
- getDocsUrl,
- scopeHasLocalReference,
- composeFixers,
-};

rules/valid-describe.js

@@ -1,91 +0,0 @@
-'use strict';
-
-const { getDocsUrl, isDescribe, isFunction } = require('./util');
-
-const isAsync = node => node.async;
-
-const isString = node =>
- (node.type === 'Literal' && typeof node.value === 'string') ||
- node.type === 'TemplateLiteral';
-
-const hasParams = node => node.params.length > 0;
-
-const paramsLocation = params => {
- const [first] = params;
- const last = params[params.length - 1];
- return {
- start: {
- line: first.loc.start.line,
- column: first.loc.start.column,
- },
- end: {
- line: last.loc.end.line,
- column: last.loc.end.column,
- },
- };
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- if (isDescribe(node)) {
- if (node.arguments.length === 0) {
- return context.report({
- message: 'Describe requires name and callback arguments',
- loc: node.loc,
- });
- }
-
- const [name] = node.arguments;
- const [, callbackFunction] = node.arguments;
- if (!isString(name)) {
- context.report({
- message: 'First argument must be name',
- loc: paramsLocation(node.arguments),
- });
- }
- if (callbackFunction === undefined) {
- return context.report({
- message: 'Describe requires name and callback arguments',
- loc: paramsLocation(node.arguments),
- });
- }
- if (!isFunction(callbackFunction)) {
- return context.report({
- message: 'Second argument must be function',
- loc: paramsLocation(node.arguments),
- });
- }
- if (isAsync(callbackFunction)) {
- context.report({
- message: 'No async describe callback',
- node: callbackFunction,
- });
- }
- if (hasParams(callbackFunction)) {
- context.report({
- message: 'Unexpected argument(s) in describe callback',
- loc: paramsLocation(callbackFunction.params),
- });
- }
- if (callbackFunction.body.type === 'BlockStatement') {
- callbackFunction.body.body.forEach(node => {
- if (node.type === 'ReturnStatement') {
- context.report({
- message: 'Unexpected return statement in describe callback',
- node,
- });
- }
- });
- }
- }
- },
- };
- },
-};

rules/valid-expect-in-promise.js

@@ -1,166 +0,0 @@
-'use strict';
-
-const { getDocsUrl, isFunction } = require('./util');
-
-const reportMsg =
- 'Promise should be returned to test its fulfillment or rejection';
-
-const isThenOrCatch = node => {
- return (
- node.property &&
- (node.property.name === 'then' || node.property.name === 'catch')
- );
-};
-
-const isExpectCallPresentInFunction = body => {
- if (body.type === 'BlockStatement') {
- return body.body.find(line => {
- if (line.type === 'ExpressionStatement')
- return isExpectCall(line.expression);
- if (line.type === 'ReturnStatement') return isExpectCall(line.argument);
- });
- } else {
- return isExpectCall(body);
- }
-};
-
-const isExpectCall = expression => {
- return (
- expression &&
- expression.type === 'CallExpression' &&
- expression.callee.type === 'MemberExpression' &&
- expression.callee.object.type === 'CallExpression' &&
- expression.callee.object.callee.name === 'expect'
- );
-};
-
-const reportReturnRequired = (context, node) => {
- context.report({
- loc: {
- end: {
- column: node.parent.parent.loc.end.column,
- line: node.parent.parent.loc.end.line,
- },
- start: node.parent.parent.loc.start,
- },
- message: reportMsg,
- node,
- });
-};
-
-const isPromiseReturnedLater = (node, testFunctionBody) => {
- let promiseName;
- if (node.parent.parent.type === 'ExpressionStatement') {
- promiseName = node.parent.parent.expression.callee.object.name;
- } else if (node.parent.parent.type === 'VariableDeclarator') {
- promiseName = node.parent.parent.id.name;
- }
- const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1];
- return (
- lastLineInTestFunc.type === 'ReturnStatement' &&
- lastLineInTestFunc.argument.name === promiseName
- );
-};
-
-const isTestFunc = node => {
- return (
- node.type === 'CallExpression' &&
- (node.callee.name === 'it' || node.callee.name === 'test')
- );
-};
-
-const getFunctionBody = func => {
- if (func.body.type === 'BlockStatement') return func.body.body;
- return func.body; //arrow-short-hand-fn
-};
-
-const getTestFunction = node => {
- let { parent } = node;
- while (parent) {
- if (isFunction(parent) && isTestFunc(parent.parent)) {
- return parent;
- }
- parent = parent.parent;
- }
-};
-
-const isParentThenOrPromiseReturned = (node, testFunctionBody) => {
- return (
- testFunctionBody.type === 'CallExpression' ||
- testFunctionBody.type === 'NewExpression' ||
- node.parent.parent.type === 'ReturnStatement' ||
- isPromiseReturnedLater(node, testFunctionBody) ||
- isThenOrCatch(node.parent.parent)
- );
-};
-
-const verifyExpectWithReturn = (
- promiseCallbacks,
- node,
- context,
- testFunctionBody
-) => {
- promiseCallbacks.some(promiseCallback => {
- if (promiseCallback && isFunction(promiseCallback)) {
- if (
- isExpectCallPresentInFunction(promiseCallback.body) &&
- !isParentThenOrPromiseReturned(node, testFunctionBody)
- ) {
- reportReturnRequired(context, node);
- return true;
- }
- }
- });
-};
-
-const isAwaitExpression = node => {
- return node.parent.parent && node.parent.parent.type === 'AwaitExpression';
-};
-
-const isHavingAsyncCallBackParam = testFunction => {
- try {
- return testFunction.params[0].type === 'Identifier';
- } catch (e) {
- return false;
- }
-};
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- MemberExpression(node) {
- if (
- node.type === 'MemberExpression' &&
- isThenOrCatch(node) &&
- node.parent.type === 'CallExpression' &&
- !isAwaitExpression(node)
- ) {
- const testFunction = getTestFunction(node);
- if (testFunction && !isHavingAsyncCallBackParam(testFunction)) {
- const testFunctionBody = getFunctionBody(testFunction);
- const [
- fulfillmentCallback,
- rejectionCallback,
- ] = node.parent.arguments;
-
- // then block can have two args, fulfillment & rejection
- // then block can have one args, fulfillment
- // catch block can have one args, rejection
- // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- verifyExpectWithReturn(
- [fulfillmentCallback, rejectionCallback],
- node,
- context,
- testFunctionBody
- );
- }
- }
- },
- };
- },
-};

rules/valid-expect.js

@@ -1,130 +0,0 @@
-'use strict';
-
-/*
- * This implementation is ported from from eslint-plugin-jasmine.
- * MIT license, Tom Vincent.
- */
-
-const { getDocsUrl } = require('./util');
-
-const expectProperties = ['not', 'resolves', 'rejects'];
-
-module.exports = {
- meta: {
- docs: {
- url: getDocsUrl(__filename),
- },
- },
- create(context) {
- return {
- CallExpression(node) {
- const calleeName = node.callee.name;
-
- if (calleeName === 'expect') {
- // checking "expect()" arguments
- if (node.arguments.length > 1) {
- const secondArgumentLocStart = node.arguments[1].loc.start;
- const lastArgumentLocEnd =
- node.arguments[node.arguments.length - 1].loc.end;
-
- context.report({
- loc: {
- end: {
- column: lastArgumentLocEnd.column - 1,
- line: lastArgumentLocEnd.line,
- },
- start: secondArgumentLocStart,
- },
- message: 'More than one argument was passed to expect().',
- node,
- });
- } else if (node.arguments.length === 0) {
- const expectLength = calleeName.length;
- context.report({
- loc: {
- end: {
- column: node.loc.start.column + expectLength + 1,
- line: node.loc.start.line,
- },
- start: {
- column: node.loc.start.column + expectLength,
- line: node.loc.start.line,
- },
- },
- message: 'No arguments were passed to expect().',
- node,
- });
- }
-
- // something was called on `expect()`
- if (
- node.parent &&
- node.parent.type === 'MemberExpression' &&
- node.parent.parent
- ) {
- let parentNode = node.parent;
- let parentProperty = parentNode.property;
- let propertyName = parentProperty.name;
- let grandParent = parentNode.parent;
-
- // a property is accessed, get the next node
- if (grandParent.type === 'MemberExpression') {
- // a modifier is used, just get the next one
- if (expectProperties.indexOf(propertyName) > -1) {
- grandParent = grandParent.parent;
- } else {
- // only a few properties are allowed
- context.report({
- // For some reason `endColumn` isn't set in tests if `loc` is
- // not added
- loc: parentProperty.loc,
- message: `"${propertyName}" is not a valid property of expect.`,
- node: parentProperty,
- });
- }
-
- // this next one should be the matcher
- parentNode = parentNode.parent;
- parentProperty = parentNode.property;
- propertyName = parentProperty.name;
- }
-
- // matcher was not called
- if (grandParent.type === 'ExpressionStatement') {
- let message;
- if (expectProperties.indexOf(propertyName) > -1) {
- message = `"${propertyName}" needs to call a matcher.`;
- } else {
- message = `"${propertyName}" was not called.`;
- }
-
- context.report({
- // For some reason `endColumn` isn't set in tests if `loc` is not
- // added
- loc: parentProperty.loc,
- message,
- node: parentProperty,
- });
- }
- }
- }
- },
-
- // nothing called on "expect()"
- 'CallExpression:exit'(node) {
- if (
- node.callee.name === 'expect' &&
- node.parent.type === 'ExpressionStatement'
- ) {
- context.report({
- // For some reason `endColumn` isn't set in tests if `loc` is not
- // added
- loc: node.loc,
- message: 'No assertion was called on expect().',
- node,
- });
- }
- },
- };
- },
-};

src/index.js

@@ -0,0 +1,75 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+
+const rules = fs
+ .readdirSync(path.join(__dirname, 'rules'))
+ .filter(rule => rule !== '__tests__' && rule !== 'util.js')
+ .map(rule => path.basename(rule, '.js'))
+ .reduce(
+ (acc, curr) => Object.assign(acc, { [curr]: require(`./rules/${curr}`) }),
+ {},
+ );
+
+const snapshotProcessor = require('./processors/snapshot-processor');
+
+module.exports = {
+ configs: {
+ recommended: {
+ plugins: ['jest'],
+ env: {
+ 'jest/globals': true,
+ },
+ rules: {
+ 'jest/no-alias-methods': 'warn',
+ 'jest/no-disabled-tests': 'warn',
+ 'jest/no-focused-tests': 'error',
+ 'jest/no-identical-title': 'error',
+ 'jest/no-jest-import': 'error',
+ // 'jest/no-mocks-import': 'error',
+ 'jest/no-jasmine-globals': 'warn',
+ 'jest/no-test-prefixes': 'error',
+ 'jest/valid-describe': 'error',
+ 'jest/valid-expect': 'error',
+ 'jest/valid-expect-in-promise': 'error',
+ },
+ },
+ style: {
+ plugins: ['jest'],
+ rules: {
+ 'jest/prefer-to-be-null': 'error',
+ 'jest/prefer-to-be-undefined': 'error',
+ 'jest/prefer-to-contain': 'error',
+ 'jest/prefer-to-have-length': 'error',
+ },
+ },
+ },
+ environments: {
+ globals: {
+ globals: {
+ afterAll: false,
+ afterEach: false,
+ beforeAll: false,
+ beforeEach: false,
+ describe: false,
+ expect: false,
+ fit: false,
+ it: false,
+ jasmine: false,
+ jest: false,
+ pending: false,
+ pit: false,
+ require: false,
+ test: false,
+ xdescribe: false,
+ xit: false,
+ xtest: false,
+ },
+ },
+ },
+ processors: {
+ '.snap': snapshotProcessor,
+ },
+ rules,
+};

src/processors/snapshot-processor.js

@@ -0,0 +1,15 @@
+'use strict';
+
+// https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
+const preprocess = source => [source];
+
+const postprocess = messages =>
+ messages[0].filter(
+ // snapshot files should only be linted with snapshot specific rules
+ message => message.ruleId === 'jest/no-large-snapshots',
+ );
+
+module.exports = {
+ preprocess,
+ postprocess,
+};

src/processors/__tests__/snapshot-processor.test.js

@@ -0,0 +1,37 @@
+'use strict';
+
+const snapshotProcessor = require('../snapshot-processor');
+
+describe('snapshot-processor', () => {
+ it('exports an object with preprocess and postprocess functions', () => {
+ expect(snapshotProcessor).toMatchObject({
+ preprocess: expect.any(Function),
+ postprocess: expect.any(Function),
+ });
+ });
+
+ describe('preprocess function', () => {
+ it('should pass on untouched source code to source array', () => {
+ const { preprocess } = snapshotProcessor;
+ const sourceCode = "const name = 'johnny bravo';";
+ const result = preprocess(sourceCode);
+
+ expect(result).toEqual([sourceCode]);
+ });
+ });
+
+ describe('postprocess function', () => {
+ it('should only return messages about snapshot specific rules', () => {
+ const { postprocess } = snapshotProcessor;
+ const result = postprocess([
+ [
+ { ruleId: 'no-console' },
+ { ruleId: 'global-require' },
+ { ruleId: 'jest/no-large-snapshots' },
+ ],
+ ]);
+
+ expect(result).toEqual([{ ruleId: 'jest/no-large-snapshots' }]);
+ });
+ });
+});

src/rules/consistent-test-it.js

@@ -0,0 +1,121 @@
+'use strict';
+
+const { getDocsUrl, getNodeName, isTestCase, isDescribe } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ fn: {
+ enum: ['it', 'test'],
+ },
+ withinDescribe: {
+ enum: ['it', 'test'],
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
+ },
+ create(context) {
+ const configObj = context.options[0] || {};
+ const testKeyword = configObj.fn || 'test';
+ const testKeywordWithinDescribe =
+ configObj.withinDescribe || configObj.fn || 'it';
+
+ let describeNestingLevel = 0;
+
+ return {
+ CallExpression(node) {
+ const nodeName = getNodeName(node.callee);
+
+ if (isDescribe(node)) {
+ describeNestingLevel++;
+ }
+
+ if (
+ isTestCase(node) &&
+ describeNestingLevel === 0 &&
+ nodeName.indexOf(testKeyword) === -1
+ ) {
+ const oppositeTestKeyword = getOppositeTestKeyword(testKeyword);
+
+ context.report({
+ message:
+ "Prefer using '{{ testKeyword }}' instead of '{{ oppositeTestKeyword }}'",
+ node: node.callee,
+ data: { testKeyword, oppositeTestKeyword },
+ fix(fixer) {
+ const nodeToReplace =
+ node.callee.type === 'MemberExpression'
+ ? node.callee.object
+ : node.callee;
+
+ const fixedNodeName = getPreferredNodeName(nodeName, testKeyword);
+ return [fixer.replaceText(nodeToReplace, fixedNodeName)];
+ },
+ });
+ }
+
+ if (
+ isTestCase(node) &&
+ describeNestingLevel > 0 &&
+ nodeName.indexOf(testKeywordWithinDescribe) === -1
+ ) {
+ const oppositeTestKeyword = getOppositeTestKeyword(
+ testKeywordWithinDescribe,
+ );
+
+ context.report({
+ message:
+ "Prefer using '{{ testKeywordWithinDescribe }}' instead of '{{ oppositeTestKeyword }}' within describe",
+ node: node.callee,
+ data: { testKeywordWithinDescribe, oppositeTestKeyword },
+ fix(fixer) {
+ const nodeToReplace =
+ node.callee.type === 'MemberExpression'
+ ? node.callee.object
+ : node.callee;
+
+ const fixedNodeName = getPreferredNodeName(
+ nodeName,
+ testKeywordWithinDescribe,
+ );
+ return [fixer.replaceText(nodeToReplace, fixedNodeName)];
+ },
+ });
+ }
+ },
+ 'CallExpression:exit'(node) {
+ if (isDescribe(node)) {
+ describeNestingLevel--;
+ }
+ },
+ };
+ },
+};
+
+function getPreferredNodeName(nodeName, preferredTestKeyword) {
+ switch (nodeName) {
+ case 'fit':
+ return 'test.only';
+ default:
+ return nodeName.startsWith('f') || nodeName.startsWith('x')
+ ? nodeName.charAt(0) + preferredTestKeyword
+ : preferredTestKeyword;
+ }
+}
+
+function getOppositeTestKeyword(test) {
+ if (test === 'test') {
+ return 'it';
+ }
+
+ return 'test';
+}

src/rules/expect-expect.js

@@ -0,0 +1,62 @@
+'use strict';
+
+/*
+ * This implementation is adapted from eslint-plugin-jasmine.
+ * MIT license, Remco Haszing.
+ */
+
+const { getDocsUrl, getNodeName } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ assertFunctionNames: {
+ type: 'array',
+ items: [{ type: 'string' }],
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
+ },
+ create(context) {
+ const unchecked = [];
+ const assertFunctionNames = new Set(
+ context.options[0] && context.options[0].assertFunctionNames
+ ? context.options[0].assertFunctionNames
+ : ['expect'],
+ );
+
+ return {
+ CallExpression(node) {
+ const name = getNodeName(node.callee);
+ if (name === 'it' || name === 'test') {
+ unchecked.push(node);
+ } else if (assertFunctionNames.has(name)) {
+ // Return early in case of nested `it` statements.
+ for (const ancestor of context.getAncestors()) {
+ const index = unchecked.indexOf(ancestor);
+ if (index !== -1) {
+ unchecked.splice(index, 1);
+ break;
+ }
+ }
+ }
+ },
+ 'Program:exit'() {
+ unchecked.forEach(node =>
+ context.report({
+ message: 'Test has no assertions',
+ node,
+ }),
+ );
+ },
+ };
+ },
+};

src/rules/lowercase-name.js

@@ -0,0 +1,97 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+const isItTestOrDescribeFunction = node => {
+ return (
+ node.type === 'CallExpression' &&
+ node.callee &&
+ (node.callee.name === 'it' ||
+ node.callee.name === 'test' ||
+ node.callee.name === 'describe')
+ );
+};
+
+const isItDescription = node => {
+ return (
+ node.arguments &&
+ node.arguments[0] &&
+ (node.arguments[0].type === 'Literal' ||
+ node.arguments[0].type === 'TemplateLiteral')
+ );
+};
+
+const testDescription = node => {
+ const [firstArgument] = node.arguments;
+ const { type } = firstArgument;
+
+ if (type === 'Literal') {
+ return firstArgument.value;
+ }
+
+ // `isItDescription` guarantees this is `type === 'TemplateLiteral'`
+ return firstArgument.quasis[0].value.raw;
+};
+
+const descriptionBeginsWithLowerCase = node => {
+ if (isItTestOrDescribeFunction(node) && isItDescription(node)) {
+ const description = testDescription(node);
+ if (!description[0]) {
+ return false;
+ }
+
+ if (description[0] !== description[0].toLowerCase()) {
+ return node.callee.name;
+ }
+ }
+ return false;
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ const ignore = (context.options[0] && context.options[0].ignore) || [];
+ const ignoredFunctionNames = ignore.reduce((accumulator, value) => {
+ accumulator[value] = true;
+ return accumulator;
+ }, Object.create(null));
+
+ const isIgnoredFunctionName = node =>
+ ignoredFunctionNames[node.callee.name];
+
+ return {
+ CallExpression(node) {
+ const erroneousMethod = descriptionBeginsWithLowerCase(node);
+
+ if (erroneousMethod && !isIgnoredFunctionName(node)) {
+ context.report({
+ message: '`{{ method }}`s should begin with lowercase',
+ data: { method: erroneousMethod },
+ node,
+ fix(fixer) {
+ const [firstArg] = node.arguments;
+ const description = testDescription(node);
+
+ const rangeIgnoringQuotes = [
+ firstArg.range[0] + 1,
+ firstArg.range[1] - 1,
+ ];
+ const newDescription =
+ description.substring(0, 1).toLowerCase() +
+ description.substring(1);
+
+ return [
+ fixer.replaceTextRange(rangeIgnoringQuotes, newDescription),
+ ];
+ },
+ });
+ }
+ },
+ };
+ },
+};

src/rules/no-alias-methods.js

@@ -0,0 +1,69 @@
+'use strict';
+
+const { expectCase, getDocsUrl, method } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ // The Jest methods which have aliases. The canonical name is the first
+ // index of each item.
+ const methodNames = [
+ ['toHaveBeenCalled', 'toBeCalled'],
+ ['toHaveBeenCalledTimes', 'toBeCalledTimes'],
+ ['toHaveBeenCalledWith', 'toBeCalledWith'],
+ ['toHaveBeenLastCalledWith', 'lastCalledWith'],
+ ['toHaveBeenNthCalledWith', 'nthCalledWith'],
+ ['toHaveReturned', 'toReturn'],
+ ['toHaveReturnedTimes', 'toReturnTimes'],
+ ['toHaveReturnedWith', 'toReturnWith'],
+ ['toHaveLastReturnedWith', 'lastReturnedWith'],
+ ['toHaveNthReturnedWith', 'nthReturnedWith'],
+ ['toThrow', 'toThrowError'],
+ ];
+
+ return {
+ CallExpression(node) {
+ if (!expectCase(node)) {
+ return;
+ }
+
+ let targetNode = method(node);
+ if (
+ targetNode.name === 'resolves' ||
+ targetNode.name === 'rejects' ||
+ targetNode.name === 'not'
+ ) {
+ targetNode = method(node.parent);
+ }
+
+ if (!targetNode) {
+ return;
+ }
+
+ // Check if the method used matches any of ours
+ const methodItem = methodNames.find(
+ item => item[1] === targetNode.name,
+ );
+
+ if (methodItem) {
+ context.report({
+ message: `Replace {{ replace }}() with its canonical name of {{ canonical }}()`,
+ data: {
+ replace: methodItem[1],
+ canonical: methodItem[0],
+ },
+ node: targetNode,
+ fix(fixer) {
+ return [fixer.replaceText(targetNode, methodItem[0])];
+ },
+ });
+ }
+ },
+ };
+ },
+};

src/rules/no-commented-out-tests.js

@@ -0,0 +1,39 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+const message = 'Some tests seem to be commented';
+
+function hasTests(node) {
+ return /^\s*(x|f)?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\(/m.test(
+ node.value,
+ );
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ const sourceCode = context.getSourceCode();
+
+ function checkNode(node) {
+ if (!hasTests(node)) return;
+
+ context.report({
+ message,
+ node,
+ });
+ }
+
+ return {
+ Program() {
+ const comments = sourceCode.getAllComments();
+
+ comments.filter(token => token.type !== 'Shebang').forEach(checkNode);
+ },
+ };
+ },
+};

src/rules/no-disabled-tests.js

@@ -0,0 +1,78 @@
+'use strict';
+
+const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ let suiteDepth = 0;
+ let testDepth = 0;
+
+ return {
+ 'CallExpression[callee.name="describe"]'() {
+ suiteDepth++;
+ },
+ 'CallExpression[callee.name=/^(it|test)$/]'() {
+ testDepth++;
+ },
+ 'CallExpression[callee.name=/^(it|test)$/][arguments.length<2]'(node) {
+ context.report({
+ message: 'Test is missing function argument',
+ node,
+ });
+ },
+ CallExpression(node) {
+ const functionName = getNodeName(node.callee);
+
+ switch (functionName) {
+ case 'describe.skip':
+ context.report({ message: 'Skipped test suite', node });
+ break;
+
+ case 'it.skip':
+ case 'test.skip':
+ context.report({ message: 'Skipped test', node });
+ break;
+ }
+ },
+ 'CallExpression[callee.name="pending"]'(node) {
+ if (scopeHasLocalReference(context.getScope(), 'pending')) {
+ return;
+ }
+
+ if (testDepth > 0) {
+ context.report({
+ message: 'Call to pending() within test',
+ node,
+ });
+ } else if (suiteDepth > 0) {
+ context.report({
+ message: 'Call to pending() within test suite',
+ node,
+ });
+ } else {
+ context.report({
+ message: 'Call to pending()',
+ node,
+ });
+ }
+ },
+ 'CallExpression[callee.name="xdescribe"]'(node) {
+ context.report({ message: 'Disabled test suite', node });
+ },
+ 'CallExpression[callee.name=/^xit|xtest$/]'(node) {
+ context.report({ message: 'Disabled test', node });
+ },
+ 'CallExpression[callee.name="describe"]:exit'() {
+ suiteDepth--;
+ },
+ 'CallExpression[callee.name=/^it|test$/]:exit'() {
+ testDepth--;
+ },
+ };
+ },
+};

src/rules/no-empty-title.js

@@ -0,0 +1,54 @@
+'use strict';
+
+const {
+ getDocsUrl,
+ hasExpressions,
+ isDescribe,
+ isTestCase,
+ isTemplateLiteral,
+ isString,
+ getStringValue,
+} = require('./util');
+
+const errorMessages = {
+ describe: 'describe should not have an empty title',
+ test: 'test should not have an empty title',
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ const is = {
+ describe: isDescribe(node),
+ testCase: isTestCase(node),
+ };
+ if (!is.describe && !is.testCase) {
+ return;
+ }
+ const [firstArgument] = node.arguments;
+ if (!isString(firstArgument)) {
+ return;
+ }
+ if (isTemplateLiteral(firstArgument) && hasExpressions(firstArgument)) {
+ return;
+ }
+ if (getStringValue(firstArgument) === '') {
+ const message = is.describe
+ ? errorMessages.describe
+ : errorMessages.test;
+ context.report({
+ message,
+ node,
+ });
+ }
+ },
+ };
+ },
+ errorMessages,
+};

src/rules/no-focused-tests.js

@@ -0,0 +1,73 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+const testFunctions = Object.assign(Object.create(null), {
+ describe: true,
+ it: true,
+ test: true,
+});
+
+const matchesTestFunction = object => object && testFunctions[object.name];
+
+const isCallToFocusedTestFunction = object =>
+ object && object.name[0] === 'f' && testFunctions[object.name.substring(1)];
+
+const isPropertyNamedOnly = property =>
+ property && (property.name === 'only' || property.value === 'only');
+
+const isCallToTestOnlyFunction = callee =>
+ matchesTestFunction(callee.object) && isPropertyNamedOnly(callee.property);
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create: context => ({
+ CallExpression(node) {
+ const { callee } = node;
+
+ if (callee.type === 'MemberExpression') {
+ if (
+ callee.object.type === 'Identifier' &&
+ isCallToFocusedTestFunction(callee.object)
+ ) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee.object,
+ });
+ return;
+ }
+
+ if (
+ callee.object.type === 'MemberExpression' &&
+ isCallToTestOnlyFunction(callee.object)
+ ) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee.object.property,
+ });
+ return;
+ }
+
+ if (isCallToTestOnlyFunction(callee)) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee.property,
+ });
+ return;
+ }
+ }
+
+ if (callee.type === 'Identifier' && isCallToFocusedTestFunction(callee)) {
+ context.report({
+ message: 'Unexpected focused test.',
+ node: callee,
+ });
+ return;
+ }
+ },
+ }),
+};

src/rules/no-hooks.js

@@ -0,0 +1,53 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ allow: {
+ type: 'array',
+ contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'],
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
+ create(context) {
+ const testHookNames = Object.assign(Object.create(null), {
+ beforeAll: true,
+ beforeEach: true,
+ afterAll: true,
+ afterEach: true,
+ });
+
+ const whitelistedHookNames = (
+ context.options[0] || { allow: [] }
+ ).allow.reduce((hashMap, value) => {
+ hashMap[value] = true;
+ return hashMap;
+ }, Object.create(null));
+
+ const isHook = node => testHookNames[node.callee.name];
+ const isWhitelisted = node => whitelistedHookNames[node.callee.name];
+
+ return {
+ CallExpression(node) {
+ if (isHook(node) && !isWhitelisted(node)) {
+ context.report({
+ node,
+ message: "Unexpected '{{ hookName }}' hook",
+ data: { hookName: node.callee.name },
+ });
+ }
+ },
+ };
+ },
+};

src/rules/no-identical-title.js

@@ -0,0 +1,88 @@
+'use strict';
+
+const {
+ getDocsUrl,
+ isDescribe,
+ isTestCase,
+ isString,
+ hasExpressions,
+ getStringValue,
+} = require('./util');
+
+const newDescribeContext = () => ({
+ describeTitles: [],
+ testTitles: [],
+});
+
+const handleTestCaseTitles = (context, titles, node, title) => {
+ if (isTestCase(node)) {
+ if (titles.indexOf(title) !== -1) {
+ context.report({
+ message:
+ 'Test title is used multiple times in the same describe block.',
+ node,
+ });
+ }
+ titles.push(title);
+ }
+};
+
+const handleDescribeBlockTitles = (context, titles, node, title) => {
+ if (!isDescribe(node)) {
+ return;
+ }
+ if (titles.indexOf(title) !== -1) {
+ context.report({
+ message:
+ 'Describe block title is used multiple times in the same describe block.',
+ node,
+ });
+ }
+ titles.push(title);
+};
+
+const isFirstArgValid = arg => {
+ if (!arg || !isString(arg)) {
+ return false;
+ }
+ if (arg.type === 'TemplateLiteral' && hasExpressions(arg)) {
+ return false;
+ }
+ return true;
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ const contexts = [newDescribeContext()];
+ return {
+ CallExpression(node) {
+ const currentLayer = contexts[contexts.length - 1];
+ if (isDescribe(node)) {
+ contexts.push(newDescribeContext());
+ }
+ const [firstArgument] = node.arguments;
+ if (!isFirstArgValid(firstArgument)) {
+ return;
+ }
+ const title = getStringValue(firstArgument);
+ handleTestCaseTitles(context, currentLayer.testTitles, node, title);
+ handleDescribeBlockTitles(
+ context,
+ currentLayer.describeTitles,
+ node,
+ title,
+ );
+ },
+ 'CallExpression:exit'(node) {
+ if (isDescribe(node)) {
+ contexts.pop();
+ }
+ },
+ };
+ },
+};

src/rules/no-jasmine-globals.js

@@ -0,0 +1,149 @@
+'use strict';
+
+const { getDocsUrl, getNodeName, scopeHasLocalReference } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ messages: {
+ illegalGlobal:
+ 'Illegal usage of global `{{ global }}`, prefer `{{ replacement }}`',
+ illegalMethod:
+ 'Illegal usage of `{{ method }}`, prefer `{{ replacement }}`',
+ illegalFail:
+ 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
+ illegalPending:
+ 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
+ illegalJasmine: 'Illegal usage of jasmine global',
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ const calleeName = getNodeName(node.callee);
+
+ if (!calleeName) {
+ return;
+ }
+ if (
+ calleeName === 'spyOn' ||
+ calleeName === 'spyOnProperty' ||
+ calleeName === 'fail' ||
+ calleeName === 'pending'
+ ) {
+ if (scopeHasLocalReference(context.getScope(), calleeName)) {
+ // It's a local variable, not a jasmine global.
+ return;
+ }
+
+ switch (calleeName) {
+ case 'spyOn':
+ case 'spyOnProperty':
+ context.report({
+ node,
+ messageId: 'illegalGlobal',
+ data: { global: calleeName, replacement: 'jest.spyOn' },
+ });
+ break;
+ case 'fail':
+ context.report({
+ node,
+ messageId: 'illegalFail',
+ });
+ break;
+ case 'pending':
+ context.report({
+ node,
+ messageId: 'illegalPending',
+ });
+ break;
+ }
+ return;
+ }
+
+ if (calleeName.startsWith('jasmine.')) {
+ const functionName = calleeName.replace('jasmine.', '');
+
+ if (
+ functionName === 'any' ||
+ functionName === 'anything' ||
+ functionName === 'arrayContaining' ||
+ functionName === 'objectContaining' ||
+ functionName === 'stringMatching'
+ ) {
+ context.report({
+ fix(fixer) {
+ return [fixer.replaceText(node.callee.object, 'expect')];
+ },
+ node,
+ messageId: 'illegalMethod',
+ data: {
+ method: calleeName,
+ replacement: `expect.${functionName}`,
+ },
+ });
+ return;
+ }
+
+ if (functionName === 'addMatchers') {
+ context.report({
+ node,
+ messageId: 'illegalMethod',
+ data: {
+ method: calleeName,
+ replacement: `expect.extend`,
+ },
+ });
+ return;
+ }
+
+ if (functionName === 'createSpy') {
+ context.report({
+ node,
+ messageId: 'illegalMethod',
+ data: {
+ method: calleeName,
+ replacement: 'jest.fn',
+ },
+ });
+ return;
+ }
+
+ context.report({
+ node,
+ messageId: 'illegalJasmine',
+ });
+ }
+ },
+ MemberExpression(node) {
+ if (node.object.name === 'jasmine') {
+ if (node.parent.type === 'AssignmentExpression') {
+ if (node.property.name === 'DEFAULT_TIMEOUT_INTERVAL') {
+ context.report({
+ fix(fixer) {
+ return [
+ fixer.replaceText(
+ node.parent,
+ `jest.setTimeout(${node.parent.right.value})`,
+ ),
+ ];
+ },
+ node,
+ message: 'Illegal usage of jasmine global',
+ });
+ return;
+ }
+
+ context.report({
+ node,
+ message: 'Illegal usage of jasmine global',
+ });
+ }
+ }
+ },
+ };
+ },
+};

src/rules/no-jest-import.js

@@ -0,0 +1,26 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+const message = `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`;
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ 'ImportDeclaration[source.value="jest"]'(node) {
+ context.report({ node, message });
+ },
+ 'CallExpression[callee.name="require"][arguments.0.value="jest"]'(node) {
+ context.report({
+ loc: node.arguments[0].loc,
+ message,
+ });
+ },
+ };
+ },
+};

src/rules/no-large-snapshots.js

@@ -0,0 +1,56 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+const reportOnViolation = (context, node) => {
+ const lineLimit =
+ context.options[0] && Number.isFinite(context.options[0].maxSize)
+ ? context.options[0].maxSize
+ : 50;
+ const startLine = node.loc.start.line;
+ const endLine = node.loc.end.line;
+ const lineCount = endLine - startLine;
+
+ if (lineCount > lineLimit) {
+ context.report({
+ message:
+ lineLimit === 0
+ ? 'Expected to not encounter a Jest snapshot but was found with {{ lineCount }} lines long'
+ : 'Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long',
+ data: { lineLimit, lineCount },
+ node,
+ });
+ }
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ if (context.getFilename().endsWith('.snap')) {
+ return {
+ ExpressionStatement(node) {
+ reportOnViolation(context, node);
+ },
+ };
+ } else if (context.getFilename().endsWith('.js')) {
+ return {
+ CallExpression(node) {
+ const propertyName =
+ node.callee.property && node.callee.property.name;
+ if (
+ propertyName === 'toMatchInlineSnapshot' ||
+ propertyName === 'toThrowErrorMatchingInlineSnapshot'
+ ) {
+ reportOnViolation(context, node);
+ }
+ },
+ };
+ }
+
+ return {};
+ },
+};

src/rules/no-mocks-import.js

@@ -0,0 +1,38 @@
+'use strict';
+
+const { posix } = require('path');
+const { getDocsUrl } = require('./util');
+
+const mocksDirName = '__mocks__';
+const message = `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use jest.mock and import from the original module path.`;
+
+const isMockPath = path => path.split(posix.sep).includes(mocksDirName);
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ ImportDeclaration(node) {
+ if (isMockPath(node.source.value)) {
+ context.report({ node, message });
+ }
+ },
+ 'CallExpression[callee.name="require"]'(node) {
+ if (
+ node.arguments.length &&
+ node.arguments[0].value &&
+ isMockPath(node.arguments[0].value)
+ ) {
+ context.report({
+ loc: node.arguments[0].loc,
+ message,
+ });
+ }
+ },
+ };
+ },
+};

src/rules/no-test-callback.js

@@ -0,0 +1,79 @@
+'use strict';
+
+const { getDocsUrl, isTestCase } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!isTestCase(node) || node.arguments.length !== 2) {
+ return;
+ }
+
+ const [, callback] = node.arguments;
+
+ if (
+ !/^(Arrow)?FunctionExpression$/.test(callback.type) ||
+ callback.params.length !== 1
+ ) {
+ return;
+ }
+
+ const [argument] = callback.params;
+ context.report({
+ node: argument,
+ message: 'Illegal usage of test callback',
+ fix(fixer) {
+ const sourceCode = context.getSourceCode();
+ const { body } = callback;
+ const firstBodyToken = sourceCode.getFirstToken(body);
+ const lastBodyToken = sourceCode.getLastToken(body);
+ const tokenBeforeArgument = sourceCode.getTokenBefore(argument);
+ const tokenAfterArgument = sourceCode.getTokenAfter(argument);
+ const argumentInParens =
+ tokenBeforeArgument.value === '(' &&
+ tokenAfterArgument.value === ')';
+
+ let argumentFix = fixer.replaceText(argument, '()');
+
+ if (argumentInParens) {
+ argumentFix = fixer.remove(argument);
+ }
+
+ let newCallback = argument.name;
+
+ if (argumentInParens) {
+ newCallback = `(${newCallback})`;
+ }
+
+ let beforeReplacement = `new Promise(${newCallback} => `;
+ let afterReplacement = ')';
+ let replaceBefore = true;
+
+ if (body.type === 'BlockStatement') {
+ const keyword = callback.async ? 'await' : 'return';
+
+ beforeReplacement = `${keyword} ${beforeReplacement}{`;
+ afterReplacement += '}';
+ replaceBefore = false;
+ }
+
+ return [
+ argumentFix,
+ replaceBefore
+ ? fixer.insertTextBefore(firstBodyToken, beforeReplacement)
+ : fixer.insertTextAfter(firstBodyToken, beforeReplacement),
+ fixer.insertTextAfter(lastBodyToken, afterReplacement),
+ ];
+ },
+ });
+ },
+ };
+ },
+};

src/rules/no-test-prefixes.js

@@ -0,0 +1,46 @@
+'use strict';
+
+const { getDocsUrl, getNodeName, isTestCase, isDescribe } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ const nodeName = getNodeName(node.callee);
+
+ if (!isDescribe(node) && !isTestCase(node)) return;
+
+ const preferredNodeName = getPreferredNodeName(nodeName);
+
+ if (!preferredNodeName) return;
+
+ context.report({
+ message: 'Use "{{ preferredNodeName }}" instead',
+ node: node.callee,
+ data: { preferredNodeName },
+ fix(fixer) {
+ return [fixer.replaceText(node.callee, preferredNodeName)];
+ },
+ });
+ },
+ };
+ },
+};
+
+function getPreferredNodeName(nodeName) {
+ const firstChar = nodeName.charAt(0);
+
+ if (firstChar === 'f') {
+ return `${nodeName.slice(1)}.only`;
+ }
+
+ if (firstChar === 'x') {
+ return `${nodeName.slice(1)}.skip`;
+ }
+}

src/rules/no-test-return-statement.js

@@ -0,0 +1,41 @@
+'use strict';
+
+const { getDocsUrl, isFunction, isTestCase } = require('./util');
+
+const MESSAGE = 'Jest tests should not return a value.';
+const RETURN_STATEMENT = 'ReturnStatement';
+const BLOCK_STATEMENT = 'BlockStatement';
+
+const getBody = args => {
+ if (
+ args.length > 1 &&
+ isFunction(args[1]) &&
+ args[1].body.type === BLOCK_STATEMENT
+ ) {
+ return args[1].body.body;
+ }
+ return [];
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!isTestCase(node)) return;
+ const body = getBody(node.arguments);
+ const returnStmt = body.find(t => t.type === RETURN_STATEMENT);
+ if (!returnStmt) return;
+
+ context.report({
+ message: MESSAGE,
+ node: returnStmt,
+ });
+ },
+ };
+ },
+};

src/rules/no-truthy-falsy.js

@@ -0,0 +1,44 @@
+'use strict';
+
+const {
+ getDocsUrl,
+ expectCase,
+ expectNotCase,
+ expectResolveCase,
+ expectRejectCase,
+ method,
+} = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (
+ expectCase(node) ||
+ expectNotCase(node) ||
+ expectResolveCase(node) ||
+ expectRejectCase(node)
+ ) {
+ const targetNode =
+ node.parent.parent.type === 'MemberExpression' ? node.parent : node;
+
+ const methodNode = method(targetNode);
+ const { name: methodName } = methodNode;
+
+ if (methodName === 'toBeTruthy' || methodName === 'toBeFalsy') {
+ context.report({
+ data: { methodName },
+ message: 'Avoid {{methodName}}',
+ node: methodNode,
+ });
+ }
+ }
+ },
+ };
+ },
+};

src/rules/prefer-called-with.js

@@ -0,0 +1,29 @@
+'use strict';
+
+const { getDocsUrl, expectCase, expectNotCase, method } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ // Could check resolves/rejects here but not a likely idiom.
+ if (expectCase(node) && !expectNotCase(node)) {
+ const methodNode = method(node);
+ const { name } = methodNode;
+ if (name === 'toBeCalled' || name === 'toHaveBeenCalled') {
+ context.report({
+ data: { name },
+ message: 'Prefer {{name}}With(/* expected args */)',
+ node: methodNode,
+ });
+ }
+ }
+ },
+ };
+ },
+};

src/rules/prefer-expect-assertions.js

@@ -0,0 +1,71 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+const ruleMsg =
+ 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';
+
+const validateArguments = expression => {
+ return (
+ expression.arguments &&
+ expression.arguments.length === 1 &&
+ Number.isInteger(expression.arguments[0].value)
+ );
+};
+
+const isExpectAssertionsOrHasAssertionsCall = expression => {
+ try {
+ const expectAssertionOrHasAssertionCall =
+ expression.type === 'CallExpression' &&
+ expression.callee.type === 'MemberExpression' &&
+ expression.callee.object.name === 'expect' &&
+ (expression.callee.property.name === 'assertions' ||
+ expression.callee.property.name === 'hasAssertions');
+
+ if (expression.callee.property.name === 'assertions') {
+ return expectAssertionOrHasAssertionCall && validateArguments(expression);
+ }
+ return expectAssertionOrHasAssertionCall;
+ } catch (e) {
+ return false;
+ }
+};
+
+const getFunctionFirstLine = functionBody => {
+ return functionBody[0] && functionBody[0].expression;
+};
+
+const isFirstLineExprStmt = functionBody => {
+ return functionBody[0] && functionBody[0].type === 'ExpressionStatement';
+};
+
+const reportMsg = (context, node) => {
+ context.report({
+ message: ruleMsg,
+ node,
+ });
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ 'CallExpression[callee.name=/^(it|test)$/][arguments.1.body.body]'(node) {
+ const testFuncBody = node.arguments[1].body.body;
+
+ if (!isFirstLineExprStmt(testFuncBody)) {
+ reportMsg(context, node);
+ } else {
+ const testFuncFirstLine = getFunctionFirstLine(testFuncBody);
+ if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) {
+ reportMsg(context, node);
+ }
+ }
+ },
+ };
+ },
+};

src/rules/prefer-inline-snapshots.js

@@ -0,0 +1,46 @@
+'use strict';
+
+const { getDocsUrl } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ const propertyName = node.callee.property && node.callee.property.name;
+ if (propertyName === 'toMatchSnapshot') {
+ context.report({
+ fix(fixer) {
+ return [
+ fixer.replaceText(
+ node.callee.property,
+ 'toMatchInlineSnapshot',
+ ),
+ ];
+ },
+ message: 'Use toMatchInlineSnapshot() instead',
+ node: node.callee.property,
+ });
+ } else if (propertyName === 'toThrowErrorMatchingSnapshot') {
+ context.report({
+ fix(fixer) {
+ return [
+ fixer.replaceText(
+ node.callee.property,
+ 'toThrowErrorMatchingInlineSnapshot',
+ ),
+ ];
+ },
+ message: 'Use toThrowErrorMatchingInlineSnapshot() instead',
+ node: node.callee.property,
+ });
+ }
+ },
+ };
+ },
+};

src/rules/prefer-spy-on.js

@@ -0,0 +1,69 @@
+'use strict';
+
+const { getDocsUrl, getNodeName } = require('./util');
+
+const getJestFnCall = node => {
+ if (
+ (node.type !== 'CallExpression' && node.type !== 'MemberExpression') ||
+ (node.callee && node.callee.type !== 'MemberExpression')
+ ) {
+ return null;
+ }
+
+ const obj = node.callee ? node.callee.object : node.object;
+
+ if (obj.type === 'Identifier') {
+ return node.type === 'CallExpression' &&
+ getNodeName(node.callee) === 'jest.fn'
+ ? node
+ : null;
+ }
+
+ return getJestFnCall(obj);
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ AssignmentExpression(node) {
+ if (node.left.type !== 'MemberExpression') return;
+
+ const jestFnCall = getJestFnCall(node.right);
+
+ if (!jestFnCall) return;
+
+ context.report({
+ node,
+ message: 'Use jest.spyOn() instead.',
+ fix(fixer) {
+ const leftPropQuote =
+ node.left.property.type === 'Identifier' ? "'" : '';
+ const [arg] = jestFnCall.arguments;
+ const argSource = arg && context.getSourceCode().getText(arg);
+ const mockImplementation = argSource
+ ? `.mockImplementation(${argSource})`
+ : '';
+
+ return [
+ fixer.insertTextBefore(node.left, `jest.spyOn(`),
+ fixer.replaceTextRange(
+ [node.left.object.range[1], node.left.property.range[0]],
+ `, ${leftPropQuote}`,
+ ),
+ fixer.replaceTextRange(
+ [node.left.property.range[1], jestFnCall.range[1]],
+ `${leftPropQuote})${mockImplementation}`,
+ ),
+ ];
+ },
+ });
+ },
+ };
+ },
+};

src/rules/prefer-strict-equal.js

@@ -0,0 +1,33 @@
+'use strict';
+
+const { expectCase, getDocsUrl, method } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!expectCase(node)) {
+ return;
+ }
+
+ const propertyName = method(node) && method(node).name;
+
+ if (propertyName === 'toEqual') {
+ context.report({
+ fix(fixer) {
+ return [fixer.replaceText(method(node), 'toStrictEqual')];
+ },
+ message: 'Use toStrictEqual() instead',
+ node: method(node),
+ });
+ }
+ },
+ };
+ },
+};

src/rules/prefer-to-be-null.js

@@ -0,0 +1,50 @@
+'use strict';
+
+const {
+ getDocsUrl,
+ argument,
+ argument2,
+ expectToBeCase,
+ expectToEqualCase,
+ expectNotToEqualCase,
+ expectNotToBeCase,
+ method,
+ method2,
+} = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ const is = expectToBeCase(node, null) || expectToEqualCase(node, null);
+ const isNot =
+ expectNotToEqualCase(node, null) || expectNotToBeCase(node, null);
+
+ if (is || isNot) {
+ context.report({
+ fix(fixer) {
+ if (is) {
+ return [
+ fixer.replaceText(method(node), 'toBeNull'),
+ fixer.remove(argument(node)),
+ ];
+ }
+ return [
+ fixer.replaceText(method2(node), 'toBeNull'),
+ fixer.remove(argument2(node)),
+ ];
+ },
+ message: 'Use toBeNull() instead',
+ node: is ? method(node) : method2(node),
+ });
+ }
+ },
+ };
+ },
+};

src/rules/prefer-to-be-undefined.js

@@ -0,0 +1,52 @@
+'use strict';
+
+const {
+ argument,
+ argument2,
+ expectToBeCase,
+ expectNotToBeCase,
+ expectToEqualCase,
+ expectNotToEqualCase,
+ getDocsUrl,
+ method,
+ method2,
+} = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ const is =
+ expectToBeCase(node, undefined) || expectToEqualCase(node, undefined);
+ const isNot =
+ expectNotToEqualCase(node, undefined) ||
+ expectNotToBeCase(node, undefined);
+
+ if (is || isNot) {
+ context.report({
+ fix(fixer) {
+ if (is) {
+ return [
+ fixer.replaceText(method(node), 'toBeUndefined'),
+ fixer.remove(argument(node)),
+ ];
+ }
+ return [
+ fixer.replaceText(method2(node), 'toBeUndefined'),
+ fixer.remove(argument2(node)),
+ ];
+ },
+ message: 'Use toBeUndefined() instead',
+ node: is ? method(node) : method2(node),
+ });
+ }
+ },
+ };
+ },
+};

src/rules/prefer-to-contain.js

@@ -0,0 +1,129 @@
+'use strict';
+
+const {
+ getDocsUrl,
+ expectCase,
+ expectResolveCase,
+ expectRejectCase,
+ method,
+ argument,
+} = require('./util');
+
+const isEqualityCheck = node =>
+ method(node) &&
+ (method(node).name === 'toBe' || method(node).name === 'toEqual');
+
+const isArgumentValid = node =>
+ argument(node).value === true || argument(node).value === false;
+
+const hasOneArgument = node => node.arguments && node.arguments.length === 1;
+
+const isValidEqualityCheck = node =>
+ isEqualityCheck(node) &&
+ hasOneArgument(node.parent.parent) &&
+ isArgumentValid(node);
+
+const isEqualityNegation = node =>
+ method(node).name === 'not' && isValidEqualityCheck(node.parent);
+
+const hasIncludesMethod = node =>
+ node.arguments[0] &&
+ node.arguments[0].callee &&
+ node.arguments[0].callee.property &&
+ node.arguments[0].callee.property.name === 'includes';
+
+const isValidIncludesMethod = node =>
+ hasIncludesMethod(node) && hasOneArgument(node.arguments[0]);
+
+const getNegationFixes = (node, sourceCode, fixer) => {
+ const negationPropertyDot = sourceCode.getFirstTokenBetween(
+ node.parent.object,
+ node.parent.property,
+ token => token.value === '.',
+ );
+ const toContainFunc =
+ isEqualityNegation(node) && argument(node.parent).value
+ ? 'not.toContain'
+ : 'toContain';
+
+ //.includes function argument
+ const [containArg] = node.arguments[0].arguments;
+ return [
+ fixer.remove(negationPropertyDot),
+ fixer.remove(method(node)),
+ fixer.replaceText(method(node.parent), toContainFunc),
+ fixer.replaceText(argument(node.parent), sourceCode.getText(containArg)),
+ ];
+};
+
+const getCommonFixes = (node, sourceCode, fixer) => {
+ const [containArg] = node.arguments[0].arguments;
+ const includesCaller = node.arguments[0].callee;
+
+ const propertyDot = sourceCode.getFirstTokenBetween(
+ includesCaller.object,
+ includesCaller.property,
+ token => token.value === '.',
+ );
+
+ const closingParenthesis = sourceCode.getTokenAfter(containArg);
+ const openParenthesis = sourceCode.getTokenBefore(containArg);
+
+ return [
+ fixer.remove(containArg),
+ fixer.remove(includesCaller.property),
+ fixer.remove(propertyDot),
+ fixer.remove(closingParenthesis),
+ fixer.remove(openParenthesis),
+ ];
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (
+ !(expectResolveCase(node) || expectRejectCase(node)) &&
+ expectCase(node) &&
+ (isEqualityNegation(node) || isValidEqualityCheck(node)) &&
+ isValidIncludesMethod(node)
+ ) {
+ context.report({
+ fix(fixer) {
+ const sourceCode = context.getSourceCode();
+
+ let fixArr = getCommonFixes(node, sourceCode, fixer);
+ if (isEqualityNegation(node)) {
+ return getNegationFixes(node, sourceCode, fixer).concat(fixArr);
+ }
+
+ const toContainFunc = argument(node).value
+ ? 'toContain'
+ : 'not.toContain';
+
+ //.includes function argument
+ const [containArg] = node.arguments[0].arguments;
+
+ fixArr.push(fixer.replaceText(method(node), toContainFunc));
+ fixArr.push(
+ fixer.replaceText(
+ argument(node),
+ sourceCode.getText(containArg),
+ ),
+ );
+ return fixArr;
+ },
+ message: 'Use toContain() instead',
+ node: method(node),
+ });
+ }
+ },
+ };
+ },
+};

src/rules/prefer-todo.js

@@ -0,0 +1,78 @@
+'use strict';
+
+const {
+ getDocsUrl,
+ isFunction,
+ composeFixers,
+ getNodeName,
+ isString,
+} = require('./util');
+
+function isOnlyTestTitle(node) {
+ return node.arguments.length === 1;
+}
+
+function isFunctionBodyEmpty(node) {
+ return node.body.body && !node.body.body.length;
+}
+
+function isTestBodyEmpty(node) {
+ const fn = node.arguments[1]; // eslint-disable-line prefer-destructuring
+ return fn && isFunction(fn) && isFunctionBodyEmpty(fn);
+}
+
+function addTodo(node, fixer) {
+ const testName = getNodeName(node.callee)
+ .split('.')
+ .shift();
+ return fixer.replaceText(node.callee, `${testName}.todo`);
+}
+
+function removeSecondArg({ arguments: [first, second] }, fixer) {
+ return fixer.removeRange([first.range[1], second.range[1]]);
+}
+
+function isFirstArgString({ arguments: [firstArg] }) {
+ return firstArg && isString(firstArg);
+}
+
+const isTestCase = node =>
+ node &&
+ node.type === 'CallExpression' &&
+ ['it', 'test', 'it.skip', 'test.skip'].includes(getNodeName(node.callee));
+
+function create(context) {
+ return {
+ CallExpression(node) {
+ if (isTestCase(node) && isFirstArgString(node)) {
+ const combineFixers = composeFixers(node);
+
+ if (isTestBodyEmpty(node)) {
+ context.report({
+ message: 'Prefer todo test case over empty test case',
+ node,
+ fix: combineFixers(removeSecondArg, addTodo),
+ });
+ }
+
+ if (isOnlyTestTitle(node)) {
+ context.report({
+ message: 'Prefer todo test case over unimplemented test case',
+ node,
+ fix: combineFixers(addTodo),
+ });
+ }
+ }
+ },
+ };
+}
+
+module.exports = {
+ create,
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+};

src/rules/prefer-to-have-length.js

@@ -0,0 +1,55 @@
+'use strict';
+
+const {
+ getDocsUrl,
+ expectCase,
+ expectNotCase,
+ expectResolveCase,
+ expectRejectCase,
+ method,
+} = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ fixable: 'code',
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (
+ !(
+ expectNotCase(node) ||
+ expectResolveCase(node) ||
+ expectRejectCase(node)
+ ) &&
+ expectCase(node) &&
+ (method(node).name === 'toBe' || method(node).name === 'toEqual') &&
+ node.arguments[0].property &&
+ node.arguments[0].property.name === 'length'
+ ) {
+ const propertyDot = context
+ .getSourceCode()
+ .getFirstTokenBetween(
+ node.arguments[0].object,
+ node.arguments[0].property,
+ token => token.value === '.',
+ );
+ context.report({
+ fix(fixer) {
+ return [
+ fixer.remove(propertyDot),
+ fixer.remove(node.arguments[0].property),
+ fixer.replaceText(method(node), 'toHaveLength'),
+ ];
+ },
+ message: 'Use toHaveLength() instead',
+ node: method(node),
+ });
+ }
+ },
+ };
+ },
+};

src/rules/require-tothrow-message.js

@@ -0,0 +1,36 @@
+'use strict';
+
+const { argument, expectCase, getDocsUrl, method } = require('./util');
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!expectCase(node)) {
+ return;
+ }
+
+ const propertyName = method(node) && method(node).name;
+
+ // Look for `toThrow` calls with no arguments.
+ if (
+ ['toThrow', 'toThrowError'].includes(propertyName) &&
+ !argument(node)
+ ) {
+ context.report({
+ message: `Add an error message to {{ propertyName }}()`,
+ data: {
+ propertyName,
+ },
+ node: method(node),
+ });
+ }
+ },
+ };
+ },
+};

src/rules/__tests__/consistent-test-it.test.js

@@ -0,0 +1,393 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../consistent-test-it');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ },
+});
+
+ruleTester.run('consistent-test-it with fn=test', rule, {
+ valid: [
+ {
+ code: 'test("foo")',
+ options: [{ fn: 'test' }],
+ },
+ {
+ code: 'test.only("foo")',
+ options: [{ fn: 'test' }],
+ },
+ {
+ code: 'test.skip("foo")',
+ options: [{ fn: 'test' }],
+ },
+ {
+ code: 'xtest("foo")',
+ options: [{ fn: 'test' }],
+ },
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{ fn: 'test' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'it("foo")',
+ options: [{ fn: 'test' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'test("foo")',
+ },
+ {
+ code: 'xit("foo")',
+ options: [{ fn: 'test' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'xtest("foo")',
+ },
+ {
+ code: 'fit("foo")',
+ options: [{ fn: 'test' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'test.only("foo")',
+ },
+ {
+ code: 'it.skip("foo")',
+ options: [{ fn: 'test' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'test.skip("foo")',
+ },
+ {
+ code: 'it.only("foo")',
+ options: [{ fn: 'test' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'test.only("foo")',
+ },
+ {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{ fn: 'test' }],
+ errors: [
+ { message: "Prefer using 'test' instead of 'it' within describe" },
+ ],
+ output: 'describe("suite", () => { test("foo") })',
+ },
+ ],
+});
+
+ruleTester.run('consistent-test-it with fn=it', rule, {
+ valid: [
+ {
+ code: 'it("foo")',
+ options: [{ fn: 'it' }],
+ },
+ {
+ code: 'fit("foo")',
+ options: [{ fn: 'it' }],
+ },
+ {
+ code: 'xit("foo")',
+ options: [{ fn: 'it' }],
+ },
+ {
+ code: 'it.only("foo")',
+ options: [{ fn: 'it' }],
+ },
+ {
+ code: 'it.skip("foo")',
+ options: [{ fn: 'it' }],
+ },
+ {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{ fn: 'it' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'test("foo")',
+ options: [{ fn: 'it' }],
+ errors: [{ message: "Prefer using 'it' instead of 'test'" }],
+ output: 'it("foo")',
+ },
+ {
+ code: 'xtest("foo")',
+ options: [{ fn: 'it' }],
+ errors: [{ message: "Prefer using 'it' instead of 'test'" }],
+ output: 'xit("foo")',
+ },
+ {
+ code: 'test.skip("foo")',
+ options: [{ fn: 'it' }],
+ errors: [{ message: "Prefer using 'it' instead of 'test'" }],
+ output: 'it.skip("foo")',
+ },
+ {
+ code: 'test.only("foo")',
+ options: [{ fn: 'it' }],
+ errors: [{ message: "Prefer using 'it' instead of 'test'" }],
+ output: 'it.only("foo")',
+ },
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{ fn: 'it' }],
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { it("foo") })',
+ },
+ ],
+});
+
+ruleTester.run('consistent-test-it with fn=test and withinDescribe=it ', rule, {
+ valid: [
+ {
+ code: 'test("foo")',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ },
+ {
+ code: 'test.only("foo")',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ },
+ {
+ code: 'test.skip("foo")',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ },
+ {
+ code: 'xtest("foo")',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ },
+ {
+ code: '[1,2,3].forEach(() => { test("foo") })',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { it("foo") })',
+ },
+ {
+ code: 'describe("suite", () => { test.only("foo") })',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { it.only("foo") })',
+ },
+ {
+ code: 'describe("suite", () => { xtest("foo") })',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { xit("foo") })',
+ },
+ {
+ code: 'describe("suite", () => { test.skip("foo") })',
+ options: [{ fn: 'test', withinDescribe: 'it' }],
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { it.skip("foo") })',
+ },
+ ],
+});
+
+ruleTester.run('consistent-test-it with fn=it and withinDescribe=test ', rule, {
+ valid: [
+ {
+ code: 'it("foo")',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ },
+ {
+ code: 'it.only("foo")',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ },
+ {
+ code: 'it.skip("foo")',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ },
+ {
+ code: 'xit("foo")',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ },
+ {
+ code: '[1,2,3].forEach(() => { it("foo") })',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ errors: [
+ { message: "Prefer using 'test' instead of 'it' within describe" },
+ ],
+ output: 'describe("suite", () => { test("foo") })',
+ },
+ {
+ code: 'describe("suite", () => { it.only("foo") })',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ errors: [
+ { message: "Prefer using 'test' instead of 'it' within describe" },
+ ],
+ output: 'describe("suite", () => { test.only("foo") })',
+ },
+ {
+ code: 'describe("suite", () => { xit("foo") })',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ errors: [
+ { message: "Prefer using 'test' instead of 'it' within describe" },
+ ],
+ output: 'describe("suite", () => { xtest("foo") })',
+ },
+ {
+ code: 'describe("suite", () => { it.skip("foo") })',
+ options: [{ fn: 'it', withinDescribe: 'test' }],
+ errors: [
+ { message: "Prefer using 'test' instead of 'it' within describe" },
+ ],
+ output: 'describe("suite", () => { test.skip("foo") })',
+ },
+ ],
+});
+
+ruleTester.run(
+ 'consistent-test-it with fn=test and withinDescribe=test ',
+ rule,
+ {
+ valid: [
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{ fn: 'test', withinDescribe: 'test' }],
+ },
+ {
+ code: 'test("foo");',
+ options: [{ fn: 'test', withinDescribe: 'test' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{ fn: 'test', withinDescribe: 'test' }],
+ errors: [
+ { message: "Prefer using 'test' instead of 'it' within describe" },
+ ],
+ output: 'describe("suite", () => { test("foo") })',
+ },
+ {
+ code: 'it("foo")',
+ options: [{ fn: 'test', withinDescribe: 'test' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'test("foo")',
+ },
+ ],
+ },
+);
+
+ruleTester.run('consistent-test-it with fn=it and withinDescribe=it ', rule, {
+ valid: [
+ {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{ fn: 'it', withinDescribe: 'it' }],
+ },
+ {
+ code: 'it("foo")',
+ options: [{ fn: 'it', withinDescribe: 'it' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{ fn: 'it', withinDescribe: 'it' }],
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { it("foo") })',
+ },
+ {
+ code: 'test("foo")',
+ options: [{ fn: 'it', withinDescribe: 'it' }],
+ errors: [{ message: "Prefer using 'it' instead of 'test'" }],
+ output: 'it("foo")',
+ },
+ ],
+});
+
+ruleTester.run('consistent-test-it defaults without config object', rule, {
+ valid: [
+ {
+ code: 'test("foo")',
+ },
+ ],
+ invalid: [
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { it("foo") })',
+ },
+ ],
+});
+
+ruleTester.run('consistent-test-it with withinDescribe=it', rule, {
+ valid: [
+ {
+ code: 'test("foo")',
+ options: [{ withinDescribe: 'it' }],
+ },
+ {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{ withinDescribe: 'it' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'it("foo")',
+ options: [{ withinDescribe: 'it' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'test("foo")',
+ },
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{ withinDescribe: 'it' }],
+ errors: [
+ { message: "Prefer using 'it' instead of 'test' within describe" },
+ ],
+ output: 'describe("suite", () => { it("foo") })',
+ },
+ ],
+});
+
+ruleTester.run('consistent-test-it with withinDescribe=test', rule, {
+ valid: [
+ {
+ code: 'test("foo")',
+ options: [{ withinDescribe: 'test' }],
+ },
+ {
+ code: 'describe("suite", () => { test("foo") })',
+ options: [{ withinDescribe: 'test' }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'it("foo")',
+ options: [{ withinDescribe: 'test' }],
+ errors: [{ message: "Prefer using 'test' instead of 'it'" }],
+ output: 'test("foo")',
+ },
+ {
+ code: 'describe("suite", () => { it("foo") })',
+ options: [{ withinDescribe: 'test' }],
+ errors: [
+ { message: "Prefer using 'test' instead of 'it' within describe" },
+ ],
+ output: 'describe("suite", () => { test("foo") })',
+ },
+ ],
+});

src/rules/__tests__/expect-expect.test.js

@@ -0,0 +1,85 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../expect-expect');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ },
+});
+
+ruleTester.run('expect-expect', rule, {
+ valid: [
+ 'it("should pass", () => expect(true).toBeDefined())',
+ 'test("should pass", () => expect(true).toBeDefined())',
+ 'it("should pass", () => somePromise().then(() => expect(true).toBeDefined()))',
+ {
+ code:
+ 'test("should pass", () => { expect(true).toBeDefined(); foo(true).toBe(true); })',
+ options: [{ assertFunctionNames: ['expect', 'foo'] }],
+ },
+ {
+ code: 'it("should return undefined",() => expectSaga(mySaga).returns());',
+ options: [{ assertFunctionNames: ['expectSaga'] }],
+ },
+ {
+ code: [
+ 'test("verifies the function call", () => {',
+ ' td.verify(someFunctionCall())',
+ '})',
+ ].join('\n'),
+ options: [{ assertFunctionNames: ['td.verify'] }],
+ },
+ ],
+
+ invalid: [
+ {
+ code: 'it("should fail", () => {});',
+ errors: [
+ {
+ message: 'Test has no assertions',
+ type: 'CallExpression',
+ },
+ ],
+ },
+ {
+ code: 'test("should fail", () => {});',
+ errors: [
+ {
+ message: 'Test has no assertions',
+ type: 'CallExpression',
+ },
+ ],
+ },
+ {
+ code: 'it("should fail", () => { somePromise.then(() => {}); });',
+ errors: [
+ {
+ message: 'Test has no assertions',
+ type: 'CallExpression',
+ },
+ ],
+ },
+ {
+ code: 'test("should fail", () => { foo(true).toBe(true); })',
+ options: [{ assertFunctionNames: ['expect'] }],
+ errors: [
+ {
+ message: 'Test has no assertions',
+ type: 'CallExpression',
+ },
+ ],
+ },
+ {
+ code: 'it("should also fail",() => expectSaga(mySaga).returns());',
+ options: [{ assertFunctionNames: ['expect'] }],
+ errors: [
+ {
+ message: 'Test has no assertions',
+ type: 'CallExpression',
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/lowercase-name.test.js

@@ -0,0 +1,210 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../lowercase-name');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ },
+});
+
+ruleTester.run('lowercase-name', rule, {
+ valid: [
+ 'it()',
+ "it(' ', function () {})",
+ 'it(" ", function () {})',
+ 'it(` `, function () {})',
+ "it('foo', function () {})",
+ 'it("foo", function () {})',
+ 'it(`foo`, function () {})',
+ 'it("<Foo/>", function () {})',
+ 'it("123 foo", function () {})',
+ 'it(42, function () {})',
+ 'it(``)',
+ 'test()',
+ "test('foo', function () {})",
+ 'test("foo", function () {})',
+ 'test(`foo`, function () {})',
+ 'test("<Foo/>", function () {})',
+ 'test("123 foo", function () {})',
+ 'test("42", function () {})',
+ 'test(``)',
+ 'describe()',
+ "describe('foo', function () {})",
+ 'describe("foo", function () {})',
+ 'describe(`foo`, function () {})',
+ 'describe("<Foo/>", function () {})',
+ 'describe("123 foo", function () {})',
+ 'describe("42", function () {})',
+ 'describe(function () {})',
+ 'describe(``)',
+ ],
+
+ invalid: [
+ {
+ code: "it('Foo', function () {})",
+ output: "it('foo', function () {})",
+ errors: [
+ {
+ message: '`it`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'it("Foo", function () {})',
+ output: 'it("foo", function () {})',
+ errors: [
+ {
+ message: '`it`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'it(`Foo`, function () {})',
+ output: 'it(`foo`, function () {})',
+ errors: [
+ {
+ message: '`it`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: "test('Foo', function () {})",
+ output: "test('foo', function () {})",
+ errors: [
+ {
+ message: '`test`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'test("Foo", function () {})',
+ output: 'test("foo", function () {})',
+ errors: [
+ {
+ message: '`test`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'test(`Foo`, function () {})',
+ output: 'test(`foo`, function () {})',
+ errors: [
+ {
+ message: '`test`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: "describe('Foo', function () {})",
+ output: "describe('foo', function () {})",
+ errors: [
+ {
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'describe("Foo", function () {})',
+ output: 'describe("foo", function () {})',
+ errors: [
+ {
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'describe(`Foo`, function () {})',
+ output: 'describe(`foo`, function () {})',
+ errors: [
+ {
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'describe(`Some longer description`, function () {})',
+ output: 'describe(`some longer description`, function () {})',
+ errors: [
+ {
+ message: '`describe`s should begin with lowercase',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ ],
+});
+
+ruleTester.run('lowercase-name with ignore=describe', rule, {
+ valid: [
+ {
+ code: "describe('Foo', function () {})",
+ options: [{ ignore: ['describe'] }],
+ },
+ {
+ code: 'describe("Foo", function () {})',
+ options: [{ ignore: ['describe'] }],
+ },
+ {
+ code: 'describe(`Foo`, function () {})',
+ options: [{ ignore: ['describe'] }],
+ },
+ ],
+ invalid: [],
+});
+
+ruleTester.run('lowercase-name with ignore=test', rule, {
+ valid: [
+ {
+ code: "test('Foo', function () {})",
+ options: [{ ignore: ['test'] }],
+ },
+ {
+ code: 'test("Foo", function () {})',
+ options: [{ ignore: ['test'] }],
+ },
+ {
+ code: 'test(`Foo`, function () {})',
+ options: [{ ignore: ['test'] }],
+ },
+ ],
+ invalid: [],
+});
+
+ruleTester.run('lowercase-name with ignore=it', rule, {
+ valid: [
+ {
+ code: "it('Foo', function () {})",
+ options: [{ ignore: ['it'] }],
+ },
+ {
+ code: 'it("Foo", function () {})',
+ options: [{ ignore: ['it'] }],
+ },
+ {
+ code: 'it(`Foo`, function () {})',
+ options: [{ ignore: ['it'] }],
+ },
+ ],
+ invalid: [],
+});

src/rules/__tests__/no-alias-methods.test.js

@@ -0,0 +1,194 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-alias-methods');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('no-alias-methods', rule, {
+ valid: [
+ 'expect(a).toHaveBeenCalled()',
+ 'expect(a).toHaveBeenCalledTimes()',
+ 'expect(a).toHaveBeenCalledWith()',
+ 'expect(a).toHaveBeenLastCalledWith()',
+ 'expect(a).toHaveBeenNthCalledWith()',
+ 'expect(a).toHaveReturned()',
+ 'expect(a).toHaveReturnedTimes()',
+ 'expect(a).toHaveReturnedWith()',
+ 'expect(a).toHaveLastReturnedWith()',
+ 'expect(a).toHaveNthReturnedWith()',
+ 'expect(a).toThrow()',
+ 'expect(a).rejects;',
+ ],
+
+ invalid: [
+ {
+ code: 'expect(a).toBeCalled()',
+ errors: [
+ {
+ message:
+ 'Replace toBeCalled() with its canonical name of toHaveBeenCalled()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveBeenCalled()',
+ },
+ {
+ code: 'expect(a).toBeCalledTimes()',
+ errors: [
+ {
+ message:
+ 'Replace toBeCalledTimes() with its canonical name of toHaveBeenCalledTimes()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveBeenCalledTimes()',
+ },
+ {
+ code: 'expect(a).toBeCalledWith()',
+ errors: [
+ {
+ message:
+ 'Replace toBeCalledWith() with its canonical name of toHaveBeenCalledWith()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveBeenCalledWith()',
+ },
+ {
+ code: 'expect(a).lastCalledWith()',
+ errors: [
+ {
+ message:
+ 'Replace lastCalledWith() with its canonical name of toHaveBeenLastCalledWith()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveBeenLastCalledWith()',
+ },
+ {
+ code: 'expect(a).nthCalledWith()',
+ errors: [
+ {
+ message:
+ 'Replace nthCalledWith() with its canonical name of toHaveBeenNthCalledWith()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveBeenNthCalledWith()',
+ },
+ {
+ code: 'expect(a).toReturn()',
+ errors: [
+ {
+ message:
+ 'Replace toReturn() with its canonical name of toHaveReturned()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveReturned()',
+ },
+ {
+ code: 'expect(a).toReturnTimes()',
+ errors: [
+ {
+ message:
+ 'Replace toReturnTimes() with its canonical name of toHaveReturnedTimes()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveReturnedTimes()',
+ },
+ {
+ code: 'expect(a).toReturnWith()',
+ errors: [
+ {
+ message:
+ 'Replace toReturnWith() with its canonical name of toHaveReturnedWith()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveReturnedWith()',
+ },
+ {
+ code: 'expect(a).lastReturnedWith()',
+ errors: [
+ {
+ message:
+ 'Replace lastReturnedWith() with its canonical name of toHaveLastReturnedWith()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveLastReturnedWith()',
+ },
+ {
+ code: 'expect(a).nthReturnedWith()',
+ errors: [
+ {
+ message:
+ 'Replace nthReturnedWith() with its canonical name of toHaveNthReturnedWith()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toHaveNthReturnedWith()',
+ },
+ {
+ code: 'expect(a).toThrowError()',
+ errors: [
+ {
+ message:
+ 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 11,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toThrow()',
+ },
+ {
+ code: 'expect(a).resolves.toThrowError()',
+ errors: [
+ {
+ message:
+ 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 20,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).resolves.toThrow()',
+ },
+ {
+ code: 'expect(a).rejects.toThrowError()',
+ errors: [
+ {
+ message:
+ 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 19,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).rejects.toThrow()',
+ },
+ {
+ code: 'expect(a).not.toThrowError()',
+ errors: [
+ {
+ message:
+ 'Replace toThrowError() with its canonical name of toThrow()',
+ column: 15,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).not.toThrow()',
+ },
+ ],
+});

src/rules/__tests__/no-commented-out-tests.test.js

@@ -0,0 +1,217 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-commented-out-tests');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ sourceType: 'module',
+ },
+});
+
+ruleTester.run('no-commented-out-tests', rule, {
+ valid: [
+ '// foo("bar", function () {})',
+ 'describe("foo", function () {})',
+ 'it("foo", function () {})',
+ 'describe.only("foo", function () {})',
+ 'it.only("foo", function () {})',
+ 'test("foo", function () {})',
+ 'test.only("foo", function () {})',
+ 'var appliedSkip = describe.skip; appliedSkip.apply(describe)',
+ 'var calledSkip = it.skip; calledSkip.call(it)',
+ '({ f: function () {} }).f()',
+ '(a || b).f()',
+ 'itHappensToStartWithIt()',
+ 'testSomething()',
+ '// latest(dates)',
+ '// TODO: unify with Git implementation from Shipit (?)',
+ [
+ 'import { pending } from "actions"',
+ '',
+ 'test("foo", () => {',
+ ' expect(pending()).toEqual({})',
+ '})',
+ ].join('\n'),
+ [
+ 'const { pending } = require("actions")',
+ '',
+ 'test("foo", () => {',
+ ' expect(pending()).toEqual({})',
+ '})',
+ ].join('\n'),
+ [
+ 'test("foo", () => {',
+ ' const pending = getPending()',
+ ' expect(pending()).toEqual({})',
+ '})',
+ ].join('\n'),
+ [
+ 'test("foo", () => {',
+ ' expect(pending()).toEqual({})',
+ '})',
+ '',
+ 'function pending() {',
+ ' return {}',
+ '}',
+ ].join('\n'),
+ ],
+
+ invalid: [
+ {
+ code: '// describe("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// describe["skip"]("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// describe[\'skip\']("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// it.skip("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// it.only("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// it["skip"]("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// test.skip("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// test["skip"]("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// xdescribe("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// xit("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// fit("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// xtest("foo", function () {})',
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: `// test(
+ // "foo", function () {}
+ // )`,
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: `/* test
+ (
+ "foo", function () {}
+ )
+ */`,
+ errors: [
+ { message: 'Some tests seem to be commented', column: 1, line: 1 },
+ ],
+ },
+ {
+ code: '// it("has title but no callback")',
+ errors: [
+ {
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: '// it()',
+ errors: [
+ {
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: '// test.someNewMethodThatMightBeAddedInTheFuture()',
+ errors: [
+ {
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: '// test["someNewMethodThatMightBeAddedInTheFuture"]()',
+ errors: [
+ {
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: '// test("has title but no callback")',
+ errors: [
+ {
+ message: 'Some tests seem to be commented',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: `
+ foo()
+ /*
+ describe("has title but no callback", () => {})
+ */
+ bar()`,
+ errors: [
+ {
+ message: 'Some tests seem to be commented',
+ column: 7,
+ line: 3,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-disabled-tests.test.js

@@ -0,0 +1,135 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-disabled-tests');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ sourceType: 'module',
+ },
+});
+
+ruleTester.run('no-disabled-tests', rule, {
+ valid: [
+ 'describe("foo", function () {})',
+ 'it("foo", function () {})',
+ 'describe.only("foo", function () {})',
+ 'it.only("foo", function () {})',
+ 'test("foo", function () {})',
+ 'test.only("foo", function () {})',
+ 'var appliedSkip = describe.skip; appliedSkip.apply(describe)',
+ 'var calledSkip = it.skip; calledSkip.call(it)',
+ '({ f: function () {} }).f()',
+ '(a || b).f()',
+ 'itHappensToStartWithIt()',
+ 'testSomething()',
+ [
+ 'import { pending } from "actions"',
+ '',
+ 'test("foo", () => {',
+ ' expect(pending()).toEqual({})',
+ '})',
+ ].join('\n'),
+ [
+ 'const { pending } = require("actions")',
+ '',
+ 'test("foo", () => {',
+ ' expect(pending()).toEqual({})',
+ '})',
+ ].join('\n'),
+ [
+ 'test("foo", () => {',
+ ' const pending = getPending()',
+ ' expect(pending()).toEqual({})',
+ '})',
+ ].join('\n'),
+ [
+ 'test("foo", () => {',
+ ' expect(pending()).toEqual({})',
+ '})',
+ '',
+ 'function pending() {',
+ ' return {}',
+ '}',
+ ].join('\n'),
+ ],
+
+ invalid: [
+ {
+ code: 'describe.skip("foo", function () {})',
+ errors: [{ message: 'Skipped test suite', column: 1, line: 1 }],
+ },
+ {
+ code: 'describe["skip"]("foo", function () {})',
+ errors: [{ message: 'Skipped test suite', column: 1, line: 1 }],
+ },
+ {
+ code: 'it.skip("foo", function () {})',
+ errors: [{ message: 'Skipped test', column: 1, line: 1 }],
+ },
+ {
+ code: 'it["skip"]("foo", function () {})',
+ errors: [{ message: 'Skipped test', column: 1, line: 1 }],
+ },
+ {
+ code: 'test.skip("foo", function () {})',
+ errors: [{ message: 'Skipped test', column: 1, line: 1 }],
+ },
+ {
+ code: 'test["skip"]("foo", function () {})',
+ errors: [{ message: 'Skipped test', column: 1, line: 1 }],
+ },
+ {
+ code: 'xdescribe("foo", function () {})',
+ errors: [{ message: 'Disabled test suite', column: 1, line: 1 }],
+ },
+ {
+ code: 'xit("foo", function () {})',
+ errors: [{ message: 'Disabled test', column: 1, line: 1 }],
+ },
+ {
+ code: 'xtest("foo", function () {})',
+ errors: [{ message: 'Disabled test', column: 1, line: 1 }],
+ },
+ {
+ code: 'it("has title but no callback")',
+ errors: [
+ {
+ message: 'Test is missing function argument',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'test("has title but no callback")',
+ errors: [
+ {
+ message: 'Test is missing function argument',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'it("contains a call to pending", function () { pending() })',
+ errors: [
+ { message: 'Call to pending() within test', column: 48, line: 1 },
+ ],
+ },
+ {
+ code: 'pending();',
+ errors: [{ message: 'Call to pending()', column: 1, line: 1 }],
+ },
+ {
+ code: 'describe("contains a call to pending", function () { pending() })',
+ errors: [
+ {
+ message: 'Call to pending() within test suite',
+ column: 54,
+ line: 1,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-empty-title.js

@@ -0,0 +1,108 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-empty-title');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ sourceType: 'module',
+ },
+});
+
+ruleTester.run('no-empty-title', rule, {
+ valid: [
+ 'someFn("", function () {})',
+ 'describe(1, function () {})',
+ 'describe("foo", function () {})',
+ 'describe("foo", function () { it("bar", function () {}) })',
+ 'test("foo", function () {})',
+ 'test(`foo`, function () {})',
+ 'test(`${foo}`, function () {})',
+ "it('foo', function () {})",
+ "xdescribe('foo', function () {})",
+ "xit('foo', function () {})",
+ "xtest('foo', function () {})",
+ ],
+ invalid: [
+ {
+ code: 'describe("", function () {})',
+ errors: [
+ {
+ message: rule.errorMessages.describe,
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: ["describe('foo', () => {", "it('', () => {})", '})'].join('\n'),
+ errors: [
+ {
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: 'it("", function () {})',
+ errors: [
+ {
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'test("", function () {})',
+ errors: [
+ {
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'test(``, function () {})',
+ errors: [
+ {
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: "xdescribe('', () => {})",
+ errors: [
+ {
+ message: rule.errorMessages.describe,
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: "xit('', () => {})",
+ errors: [
+ {
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: "xtest('', () => {})",
+ errors: [
+ {
+ message: rule.errorMessages.test,
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-focused-tests.test.js

@@ -0,0 +1,71 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-focused-tests');
+
+const ruleTester = new RuleTester();
+const expectedErrorMessage = 'Unexpected focused test.';
+
+ruleTester.run('no-focused-tests', rule, {
+ valid: [
+ 'describe()',
+ 'it()',
+ 'describe.skip()',
+ 'it.skip()',
+ 'test()',
+ 'test.skip()',
+ 'var appliedOnly = describe.only; appliedOnly.apply(describe)',
+ 'var calledOnly = it.only; calledOnly.call(it)',
+ ],
+
+ invalid: [
+ {
+ code: 'describe.only()',
+ errors: [{ message: expectedErrorMessage, column: 10, line: 1 }],
+ },
+ {
+ code: 'describe.only.each()',
+ errors: [{ message: expectedErrorMessage, column: 10, line: 1 }],
+ },
+ {
+ code: 'describe["only"]()',
+ errors: [{ message: expectedErrorMessage, column: 10, line: 1 }],
+ },
+ {
+ code: 'it.only()',
+ errors: [{ message: expectedErrorMessage, column: 4, line: 1 }],
+ },
+ {
+ code: 'it.only.each()',
+ errors: [{ message: expectedErrorMessage, column: 4, line: 1 }],
+ },
+ {
+ code: 'it["only"]()',
+ errors: [{ message: expectedErrorMessage, column: 4, line: 1 }],
+ },
+ {
+ code: 'test.only()',
+ errors: [{ message: expectedErrorMessage, column: 6, line: 1 }],
+ },
+ {
+ code: 'test.only.each()',
+ errors: [{ message: expectedErrorMessage, column: 6, line: 1 }],
+ },
+ {
+ code: 'test["only"]()',
+ errors: [{ message: expectedErrorMessage, column: 6, line: 1 }],
+ },
+ {
+ code: 'fdescribe()',
+ errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
+ },
+ {
+ code: 'fit()',
+ errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
+ },
+ {
+ code: 'fit.each()',
+ errors: [{ message: expectedErrorMessage, column: 1, line: 1 }],
+ },
+ ],
+});

src/rules/__tests__/no-hooks.test.js

@@ -0,0 +1,45 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-hooks');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ },
+});
+
+ruleTester.run('no-hooks', rule, {
+ valid: [
+ 'test("foo")',
+ 'describe("foo", () => { it("bar") })',
+ 'test("foo", () => { expect(subject.beforeEach()).toBe(true) })',
+ {
+ code: 'afterEach(() => {}); afterAll(() => {});',
+ options: [{ allow: ['afterEach', 'afterAll'] }],
+ },
+ ],
+ invalid: [
+ {
+ code: 'beforeAll(() => {})',
+ errors: [{ message: "Unexpected 'beforeAll' hook" }],
+ },
+ {
+ code: 'beforeEach(() => {})',
+ errors: [{ message: "Unexpected 'beforeEach' hook" }],
+ },
+ {
+ code: 'afterAll(() => {})',
+ errors: [{ message: "Unexpected 'afterAll' hook" }],
+ },
+ {
+ code: 'afterEach(() => {})',
+ errors: [{ message: "Unexpected 'afterEach' hook" }],
+ },
+ {
+ code: 'beforeEach(() => {}); afterEach(() => { jest.resetModules() });',
+ options: [{ allow: ['afterEach'] }],
+ errors: [{ message: "Unexpected 'beforeEach' hook" }],
+ },
+ ],
+});

src/rules/__tests__/no-identical-title.test.js

@@ -0,0 +1,269 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-identical-title');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('no-identical-title', rule, {
+ valid: [
+ [
+ 'describe("describe", function() {',
+ ' it("it", function() {});',
+ '});',
+ ].join('\n'),
+ ['describe();describe();'].join('\n'),
+ ['it();it();'].join('\n'),
+ [
+ 'describe("describe1", function() {',
+ ' it("it1", function() {});',
+ ' it("it2", function() {});',
+ '});',
+ ].join('\n'),
+ ['it("it1", function() {});', 'it("it2", function() {});'].join('\n'),
+ ['it.only("it1", function() {});', 'it("it2", function() {});'].join('\n'),
+ ['it.only("it1", function() {});', 'it.only("it2", function() {});'].join(
+ '\n',
+ ),
+ ['describe("title", function() {});', 'it("title", function() {});'].join(
+ '\n',
+ ),
+ [
+ 'describe("describe1", function() {',
+ ' it("it1", function() {});',
+ ' describe("describe2", function() {',
+ ' it("it1", function() {});',
+ ' });',
+ '});',
+ ].join('\n'),
+ [
+ 'describe("describe1", function() {',
+ ' describe("describe2", function() {',
+ ' it("it1", function() {});',
+ ' });',
+ ' it("it1", function() {});',
+ '});',
+ ].join('\n'),
+ [
+ 'describe("describe1", function() {',
+ ' describe("describe2", function() {});',
+ '});',
+ ].join('\n'),
+ [
+ 'describe("describe1", function() {',
+ ' describe("describe2", function() {});',
+ '});',
+ 'describe("describe2", function() {});',
+ ].join('\n'),
+ [
+ 'describe("describe1", function() {});',
+ 'describe("describe2", function() {});',
+ ].join('\n'),
+ ['it("it" + n, function() {});', 'it("it" + n, function() {});'].join('\n'),
+ {
+ code: [
+ 'it(`it${n}`, function() {});',
+ 'it(`it${n}`, function() {});',
+ ].join('\n'),
+ env: {
+ es6: true,
+ },
+ },
+ {
+ code: 'it(`${n}`, function() {});',
+ env: {
+ es6: true,
+ },
+ },
+ [
+ 'describe("title " + foo, function() {',
+ ' describe("describe1", function() {});',
+ '});',
+ 'describe("describe1", function() {});',
+ ].join('\n'),
+ [
+ 'describe("describe1", function() {',
+ ' describe("describe2", function() {});',
+ ' describe("title " + foo, function() {',
+ ' describe("describe2", function() {});',
+ ' });',
+ '});',
+ ].join('\n'),
+ {
+ code: [
+ 'describe("describe", () => {',
+ ' it(`testing ${someVar} with the same title`, () => {});',
+ ' it(`testing ${someVar} with the same title`, () => {});',
+ '});',
+ ].join('\n'),
+ env: {
+ es6: true,
+ },
+ },
+ {
+ code: ['it(`it1`, () => {});', 'it(`it2`, () => {});'].join('\n'),
+ env: {
+ es6: true,
+ },
+ },
+ {
+ code: [
+ 'describe(`describe1`, () => {});',
+ 'describe(`describe2`, () => {});',
+ ].join('\n'),
+ env: {
+ es6: true,
+ },
+ },
+ ],
+
+ invalid: [
+ {
+ code: [
+ 'describe("describe1", function() {',
+ ' it("it1", function() {});',
+ ' it("it1", function() {});',
+ '});',
+ ].join('\n'),
+ errors: [
+ {
+ message:
+ 'Test title is used multiple times in the same describe block.',
+ column: 4,
+ line: 3,
+ },
+ ],
+ },
+ {
+ code: ['it("it1", function() {});', 'it("it1", function() {});'].join(
+ '\n',
+ ),
+ errors: [
+ {
+ message:
+ 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: [
+ 'it.only("it1", function() {});',
+ 'it("it1", function() {});',
+ ].join('\n'),
+ errors: [
+ {
+ message:
+ 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: ['fit("it1", function() {});', 'it("it1", function() {});'].join(
+ '\n',
+ ),
+ errors: [
+ {
+ message:
+ 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: [
+ 'it.only("it1", function() {});',
+ 'it.only("it1", function() {});',
+ ].join('\n'),
+ errors: [
+ {
+ message:
+ 'Test title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: [
+ 'describe("describe1", function() {});',
+ 'describe("describe1", function() {});',
+ ].join('\n'),
+ errors: [
+ {
+ message:
+ 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: [
+ 'describe("describe1", function() {});',
+ 'xdescribe("describe1", function() {});',
+ ].join('\n'),
+ errors: [
+ {
+ message:
+ 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: [
+ 'fdescribe("describe1", function() {});',
+ 'describe("describe1", function() {});',
+ ].join('\n'),
+ errors: [
+ {
+ message:
+ 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 2,
+ },
+ ],
+ },
+ {
+ code: [
+ 'describe("describe1", function() {',
+ ' describe("describe2", function() {});',
+ '});',
+ 'describe("describe1", function() {});',
+ ].join('\n'),
+ errors: [
+ {
+ message:
+ 'Describe block title is used multiple times in the same describe block.',
+ column: 1,
+ line: 4,
+ },
+ ],
+ },
+ {
+ code: [
+ 'describe("describe", () => {',
+ ' it(`testing backticks with the same title`, () => {});',
+ ' it(`testing backticks with the same title`, () => {});',
+ '});',
+ ].join('\n'),
+ env: {
+ es6: true,
+ },
+ errors: [
+ {
+ message:
+ 'Test title is used multiple times in the same describe block.',
+ column: 5,
+ line: 3,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-jasmine-globals.test.js

@@ -0,0 +1,228 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-jasmine-globals');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('no-jasmine-globals', rule, {
+ valid: [
+ 'jest.spyOn()',
+ 'jest.fn()',
+ 'expect.extend()',
+ 'expect.any()',
+ 'it("foo", function () {})',
+ 'test("foo", function () {})',
+ 'foo()',
+ `require('foo')('bar')`,
+ 'function callback(fail) { fail() }',
+ 'var spyOn = require("actions"); spyOn("foo")',
+ 'function callback(pending) { pending() }',
+ ],
+ invalid: [
+ {
+ code: 'spyOn(some, "object")',
+ errors: [
+ {
+ message: 'Illegal usage of global `spyOn`, prefer `jest.spyOn`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'spyOnProperty(some, "object")',
+ errors: [
+ {
+ message:
+ 'Illegal usage of global `spyOnProperty`, prefer `jest.spyOn`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'fail()',
+ errors: [
+ {
+ message:
+ 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'pending()',
+ errors: [
+ {
+ message:
+ 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ output: 'jest.setTimeout(5000);',
+ },
+ {
+ code: 'jasmine.addMatchers(matchers)',
+ errors: [
+ {
+ message:
+ 'Illegal usage of `jasmine.addMatchers`, prefer `expect.extend`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.createSpy()',
+ errors: [
+ {
+ message: 'Illegal usage of `jasmine.createSpy`, prefer `jest.fn`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.any()',
+ errors: [
+ {
+ message: 'Illegal usage of `jasmine.any`, prefer `expect.any`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ output: 'expect.any()',
+ },
+ {
+ code: 'jasmine.anything()',
+ errors: [
+ {
+ message:
+ 'Illegal usage of `jasmine.anything`, prefer `expect.anything`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ output: 'expect.anything()',
+ },
+ {
+ code: 'jasmine.arrayContaining()',
+ errors: [
+ {
+ message:
+ 'Illegal usage of `jasmine.arrayContaining`, prefer `expect.arrayContaining`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ output: 'expect.arrayContaining()',
+ },
+ {
+ code: 'jasmine.objectContaining()',
+ errors: [
+ {
+ message:
+ 'Illegal usage of `jasmine.objectContaining`, prefer `expect.objectContaining`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ output: 'expect.objectContaining()',
+ },
+ {
+ code: 'jasmine.stringMatching()',
+ errors: [
+ {
+ message:
+ 'Illegal usage of `jasmine.stringMatching`, prefer `expect.stringMatching`',
+ column: 1,
+ line: 1,
+ },
+ ],
+ output: 'expect.stringMatching()',
+ },
+ {
+ code: 'jasmine.getEnv()',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.empty()',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.falsy()',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.truthy()',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.arrayWithExactContents()',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.clock()',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH = 42',
+ errors: [
+ {
+ message: 'Illegal usage of jasmine global',
+ column: 1,
+ line: 1,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-jest-import.test.js

@@ -0,0 +1,73 @@
+'use strict';
+
+const rule = require('../no-jest-import.js');
+const { RuleTester } = require('eslint');
+const ruleTester = new RuleTester();
+const message = `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`;
+
+ruleTester.run('no-jest-import', rule, {
+ valid: [
+ {
+ code: 'import something from "something"',
+ parserOptions: { sourceType: 'module' },
+ },
+ 'require("somethingElse")',
+ 'require()',
+ 'entirelyDifferent(fn)',
+ ],
+ invalid: [
+ {
+ code: 'require("jest")',
+ errors: [
+ {
+ endColumn: 15,
+ column: 9,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'import jest from "jest"',
+ parserOptions: { sourceType: 'module' },
+ errors: [
+ {
+ endColumn: 24,
+ column: 1,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'var jest = require("jest")',
+ errors: [
+ {
+ endColumn: 26,
+ column: 20,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'import {jest as test} from "jest"',
+ parserOptions: { sourceType: 'module' },
+ errors: [
+ {
+ endColumn: 34,
+ column: 1,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'const jest = require("jest")',
+ parserOptions: { sourceType: 'module' },
+ errors: [
+ {
+ endColumn: 28,
+ column: 22,
+ message,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-large-snapshots.test.js

@@ -0,0 +1,173 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-large-snapshots');
+const noLargeSnapshots = rule.create;
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 2015,
+ },
+});
+
+ruleTester.run('no-large-snapshots', rule, {
+ valid: [
+ {
+ filename: 'mock.js',
+ code: `expect(something).toMatchInlineSnapshot(\`\n${'line\n'.repeat(
+ 2,
+ )}\`);`,
+ },
+ {
+ filename: 'mock.js',
+ code: `expect(something).toThrowErrorMatchingInlineSnapshot(\`\n${'line\n'.repeat(
+ 2,
+ )}\`);`,
+ },
+ ],
+ invalid: [
+ {
+ filename: 'mock.js',
+ code: `expect(something).toMatchInlineSnapshot(\`\n${'line\n'.repeat(
+ 50,
+ )}\`);`,
+ errors: [
+ {
+ message:
+ 'Expected Jest snapshot to be smaller than 50 lines but was 51 lines long',
+ },
+ ],
+ },
+ {
+ filename: 'mock.js',
+ code: `expect(something).toThrowErrorMatchingInlineSnapshot(\`\n${'line\n'.repeat(
+ 50,
+ )}\`);`,
+ errors: [
+ {
+ message:
+ 'Expected Jest snapshot to be smaller than 50 lines but was 51 lines long',
+ },
+ ],
+ },
+ ],
+});
+
+// was not able to use https://eslint.org/docs/developer-guide/nodejs-api#ruletester for these because there is no way to configure RuleTester to run non .js files
+describe('no-large-snapshots', () => {
+ it('should return an empty object for non snapshot files', () => {
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx',
+ options: [],
+ };
+ const result = noLargeSnapshots(mockContext);
+
+ expect(result).toEqual({});
+ });
+
+ it('should return an object with an ExpressionStatement function for snapshot files', () => {
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [],
+ };
+
+ const result = noLargeSnapshots(mockContext);
+
+ expect(result).toMatchObject({
+ ExpressionStatement: expect.any(Function),
+ });
+ });
+
+ describe('ExpressionStatement function', () => {
+ it('should report if node has more than 50 lines of code and no sizeThreshold option is passed', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [],
+ report: mockReport,
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 1,
+ },
+ end: {
+ line: 53,
+ },
+ },
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+
+ expect(mockReport).toHaveBeenCalledTimes(1);
+ expect(mockReport.mock.calls[0]).toMatchSnapshot();
+ });
+
+ it('should report if node has more lines of code than number given in sizeThreshold option', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [{ maxSize: 70 }],
+ report: mockReport,
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 20,
+ },
+ end: {
+ line: 103,
+ },
+ },
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+
+ expect(mockReport).toHaveBeenCalledTimes(1);
+ expect(mockReport.mock.calls[0]).toMatchSnapshot();
+ });
+
+ it('should report if maxSize is zero', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [{ maxSize: 0 }],
+ report: mockReport,
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 1,
+ },
+ end: {
+ line: 2,
+ },
+ },
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+
+ expect(mockReport).toHaveBeenCalledTimes(1);
+ expect(mockReport.mock.calls[0]).toMatchSnapshot();
+ });
+
+ it('should not report if node has fewer lines of code than limit', () => {
+ const mockReport = jest.fn();
+ const mockContext = {
+ getFilename: () => 'mock-component.jsx.snap',
+ options: [],
+ report: mockReport,
+ };
+ const mockNode = {
+ loc: {
+ start: {
+ line: 1,
+ },
+ end: {
+ line: 18,
+ },
+ },
+ };
+ noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
+
+ expect(mockReport).not.toHaveBeenCalled();
+ });
+ });
+});

src/rules/__tests__/no-mocks-import.test.js

@@ -0,0 +1,97 @@
+'use strict';
+
+const rule = require('../no-mocks-import.js');
+const { RuleTester } = require('eslint');
+const ruleTester = new RuleTester();
+const message = `Mocks should not be manually imported from a __mocks__ directory. Instead use jest.mock and import from the original module path.`;
+
+ruleTester.run('no-mocks-import', rule, {
+ valid: [
+ {
+ code: 'import something from "something"',
+ parserOptions: { sourceType: 'module' },
+ },
+ 'require("somethingElse")',
+ 'require("./__mocks__.js")',
+ 'require("./__mocks__x")',
+ 'require("./__mocks__x/x")',
+ 'require("./x__mocks__")',
+ 'require("./x__mocks__/x")',
+ 'require()',
+ 'var path = "./__mocks__.js"; require(path)',
+ 'entirelyDifferent(fn)',
+ ],
+ invalid: [
+ {
+ code: 'require("./__mocks__")',
+ errors: [
+ {
+ endColumn: 22,
+ column: 9,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'require("./__mocks__/")',
+ errors: [
+ {
+ endColumn: 23,
+ column: 9,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'require("./__mocks__/index")',
+ errors: [
+ {
+ endColumn: 28,
+ column: 9,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'require("__mocks__")',
+ errors: [
+ {
+ endColumn: 20,
+ column: 9,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'require("__mocks__/")',
+ errors: [
+ {
+ endColumn: 21,
+ column: 9,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'require("__mocks__/index")',
+ errors: [
+ {
+ endColumn: 26,
+ column: 9,
+ message,
+ },
+ ],
+ },
+ {
+ code: 'import thing from "./__mocks__/index"',
+ parserOptions: { sourceType: 'module' },
+ errors: [
+ {
+ endColumn: 38,
+ column: 1,
+ message,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-test-callback.test.js

@@ -0,0 +1,127 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-test-callback');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 8,
+ },
+});
+
+ruleTester.run('no-test-callback', rule, {
+ valid: [
+ 'test("something", () => {})',
+ 'test("something", async () => {})',
+ 'test("something", function() {})',
+ 'test("something", async function () {})',
+ 'test("something", someArg)',
+ ],
+ invalid: [
+ {
+ code: 'test("something", done => {done();})',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 19,
+ },
+ ],
+ output:
+ 'test("something", () => {return new Promise(done => {done();})})',
+ },
+ {
+ code: 'test("something", (done) => {done();})',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 20,
+ },
+ ],
+ output:
+ 'test("something", () => {return new Promise((done) => {done();})})',
+ },
+ {
+ code: 'test("something", done => done())',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 19,
+ },
+ ],
+ output: 'test("something", () => new Promise(done => done()))',
+ },
+ {
+ code: 'test("something", (done) => done())',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 20,
+ },
+ ],
+ output: 'test("something", () => new Promise((done) => done()))',
+ },
+ {
+ code: 'test("something", function(done) {done();})',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 28,
+ },
+ ],
+ output:
+ 'test("something", function() {return new Promise((done) => {done();})})',
+ },
+ {
+ code: 'test("something", function (done) {done();})',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 29,
+ },
+ ],
+ output:
+ 'test("something", function () {return new Promise((done) => {done();})})',
+ },
+ {
+ code: 'test("something", async done => {done();})',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 25,
+ },
+ ],
+ output:
+ 'test("something", async () => {await new Promise(done => {done();})})',
+ },
+ {
+ code: 'test("something", async done => done())',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 25,
+ },
+ ],
+ output: 'test("something", async () => new Promise(done => done()))',
+ },
+ {
+ code: 'test("something", async function (done) {done();})',
+ errors: [
+ {
+ message: 'Illegal usage of test callback',
+ line: 1,
+ column: 35,
+ },
+ ],
+ output:
+ 'test("something", async function () {await new Promise((done) => {done();})})',
+ },
+ ],
+});

src/rules/__tests__/no-test-prefixes.test.js

@@ -0,0 +1,48 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-test-prefixes');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('no-test-prefixes', rule, {
+ valid: [
+ 'describe("foo", function () {})',
+ 'it("foo", function () {})',
+ 'test("foo", function () {})',
+ 'describe.only("foo", function () {})',
+ 'it.only("foo", function () {})',
+ 'test.only("foo", function () {})',
+ 'describe.skip("foo", function () {})',
+ 'it.skip("foo", function () {})',
+ 'test.skip("foo", function () {})',
+ 'foo()',
+ ],
+ invalid: [
+ {
+ code: 'fdescribe("foo", function () {})',
+ errors: [{ message: 'Use "describe.only" instead', column: 1, line: 1 }],
+ output: 'describe.only("foo", function () {})',
+ },
+ {
+ code: 'fit("foo", function () {})',
+ errors: [{ message: 'Use "it.only" instead', column: 1, line: 1 }],
+ output: 'it.only("foo", function () {})',
+ },
+ {
+ code: 'xdescribe("foo", function () {})',
+ errors: [{ message: 'Use "describe.skip" instead', column: 1, line: 1 }],
+ output: 'describe.skip("foo", function () {})',
+ },
+ {
+ code: 'xit("foo", function () {})',
+ errors: [{ message: 'Use "it.skip" instead', column: 1, line: 1 }],
+ output: 'it.skip("foo", function () {})',
+ },
+ {
+ code: 'xtest("foo", function () {})',
+ errors: [{ message: 'Use "test.skip" instead', column: 1, line: 1 }],
+ output: 'test.skip("foo", function () {})',
+ },
+ ],
+});

src/rules/__tests__/no-test-return-statement.test.js

@@ -0,0 +1,55 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-test-return-statement');
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
+
+ruleTester.run('no-test-prefixes', rule, {
+ valid: [
+ 'it("noop", function () {});',
+ 'test("noop", () => {});',
+ 'test("one", () => expect(1).toBe(1));',
+ 'test("empty")',
+ `
+ test("one", () => {
+ expect(1).toBe(1);
+ });
+ `,
+ `
+ it("one", function () {
+ expect(1).toBe(1);
+ });
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+ test("one", () => {
+ return expect(1).toBe(1);
+ });
+ `,
+ errors: [
+ {
+ message: 'Jest tests should not return a value.',
+ column: 9,
+ line: 3,
+ },
+ ],
+ },
+ {
+ code: `
+ it("one", function () {
+ return expect(1).toBe(1);
+ });
+ `,
+ errors: [
+ {
+ message: 'Jest tests should not return a value.',
+ column: 9,
+ line: 3,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/no-truthy-falsy.test.js

@@ -0,0 +1,102 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../no-truthy-falsy');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('no-truthy-falsy', rule, {
+ valid: [
+ 'expect(true).toBe(true);',
+ 'expect(false).toBe(false);',
+ 'expect("anything").toBe(true);',
+ 'expect("anything").toEqual(false);',
+ 'expect("anything").not.toBe(true);',
+ 'expect("anything").not.toEqual(true);',
+ 'expect(Promise.resolve({})).resolves.toBe(true);',
+ 'expect(Promise.reject({})).rejects.toBe(true);',
+ ],
+
+ invalid: [
+ {
+ code: 'expect(true).toBeTruthy();',
+ errors: [
+ {
+ message: 'Avoid toBeTruthy',
+ column: 14,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(false).not.toBeTruthy();',
+ errors: [
+ {
+ message: 'Avoid toBeTruthy',
+ column: 19,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(Promise.resolve({})).resolves.toBeTruthy()',
+ errors: [
+ {
+ message: 'Avoid toBeTruthy',
+ column: 38,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(Promise.resolve({})).rejects.toBeTruthy()',
+ errors: [
+ {
+ message: 'Avoid toBeTruthy',
+ column: 37,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(false).toBeFalsy();',
+ errors: [
+ {
+ message: 'Avoid toBeFalsy',
+ column: 15,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(true).not.toBeFalsy();',
+ errors: [
+ {
+ message: 'Avoid toBeFalsy',
+ column: 18,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(Promise.resolve({})).resolves.toBeFalsy()',
+ errors: [
+ {
+ message: 'Avoid toBeFalsy',
+ column: 38,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(Promise.resolve({})).rejects.toBeFalsy()',
+ errors: [
+ {
+ message: 'Avoid toBeFalsy',
+ column: 37,
+ line: 1,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/prefer-called-with.js

@@ -0,0 +1,44 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-called-with');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('prefer-called-with', rule, {
+ valid: [
+ 'expect(fn).toBeCalledWith();',
+ 'expect(fn).toHaveBeenCalledWith();',
+ 'expect(fn).toBeCalledWith(expect.anything());',
+ 'expect(fn).toHaveBeenCalledWith(expect.anything());',
+ 'expect(fn).not.toBeCalled();',
+ 'expect(fn).not.toHaveBeenCalled();',
+ 'expect(fn).not.toBeCalledWith();',
+ 'expect(fn).not.toHaveBeenCalledWith();',
+ 'expect(fn).toBeCalledTimes(0);',
+ 'expect(fn).toHaveBeenCalledTimes(0);',
+ ],
+
+ invalid: [
+ {
+ code: 'expect(fn).toBeCalled();',
+ errors: [
+ {
+ message: 'Prefer toBeCalledWith(/* expected args */)',
+ column: 12,
+ line: 1,
+ },
+ ],
+ },
+ {
+ code: 'expect(fn).toHaveBeenCalled();',
+ errors: [
+ {
+ message: 'Prefer toHaveBeenCalledWith(/* expected args */)',
+ column: 12,
+ line: 1,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/prefer-expect-assertions.test.js

@@ -0,0 +1,93 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-expect-assertions');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ },
+});
+
+const expectedMsg =
+ 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression';
+
+ruleTester.run('prefer-expect-assertions', rule, {
+ invalid: [
+ {
+ code: 'it("it1", () => {})',
+ errors: [
+ {
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: 'it("it1", () => { foo()})',
+ errors: [
+ {
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code:
+ 'it("it1", function() {' +
+ '\n\t\t\tsomeFunctionToDo();' +
+ '\n\t\t\tsomeFunctionToDo2();\n' +
+ '\t\t\t})',
+ errors: [
+ {
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: 'it("it1", function() {var a = 2;})',
+ errors: [
+ {
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: 'it("it1", function() {expect.assertions();})',
+ errors: [
+ {
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: 'it("it1", function() {expect.assertions(1,2);})',
+ errors: [
+ {
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: 'it("it1", function() {expect.assertions("1");})',
+ errors: [
+ {
+ message: expectedMsg,
+ },
+ ],
+ },
+ ],
+
+ valid: [
+ {
+ code: 'test("it1", () => {expect.assertions(0);})',
+ },
+ 'test("it1", function() {expect.assertions(0);})',
+ 'test("it1", function() {expect.hasAssertions();})',
+ 'it("it1", function() {expect.assertions(0);})',
+ 'it("it1", function() {\n\t\t\texpect.assertions(1);' +
+ '\n\t\t\texpect(someValue).toBe(true)\n' +
+ '\t\t\t})',
+ 'test("it1")',
+ 'itHappensToStartWithIt("foo", function() {})',
+ 'testSomething("bar", function() {})',
+ ],
+});

src/rules/__tests__/prefer-inline-snapshots.test.js

@@ -0,0 +1,37 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-inline-snapshots');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('prefer-inline-snapshots', rule, {
+ valid: [
+ 'expect(something).toMatchInlineSnapshot();',
+ 'expect(something).toThrowErrorMatchingInlineSnapshot();',
+ ],
+ invalid: [
+ {
+ code: 'expect(something).toMatchSnapshot();',
+ errors: [
+ {
+ message: 'Use toMatchInlineSnapshot() instead',
+ column: 19,
+ line: 1,
+ },
+ ],
+ output: 'expect(something).toMatchInlineSnapshot();',
+ },
+ {
+ code: 'expect(something).toThrowErrorMatchingSnapshot();',
+ errors: [
+ {
+ message: 'Use toThrowErrorMatchingInlineSnapshot() instead',
+ column: 19,
+ line: 1,
+ },
+ ],
+ output: 'expect(something).toThrowErrorMatchingInlineSnapshot();',
+ },
+ ],
+});

src/rules/__tests__/prefer-spy-on.test.js

@@ -0,0 +1,109 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-spy-on');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ },
+});
+
+ruleTester.run('prefer-spy-on', rule, {
+ valid: [
+ 'Date.now = () => 10',
+ 'window.fetch = jest.fn',
+ 'Date.now = fn()',
+ 'obj.mock = jest.something()',
+ 'const mock = jest.fn()',
+ 'mock = jest.fn()',
+ 'const mockObj = { mock: jest.fn() }',
+ 'mockObj = { mock: jest.fn() }',
+ 'window[`${name}`] = jest[`fn${expression}`]()',
+ ],
+ invalid: [
+ {
+ code: 'obj.a = jest.fn(); const test = 10;',
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output: "jest.spyOn(obj, 'a'); const test = 10;",
+ },
+ {
+ code: "Date['now'] = jest['fn']()",
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output: "jest.spyOn(Date, 'now')",
+ },
+ {
+ code: 'window[`${name}`] = jest[`fn`]()',
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output: 'jest.spyOn(window, `${name}`)',
+ },
+ {
+ code: "obj['prop' + 1] = jest['fn']()",
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output: "jest.spyOn(obj, 'prop' + 1)",
+ },
+ {
+ code: 'obj.one.two = jest.fn(); const test = 10;',
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output: "jest.spyOn(obj.one, 'two'); const test = 10;",
+ },
+ {
+ code: 'obj.a = jest.fn(() => 10)',
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output: "jest.spyOn(obj, 'a').mockImplementation(() => 10)",
+ },
+ {
+ code:
+ "obj.a.b = jest.fn(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output:
+ "jest.spyOn(obj.a, 'b').mockImplementation(() => ({})).mockReturnValue('default').mockReturnValueOnce('first call'); test();",
+ },
+ {
+ code: 'window.fetch = jest.fn(() => ({})).one.two().three().four',
+ errors: [
+ {
+ message: 'Use jest.spyOn() instead.',
+ type: 'AssignmentExpression',
+ },
+ ],
+ output:
+ "jest.spyOn(window, 'fetch').mockImplementation(() => ({})).one.two().three().four",
+ },
+ ],
+});

src/rules/__tests__/prefer-strict-equal.test.js

@@ -0,0 +1,26 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-strict-equal');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('prefer-strict-equal', rule, {
+ valid: [
+ 'expect(something).toStrictEqual(somethingElse);',
+ "a().toEqual('b')",
+ ],
+ invalid: [
+ {
+ code: 'expect(something).toEqual(somethingElse);',
+ errors: [
+ {
+ message: 'Use toStrictEqual() instead',
+ column: 19,
+ line: 1,
+ },
+ ],
+ output: 'expect(something).toStrictEqual(somethingElse);',
+ },
+ ],
+});

src/rules/__tests__/prefer-to-be-null.test.js

@@ -0,0 +1,70 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-to-be-null');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('prefer-to-be-null', rule, {
+ valid: [
+ 'expect(null).toBeNull();',
+ 'expect(null).toEqual();',
+ 'expect(null).not.toBeNull();',
+ 'expect(null).not.toEqual();',
+ 'expect(null).toBe(undefined);',
+ 'expect(null).not.toBe(undefined);',
+ 'expect(null).toBe();',
+ 'expect(null).toMatchSnapshot();',
+ 'expect("a string").toMatchSnapshot(null);',
+ 'expect("a string").not.toMatchSnapshot();',
+ "expect(something).toEqual('a string');",
+ 'expect(null).toBe',
+ ],
+
+ invalid: [
+ {
+ code: 'expect(null).toBe(null);',
+ errors: [
+ {
+ message: 'Use toBeNull() instead',
+ column: 14,
+ line: 1,
+ },
+ ],
+ output: 'expect(null).toBeNull();',
+ },
+ {
+ code: 'expect(null).toEqual(null);',
+ errors: [
+ {
+ message: 'Use toBeNull() instead',
+ column: 14,
+ line: 1,
+ },
+ ],
+ output: 'expect(null).toBeNull();',
+ },
+ {
+ code: 'expect("a string").not.toBe(null);',
+ errors: [
+ {
+ message: 'Use toBeNull() instead',
+ column: 24,
+ line: 1,
+ },
+ ],
+ output: 'expect("a string").not.toBeNull();',
+ },
+ {
+ code: 'expect("a string").not.toEqual(null);',
+ errors: [
+ {
+ message: 'Use toBeNull() instead',
+ column: 24,
+ line: 1,
+ },
+ ],
+ output: 'expect("a string").not.toBeNull();',
+ },
+ ],
+});

src/rules/__tests__/prefer-to-be-undefined.test.js

@@ -0,0 +1,67 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-to-be-undefined');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('prefer-to-be-undefined', rule, {
+ valid: [
+ 'expect(undefined).toBeUndefined();',
+ 'expect(true).not.toBeUndefined();',
+ 'expect({}).toEqual({});',
+ 'expect(null).toEqual(null);',
+ 'expect(something).toBe(somethingElse)',
+ 'expect(something).toEqual(somethingElse)',
+ 'expect(something).not.toBe(somethingElse)',
+ 'expect(something).not.toEqual(somethingElse)',
+ 'expect(undefined).toBe',
+ ],
+
+ invalid: [
+ {
+ code: 'expect(undefined).toBe(undefined);',
+ errors: [
+ {
+ message: 'Use toBeUndefined() instead',
+ column: 19,
+ line: 1,
+ },
+ ],
+ output: 'expect(undefined).toBeUndefined();',
+ },
+ {
+ code: 'expect(undefined).toEqual(undefined);',
+ errors: [
+ {
+ message: 'Use toBeUndefined() instead',
+ column: 19,
+ line: 1,
+ },
+ ],
+ output: 'expect(undefined).toBeUndefined();',
+ },
+ {
+ code: 'expect("a string").not.toBe(undefined);',
+ errors: [
+ {
+ message: 'Use toBeUndefined() instead',
+ column: 24,
+ line: 1,
+ },
+ ],
+ output: 'expect("a string").not.toBeUndefined();',
+ },
+ {
+ code: 'expect("a string").not.toEqual(undefined);',
+ errors: [
+ {
+ message: 'Use toBeUndefined() instead',
+ column: 24,
+ line: 1,
+ },
+ ],
+ output: 'expect("a string").not.toBeUndefined();',
+ },
+ ],
+});

src/rules/__tests__/prefer-to-contain.test.js

@@ -0,0 +1,207 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-to-contain');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('prefer-to-contain', rule, {
+ valid: [
+ 'expect(a).toContain(b);',
+ "expect(a.name).toBe('b');",
+ 'expect(a).toBe(true);',
+ `expect(a).toEqual(b)`,
+ `expect(a.test(c)).toEqual(b)`,
+ `expect(a.includes(b)).toEqual()`,
+ `expect(a.includes(b)).toEqual("test")`,
+ `expect(a.includes(b)).toBe("test")`,
+ `expect(a.includes()).toEqual()`,
+ `expect(a.includes()).toEqual(true)`,
+ `expect(a.includes(b,c)).toBe(true)`,
+ `expect([{a:1}]).toContain({a:1})`,
+ `expect([1].includes(1)).toEqual`,
+ `expect([1].includes).toEqual`,
+ `expect([1].includes).not`,
+ `expect(a.test(b)).resolves.toEqual(true)`,
+ `expect(a.test(b)).resolves.not.toEqual(true)`,
+ `expect(a).not.toContain(b)`,
+ ],
+ invalid: [
+ {
+ code: 'expect(a.includes(b)).toEqual(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toContain(b);',
+ },
+ {
+ code: 'expect(a.includes(b)).toEqual(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).not.toContain(b);',
+ },
+ {
+ code: 'expect(a.includes(b)).not.toEqual(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toContain(b);',
+ },
+ {
+ code: 'expect(a.includes(b)).not.toEqual(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).not.toContain(b);',
+ },
+ {
+ code: 'expect(a.includes(b)).toBe(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toContain(b);',
+ },
+ {
+ code: 'expect(a.includes(b)).toBe(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).not.toContain(b);',
+ },
+ {
+ code: 'expect(a.includes(b)).not.toBe(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).toContain(b);',
+ },
+ {
+ code: 'expect(a.includes(b)).not.toBe(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 23,
+ line: 1,
+ },
+ ],
+ output: 'expect(a).not.toContain(b);',
+ },
+ {
+ code: 'expect(a.test(t).includes(b.test(p))).toEqual(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1,
+ },
+ ],
+ output: 'expect(a.test(t)).toContain(b.test(p));',
+ },
+ {
+ code: 'expect(a.test(t).includes(b.test(p))).toEqual(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1,
+ },
+ ],
+ output: 'expect(a.test(t)).not.toContain(b.test(p));',
+ },
+ {
+ code: 'expect(a.test(t).includes(b.test(p))).not.toEqual(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1,
+ },
+ ],
+ output: 'expect(a.test(t)).not.toContain(b.test(p));',
+ },
+ {
+ code: 'expect(a.test(t).includes(b.test(p))).not.toEqual(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 39,
+ line: 1,
+ },
+ ],
+ output: 'expect(a.test(t)).toContain(b.test(p));',
+ },
+ {
+ code: 'expect([{a:1}].includes({a:1})).toBe(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1,
+ },
+ ],
+ output: 'expect([{a:1}]).toContain({a:1});',
+ },
+ {
+ code: 'expect([{a:1}].includes({a:1})).toBe(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1,
+ },
+ ],
+ output: 'expect([{a:1}]).not.toContain({a:1});',
+ },
+ {
+ code: 'expect([{a:1}].includes({a:1})).not.toBe(true);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1,
+ },
+ ],
+ output: 'expect([{a:1}]).not.toContain({a:1});',
+ },
+ {
+ code: 'expect([{a:1}].includes({a:1})).not.toBe(false);',
+ errors: [
+ {
+ message: 'Use toContain() instead',
+ column: 33,
+ line: 1,
+ },
+ ],
+ output: 'expect([{a:1}]).toContain({a:1});',
+ },
+ ],
+});

src/rules/__tests__/prefer-todo.test.js

@@ -0,0 +1,59 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-todo');
+
+const ruleTester = new RuleTester({
+ parserOptions: { ecmaVersion: 2015 },
+});
+
+ruleTester.run('prefer-todo', rule, {
+ valid: [
+ 'test.todo("i need to write this test");',
+ 'test(obj)',
+ 'fit("foo")',
+ 'xit("foo")',
+ 'test("stub", () => expect(1).toBe(1));',
+ `
+ supportsDone && params.length < test.length
+ ? done => test(...params, done)
+ : () => test(...params);
+ `,
+ ],
+ invalid: [
+ {
+ code: `test("i need to write this test");`,
+ errors: [
+ { message: 'Prefer todo test case over unimplemented test case' },
+ ],
+ output: 'test.todo("i need to write this test");',
+ },
+ {
+ code: 'test(`i need to write this test`);',
+ errors: [
+ { message: 'Prefer todo test case over unimplemented test case' },
+ ],
+ output: 'test.todo(`i need to write this test`);',
+ },
+ {
+ code: 'it("foo", function () {})',
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'it.todo("foo")',
+ },
+ {
+ code: 'it("foo", () => {})',
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'it.todo("foo")',
+ },
+ {
+ code: `test.skip("i need to write this test", () => {});`,
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'test.todo("i need to write this test");',
+ },
+ {
+ code: `test.skip("i need to write this test", function() {});`,
+ errors: ['Prefer todo test case over empty test case'],
+ output: 'test.todo("i need to write this test");',
+ },
+ ],
+});

src/rules/__tests__/prefer-to-have-length.test.js

@@ -0,0 +1,41 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../prefer-to-have-length');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('prefer-to-have-length', rule, {
+ valid: [
+ 'expect(files).toHaveLength(1);',
+ "expect(files.name).toBe('file');",
+ 'expect(result).toBe(true);',
+ `expect(user.getUserName(5)).resolves.toEqual('Paul')`,
+ `expect(user.getUserName(5)).rejects.toEqual('Paul')`,
+ ],
+
+ invalid: [
+ {
+ code: 'expect(files.length).toBe(1);',
+ errors: [
+ {
+ message: 'Use toHaveLength() instead',
+ column: 22,
+ line: 1,
+ },
+ ],
+ output: 'expect(files).toHaveLength(1);',
+ },
+ {
+ code: 'expect(files.length).toEqual(1);',
+ errors: [
+ {
+ message: 'Use toHaveLength() instead',
+ column: 22,
+ line: 1,
+ },
+ ],
+ output: 'expect(files).toHaveLength(1);',
+ },
+ ],
+});

src/rules/__tests__/require-tothrow-message.test.js

@@ -0,0 +1,52 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../require-tothrow-message');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 6,
+ },
+});
+
+ruleTester.run('require-tothrow-message', rule, {
+ valid: [
+ // String
+ "expect(() => { throw new Error('a'); }).toThrow('a');",
+ "expect(() => { throw new Error('a'); }).toThrowError('a');",
+
+ // Template literal
+ "const a = 'a'; expect(() => { throw new Error('a'); }).toThrow(`${a}`);",
+
+ // Regex
+ "expect(() => { throw new Error('a'); }).toThrow(/^a$/);",
+
+ // Function
+ "expect(() => { throw new Error('a'); })" +
+ ".toThrow((() => { return 'a'; })());",
+
+ // Allow no message for `not`.
+ "expect(() => { throw new Error('a'); }).not.toThrow();",
+ ],
+
+ invalid: [
+ // Empty toThrow
+ {
+ code: "expect(() => { throw new Error('a'); }).toThrow();",
+ errors: [
+ { message: 'Add an error message to toThrow()', column: 41, line: 1 },
+ ],
+ },
+ // Empty toThrowError
+ {
+ code: "expect(() => { throw new Error('a'); }).toThrowError();",
+ errors: [
+ {
+ message: 'Add an error message to toThrowError()',
+ column: 41,
+ line: 1,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/__snapshots__/no-large-snapshots.test.js.snap

@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`no-large-snapshots ExpressionStatement function should report if maxSize is zero 1`] = `
+Array [
+ Object {
+ "data": Object {
+ "lineCount": 1,
+ "lineLimit": 0,
+ },
+ "message": "Expected to not encounter a Jest snapshot but was found with {{ lineCount }} lines long",
+ "node": Object {
+ "loc": Object {
+ "end": Object {
+ "line": 2,
+ },
+ "start": Object {
+ "line": 1,
+ },
+ },
+ },
+ },
+]
+`;
+
+exports[`no-large-snapshots ExpressionStatement function should report if node has more lines of code than number given in sizeThreshold option 1`] = `
+Array [
+ Object {
+ "data": Object {
+ "lineCount": 83,
+ "lineLimit": 70,
+ },
+ "message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
+ "node": Object {
+ "loc": Object {
+ "end": Object {
+ "line": 103,
+ },
+ "start": Object {
+ "line": 20,
+ },
+ },
+ },
+ },
+]
+`;
+
+exports[`no-large-snapshots ExpressionStatement function should report if node has more than 50 lines of code and no sizeThreshold option is passed 1`] = `
+Array [
+ Object {
+ "data": Object {
+ "lineCount": 52,
+ "lineLimit": 50,
+ },
+ "message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
+ "node": Object {
+ "loc": Object {
+ "end": Object {
+ "line": 53,
+ },
+ "start": Object {
+ "line": 1,
+ },
+ },
+ },
+ },
+]
+`;

src/rules/__tests__/valid-describe.test.js

@@ -0,0 +1,245 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../valid-describe');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 8,
+ },
+});
+
+ruleTester.run('valid-describe', rule, {
+ valid: [
+ 'describe("foo", function() {})',
+ 'describe("foo", () => {})',
+ 'describe(`foo`, () => {})',
+ 'xdescribe("foo", () => {})',
+ 'fdescribe("foo", () => {})',
+ 'describe.only("foo", () => {})',
+ 'describe.skip("foo", () => {})',
+ `
+ describe('foo', () => {
+ it('bar', () => {
+ return Promise.resolve(42).then(value => {
+ expect(value).toBe(42)
+ })
+ })
+ })
+ `,
+ `
+ describe('foo', () => {
+ it('bar', async () => {
+ expect(await Promise.resolve(42)).toBe(42)
+ })
+ })
+ `,
+ `
+ describe('foo', () =>
+ test('bar', () => {})
+ )
+ `,
+ ],
+ invalid: [
+ {
+ code: 'describe(() => {})',
+ errors: [
+ {
+ message: 'First argument must be name',
+ line: 1,
+ column: 10,
+ },
+ {
+ message: 'Describe requires name and callback arguments',
+ line: 1,
+ column: 10,
+ },
+ ],
+ },
+ {
+ code: 'describe("foo")',
+ errors: [
+ {
+ message: 'Describe requires name and callback arguments',
+ line: 1,
+ column: 10,
+ },
+ ],
+ },
+ {
+ code: 'describe("foo", "foo2")',
+ errors: [
+ {
+ message: 'Second argument must be function',
+ line: 1,
+ column: 10,
+ },
+ ],
+ },
+ {
+ code: 'describe()',
+ errors: [
+ {
+ message: 'Describe requires name and callback arguments',
+ line: 1,
+ column: 1,
+ },
+ ],
+ },
+ {
+ code: 'describe("foo", async () => {})',
+ errors: [{ message: 'No async describe callback', line: 1, column: 17 }],
+ },
+ {
+ code: 'describe("foo", async function () {})',
+ errors: [{ message: 'No async describe callback', line: 1, column: 17 }],
+ },
+ {
+ code: 'xdescribe("foo", async function () {})',
+ errors: [{ message: 'No async describe callback', line: 1, column: 18 }],
+ },
+ {
+ code: 'fdescribe("foo", async function () {})',
+ errors: [{ message: 'No async describe callback', line: 1, column: 18 }],
+ },
+ {
+ code: 'describe.only("foo", async function () {})',
+ errors: [{ message: 'No async describe callback', line: 1, column: 22 }],
+ },
+ {
+ code: 'describe.skip("foo", async function () {})',
+ errors: [{ message: 'No async describe callback', line: 1, column: 22 }],
+ },
+ {
+ code: `
+ describe('sample case', () => {
+ it('works', () => {
+ expect(true).toEqual(true);
+ });
+ describe('async', async () => {
+ await new Promise(setImmediate);
+ it('breaks', () => {
+ throw new Error('Fail');
+ });
+ });
+ });`,
+ errors: [{ message: 'No async describe callback', line: 6, column: 27 }],
+ },
+ {
+ code: `
+ describe('foo', function () {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ })
+ `,
+ errors: [
+ {
+ message: 'Unexpected return statement in describe callback',
+ line: 3,
+ column: 9,
+ },
+ ],
+ },
+ {
+ code: `
+ describe('foo', () => {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ describe('nested', () => {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ })
+ })
+ `,
+ errors: [
+ {
+ message: 'Unexpected return statement in describe callback',
+ line: 3,
+ column: 9,
+ },
+ {
+ message: 'Unexpected return statement in describe callback',
+ line: 9,
+ column: 11,
+ },
+ ],
+ },
+ {
+ code: `
+ describe('foo', async () => {
+ await something()
+ it('does something')
+ describe('nested', () => {
+ return Promise.resolve().then(() => {
+ it('breaks', () => {
+ throw new Error('Fail')
+ })
+ })
+ })
+ })
+ `,
+ errors: [
+ {
+ message: 'No async describe callback',
+ line: 2,
+ column: 23,
+ },
+ {
+ message: 'Unexpected return statement in describe callback',
+ line: 6,
+ column: 11,
+ },
+ ],
+ },
+ {
+ code: 'describe("foo", done => {})',
+ errors: [
+ {
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 17,
+ },
+ ],
+ },
+ {
+ code: 'describe("foo", function (done) {})',
+ errors: [
+ {
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 27,
+ },
+ ],
+ },
+ {
+ code: 'describe("foo", function (one, two, three) {})',
+ errors: [
+ {
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 27,
+ },
+ ],
+ },
+ {
+ code: 'describe("foo", async function (done) {})',
+ errors: [
+ { message: 'No async describe callback', line: 1, column: 17 },
+ {
+ message: 'Unexpected argument(s) in describe callback',
+ line: 1,
+ column: 33,
+ },
+ ],
+ },
+ ],
+});

src/rules/__tests__/valid-expect-in-promise.test.js

@@ -0,0 +1,401 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../valid-expect-in-promise');
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 8,
+ },
+});
+
+const expectedMsg =
+ 'Promise should be returned to test its fulfillment or rejection';
+
+ruleTester.run('valid-expect-in-promise', rule, {
+ invalid: [
+ {
+ code: `
+ it('it1', () => {
+ somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+ errors: [
+ {
+ column: 12,
+ endColumn: 15,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('it1', function() {
+ getSomeThing().getPromise().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+ errors: [
+ {
+ column: 13,
+ endColumn: 16,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('it1', function() {
+ Promise.resolve().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+ errors: [
+ {
+ column: 13,
+ endColumn: 16,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('it1', function() {
+ somePromise.catch(function() {
+ expect(someThing).toEqual(true)
+ })
+ }
+ )
+ `,
+ errors: [
+ {
+ column: 13,
+ endColumn: 15,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('it1', function() {
+ somePromise.then(function() {
+ expect(someThing).toEqual(true)
+ })
+ }
+ )
+ `,
+ errors: [
+ {
+ column: 13,
+ endColumn: 15,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('it1', function () {
+ Promise.resolve().then(/*fulfillment*/ function () {
+ expect(someThing).toEqual(true);
+ }, /*rejection*/ function () {
+ expect(someThing).toEqual(true);
+ })
+ })
+ `,
+ errors: [
+ {
+ column: 11,
+ endColumn: 13,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('it1', function () {
+ Promise.resolve().then(/*fulfillment*/ function () {
+ }, /*rejection*/ function () {
+ expect(someThing).toEqual(true)
+ })
+ });
+ `,
+ errors: [
+ {
+ column: 11,
+ endColumn: 13,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('test function', () => {
+ Builder.getPromiseBuilder().get().build().then((data) => expect(data).toEqual('Hi'));
+ });
+ `,
+ errors: [
+ {
+ column: 11,
+ endColumn: 96,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ it('it1', () => {
+ somePromise.then(() => {
+ doSomeOperation();
+ expect(someThing).toEqual(true);
+ })
+ });
+ `,
+ errors: [
+ {
+ column: 13,
+ endColumn: 15,
+ message: expectedMsg,
+ },
+ ],
+ },
+ {
+ code: `
+ test('invalid return', () => {
+ const promise = something().then(value => {
+ const foo = "foo";
+ return expect(value).toBe('red');
+ });
+ });
+ `,
+ errors: [
+ {
+ column: 18,
+ endColumn: 14,
+ message: expectedMsg,
+ },
+ ],
+ },
+ ],
+
+ valid: [
+ `
+ it('it1', () => new Promise((done) => {
+ test()
+ .then(() => {
+ expect(someThing).toEqual(true);
+ done();
+ });
+ }));
+ `,
+ `
+ it('it1', () => {
+ return somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+
+ `
+ it('it1', function() {
+ return somePromise.catch(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+
+ `
+ it('it1', function() {
+ return somePromise.then(function() {
+ doSomeThingButNotExpect();
+ });
+ });
+ `,
+
+ `
+ it('it1', function() {
+ return getSomeThing().getPromise().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+
+ `
+ it('it1', function() {
+ return Promise.resolve().then(function() {
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+
+ `
+ it('it1', function () {
+ return Promise.resolve().then(function () {
+ /*fulfillment*/
+ expect(someThing).toEqual(true);
+ }, function () {
+ /*rejection*/
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+
+ `
+ it('it1', function () {
+ return Promise.resolve().then(function () {
+ /*fulfillment*/
+ }, function () {
+ /*rejection*/
+ expect(someThing).toEqual(true);
+ });
+ });
+ `,
+
+ `
+ it('it1', function () {
+ return somePromise.then()
+ });
+ `,
+
+ `
+ it('it1', async () => {
+ await Promise.resolve().then(function () {
+ expect(someThing).toEqual(true)
+ });
+ });
+ `,
+
+ `
+ it('it1', async () => {
+ await somePromise.then(() => {
+ expect(someThing).toEqual(true)
+ });
+ });
+ `,
+
+ `
+ it('it1', async () => {
+ await getSomeThing().getPromise().then(function () {
+ expect(someThing).toEqual(true)
+ });
+ });
+ `,
+
+ `
+ it('it1', () => {
+ return somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ })
+ .then(() => {
+ expect(someThing).toEqual(true);
+ })
+ });
+ `,
+
+ `
+ it('it1', () => {
+ return somePromise.then(() => {
+ expect(someThing).toEqual(true);
+ })
+ .catch(() => {
+ expect(someThing).toEqual(false);
+ })
+ });
+ `,
+
+ `
+ test('later return', () => {
+ const promise = something().then(value => {
+ expect(value).toBe('red');
+ });
+
+ return promise;
+ });
+ `,
+
+ `
+ it('shorthand arrow', () =>
+ something().then(value => {
+ expect(() => {
+ value();
+ }).toThrow();
+ }));
+ `,
+
+ `
+ it('promise test', () => {
+ const somePromise = getThatPromise();
+ somePromise.then((data) => {
+ expect(data).toEqual('foo');
+ });
+ expect(somePromise).toBeDefined();
+ return somePromise;
+ });
+ `,
+
+ `
+ test('promise test', function () {
+ let somePromise = getThatPromise();
+ somePromise.then((data) => {
+ expect(data).toEqual('foo');
+ });
+ expect(somePromise).toBeDefined();
+ return somePromise;
+ });
+ `,
+
+ `
+ it('crawls for files based on patterns', () => {
+ const promise = nodeCrawl({}).then(data => {
+ expect(childProcess.spawn).lastCalledWith('find');
+ });
+ return promise;
+ });
+ `,
+
+ `
+ it('test function',
+ () => {
+ return Builder.getPromiseBuilder().get().build()
+ .then((data) => {
+ expect(data).toEqual('Hi');
+ });
+ });
+ `,
+
+ `
+ notATestFunction('not a test function',
+ () => {
+ Builder.getPromiseBuilder().get().build()
+ .then((data) => {
+ expect(data).toEqual('Hi');
+ });
+ });
+ `,
+
+ `
+ it("it1", () => somePromise.then(() => {
+ expect(someThing).toEqual(true)
+ }))
+ `,
+
+ ` it("it1", () => somePromise.then(() => expect(someThing).toEqual(true)))`,
+
+ `
+ it('promise test with done', (done) => {
+ const promise = getPromise();
+ promise.then(() => expect(someThing).toEqual(true));
+ });
+ `,
+
+ `
+ it('name of done param does not matter', (nameDoesNotMatter) => {
+ const promise = getPromise();
+ promise.then(() => expect(someThing).toEqual(true));
+ });
+ `,
+ ],
+});

src/rules/__tests__/valid-expect.test.js

@@ -0,0 +1,135 @@
+'use strict';
+
+const { RuleTester } = require('eslint');
+const rule = require('../valid-expect');
+
+const ruleTester = new RuleTester();
+
+ruleTester.run('valid-expect', rule, {
+ valid: [
+ 'expect("something").toEqual("else");',
+ 'expect(true).toBeDefined();',
+ 'expect([1, 2, 3]).toEqual([1, 2, 3]);',
+ 'expect(undefined).not.toBeDefined();',
+ 'expect(Promise.resolve(2)).resolves.toBeDefined();',
+ 'expect(Promise.reject(2)).rejects.toBeDefined();',
+ ],
+
+ invalid: [
+ {
+ code: 'expect().toBe(true);',
+ errors: [
+ {
+ endColumn: 8,
+ column: 7,
+ message: 'No arguments were passed to expect().',
+ },
+ ],
+ },
+ {
+ code: 'expect().toEqual("something");',
+ errors: [
+ {
+ endColumn: 8,
+ column: 7,
+ message: 'No arguments were passed to expect().',
+ },
+ ],
+ },
+ {
+ code: 'expect("something", "else").toEqual("something");',
+ errors: [
+ {
+ endColumn: 26,
+ column: 21,
+ message: 'More than one argument was passed to expect().',
+ },
+ ],
+ },
+ {
+ code: 'expect("something");',
+ errors: [
+ {
+ endColumn: 20,
+ column: 1,
+ message: 'No assertion was called on expect().',
+ },
+ ],
+ },
+ {
+ code: 'expect();',
+ errors: [
+ {
+ endColumn: 9,
+ column: 1,
+ message: 'No assertion was called on expect().',
+ },
+ {
+ endColumn: 8,
+ column: 7,
+ message: 'No arguments were passed to expect().',
+ },
+ ],
+ },
+ {
+ code: 'expect(true).toBeDefined;',
+ errors: [
+ {
+ endColumn: 25,
+ column: 14,
+ message: '"toBeDefined" was not called.',
+ },
+ ],
+ },
+ {
+ code: 'expect(true).not.toBeDefined;',
+ errors: [
+ {
+ endColumn: 29,
+ column: 18,
+ message: '"toBeDefined" was not called.',
+ },
+ ],
+ },
+ {
+ code: 'expect(true).nope.toBeDefined;',
+ errors: [
+ {
+ endColumn: 18,
+ column: 14,
+ message: '"nope" is not a valid property of expect.',
+ },
+ ],
+ },
+ {
+ code: 'expect(true).resolves;',
+ errors: [
+ {
+ endColumn: 22,
+ column: 14,
+ message: '"resolves" needs to call a matcher.',
+ },
+ ],
+ },
+ {
+ code: 'expect(true).rejects;',
+ errors: [
+ {
+ endColumn: 21,
+ column: 14,
+ message: '"rejects" needs to call a matcher.',
+ },
+ ],
+ },
+ {
+ code: 'expect(true).not;',
+ errors: [
+ {
+ endColumn: 17,
+ column: 14,
+ message: '"not" needs to call a matcher.',
+ },
+ ],
+ },
+ ],
+});

src/rules/util.js

@@ -0,0 +1,228 @@
+'use strict';
+
+const path = require('path');
+const { version } = require('../../package.json');
+
+const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
+
+const expectCase = node =>
+ node.callee.name === 'expect' &&
+ node.arguments.length === 1 &&
+ node.parent &&
+ node.parent.type === 'MemberExpression' &&
+ node.parent.parent;
+
+const expectNotCase = node =>
+ expectCase(node) &&
+ node.parent.parent.type === 'MemberExpression' &&
+ methodName(node) === 'not';
+
+const expectResolveCase = node =>
+ expectCase(node) &&
+ node.parent.parent.type === 'MemberExpression' &&
+ methodName(node) === 'resolve';
+
+const expectRejectCase = node =>
+ expectCase(node) &&
+ node.parent.parent.type === 'MemberExpression' &&
+ methodName(node) === 'reject';
+
+const expectToBeCase = (node, arg) =>
+ !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) &&
+ expectCase(node) &&
+ methodName(node) === 'toBe' &&
+ argument(node) &&
+ ((argument(node).type === 'Literal' &&
+ argument(node).value === null &&
+ arg === null) ||
+ (argument(node).name === 'undefined' && arg === undefined));
+
+const expectNotToBeCase = (node, arg) =>
+ expectNotCase(node) &&
+ methodName2(node) === 'toBe' &&
+ argument2(node) &&
+ ((argument2(node).type === 'Literal' &&
+ argument2(node).value === null &&
+ arg === null) ||
+ (argument2(node).name === 'undefined' && arg === undefined));
+
+const expectToEqualCase = (node, arg) =>
+ !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) &&
+ expectCase(node) &&
+ methodName(node) === 'toEqual' &&
+ argument(node) &&
+ ((argument(node).type === 'Literal' &&
+ argument(node).value === null &&
+ arg === null) ||
+ (argument(node).name === 'undefined' && arg === undefined));
+
+const expectNotToEqualCase = (node, arg) =>
+ expectNotCase(node) &&
+ methodName2(node) === 'toEqual' &&
+ argument2(node) &&
+ ((argument2(node).type === 'Literal' &&
+ argument2(node).value === null &&
+ arg === null) ||
+ (argument2(node).name === 'undefined' && arg === undefined));
+
+const method = node => node.parent.property;
+
+const method2 = node => node.parent.parent.property;
+
+const methodName = node => method(node).name;
+
+const methodName2 = node => method2(node).name;
+
+const argument = node =>
+ node.parent.parent.arguments && node.parent.parent.arguments[0];
+
+const argument2 = node =>
+ node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0];
+
+const describeAliases = Object.assign(Object.create(null), {
+ describe: true,
+ 'describe.only': true,
+ 'describe.skip': true,
+ fdescribe: true,
+ xdescribe: true,
+});
+
+const testCaseNames = Object.assign(Object.create(null), {
+ fit: true,
+ it: true,
+ 'it.only': true,
+ 'it.skip': true,
+ test: true,
+ 'test.only': true,
+ 'test.skip': true,
+ xit: true,
+ xtest: true,
+});
+
+const getNodeName = node => {
+ function joinNames(a, b) {
+ return a && b ? `${a}.${b}` : null;
+ }
+
+ switch (node && node.type) {
+ case 'Identifier':
+ return node.name;
+ case 'Literal':
+ return node.value;
+ case 'TemplateLiteral':
+ if (node.expressions.length === 0) return node.quasis[0].value.cooked;
+ break;
+ case 'MemberExpression':
+ return joinNames(getNodeName(node.object), getNodeName(node.property));
+ }
+
+ return null;
+};
+
+const isTestCase = node =>
+ node &&
+ node.type === 'CallExpression' &&
+ testCaseNames[getNodeName(node.callee)];
+
+const isDescribe = node =>
+ node.type === 'CallExpression' && describeAliases[getNodeName(node.callee)];
+
+const isFunction = node =>
+ node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
+
+const isString = node =>
+ (node.type === 'Literal' && typeof node.value === 'string') ||
+ isTemplateLiteral(node);
+
+const isTemplateLiteral = node => node.type === 'TemplateLiteral';
+
+const hasExpressions = node => node.expressions && node.expressions.length > 0;
+
+const getStringValue = arg =>
+ isTemplateLiteral(arg) ? arg.quasis[0].value.raw : arg.value;
+
+/**
+ * Generates the URL to documentation for the given rule name. It uses the
+ * package version to build the link to a tagged version of the
+ * documentation file.
+ *
+ * @param {string} filename - Name of the eslint rule
+ * @returns {string} URL to the documentation for the given rule
+ */
+const getDocsUrl = filename => {
+ const ruleName = path.basename(filename, '.js');
+
+ return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
+};
+
+const collectReferences = scope => {
+ const locals = new Set();
+ const unresolved = new Set();
+
+ let currentScope = scope;
+
+ while (currentScope !== null) {
+ for (const ref of currentScope.variables) {
+ const isReferenceDefined = ref.defs.some(def => {
+ return def.type !== 'ImplicitGlobalVariable';
+ });
+
+ if (isReferenceDefined) {
+ locals.add(ref.name);
+ }
+ }
+
+ for (const ref of currentScope.through) {
+ unresolved.add(ref.identifier.name);
+ }
+
+ currentScope = currentScope.upper;
+ }
+
+ return { locals, unresolved };
+};
+
+const scopeHasLocalReference = (scope, referenceName) => {
+ const references = collectReferences(scope);
+ return (
+ // referenceName was found as a local variable or function declaration.
+ references.locals.has(referenceName) ||
+ // referenceName was not found as an unresolved reference,
+ // meaning it is likely not an implicit global reference.
+ !references.unresolved.has(referenceName)
+ );
+};
+
+function composeFixers(node) {
+ return (...fixers) => {
+ return fixerApi => {
+ return fixers.reduce((all, fixer) => [...all, fixer(node, fixerApi)], []);
+ };
+ };
+}
+
+module.exports = {
+ method,
+ method2,
+ argument,
+ argument2,
+ expectCase,
+ expectNotCase,
+ expectResolveCase,
+ expectRejectCase,
+ expectToBeCase,
+ expectNotToBeCase,
+ expectToEqualCase,
+ expectNotToEqualCase,
+ getNodeName,
+ getStringValue,
+ isDescribe,
+ isFunction,
+ isTemplateLiteral,
+ isTestCase,
+ isString,
+ hasExpressions,
+ getDocsUrl,
+ scopeHasLocalReference,
+ composeFixers,
+};

src/rules/valid-describe.js

@@ -0,0 +1,91 @@
+'use strict';
+
+const { getDocsUrl, isDescribe, isFunction } = require('./util');
+
+const isAsync = node => node.async;
+
+const isString = node =>
+ (node.type === 'Literal' && typeof node.value === 'string') ||
+ node.type === 'TemplateLiteral';
+
+const hasParams = node => node.params.length > 0;
+
+const paramsLocation = params => {
+ const [first] = params;
+ const last = params[params.length - 1];
+ return {
+ start: {
+ line: first.loc.start.line,
+ column: first.loc.start.column,
+ },
+ end: {
+ line: last.loc.end.line,
+ column: last.loc.end.column,
+ },
+ };
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (isDescribe(node)) {
+ if (node.arguments.length === 0) {
+ return context.report({
+ message: 'Describe requires name and callback arguments',
+ loc: node.loc,
+ });
+ }
+
+ const [name] = node.arguments;
+ const [, callbackFunction] = node.arguments;
+ if (!isString(name)) {
+ context.report({
+ message: 'First argument must be name',
+ loc: paramsLocation(node.arguments),
+ });
+ }
+ if (callbackFunction === undefined) {
+ return context.report({
+ message: 'Describe requires name and callback arguments',
+ loc: paramsLocation(node.arguments),
+ });
+ }
+ if (!isFunction(callbackFunction)) {
+ return context.report({
+ message: 'Second argument must be function',
+ loc: paramsLocation(node.arguments),
+ });
+ }
+ if (isAsync(callbackFunction)) {
+ context.report({
+ message: 'No async describe callback',
+ node: callbackFunction,
+ });
+ }
+ if (hasParams(callbackFunction)) {
+ context.report({
+ message: 'Unexpected argument(s) in describe callback',
+ loc: paramsLocation(callbackFunction.params),
+ });
+ }
+ if (callbackFunction.body.type === 'BlockStatement') {
+ callbackFunction.body.body.forEach(node => {
+ if (node.type === 'ReturnStatement') {
+ context.report({
+ message: 'Unexpected return statement in describe callback',
+ node,
+ });
+ }
+ });
+ }
+ }
+ },
+ };
+ },
+};

src/rules/valid-expect-in-promise.js

@@ -0,0 +1,166 @@
+'use strict';
+
+const { getDocsUrl, isFunction } = require('./util');
+
+const reportMsg =
+ 'Promise should be returned to test its fulfillment or rejection';
+
+const isThenOrCatch = node => {
+ return (
+ node.property &&
+ (node.property.name === 'then' || node.property.name === 'catch')
+ );
+};
+
+const isExpectCallPresentInFunction = body => {
+ if (body.type === 'BlockStatement') {
+ return body.body.find(line => {
+ if (line.type === 'ExpressionStatement')
+ return isExpectCall(line.expression);
+ if (line.type === 'ReturnStatement') return isExpectCall(line.argument);
+ });
+ } else {
+ return isExpectCall(body);
+ }
+};
+
+const isExpectCall = expression => {
+ return (
+ expression &&
+ expression.type === 'CallExpression' &&
+ expression.callee.type === 'MemberExpression' &&
+ expression.callee.object.type === 'CallExpression' &&
+ expression.callee.object.callee.name === 'expect'
+ );
+};
+
+const reportReturnRequired = (context, node) => {
+ context.report({
+ loc: {
+ end: {
+ column: node.parent.parent.loc.end.column,
+ line: node.parent.parent.loc.end.line,
+ },
+ start: node.parent.parent.loc.start,
+ },
+ message: reportMsg,
+ node,
+ });
+};
+
+const isPromiseReturnedLater = (node, testFunctionBody) => {
+ let promiseName;
+ if (node.parent.parent.type === 'ExpressionStatement') {
+ promiseName = node.parent.parent.expression.callee.object.name;
+ } else if (node.parent.parent.type === 'VariableDeclarator') {
+ promiseName = node.parent.parent.id.name;
+ }
+ const lastLineInTestFunc = testFunctionBody[testFunctionBody.length - 1];
+ return (
+ lastLineInTestFunc.type === 'ReturnStatement' &&
+ lastLineInTestFunc.argument.name === promiseName
+ );
+};
+
+const isTestFunc = node => {
+ return (
+ node.type === 'CallExpression' &&
+ (node.callee.name === 'it' || node.callee.name === 'test')
+ );
+};
+
+const getFunctionBody = func => {
+ if (func.body.type === 'BlockStatement') return func.body.body;
+ return func.body; //arrow-short-hand-fn
+};
+
+const getTestFunction = node => {
+ let { parent } = node;
+ while (parent) {
+ if (isFunction(parent) && isTestFunc(parent.parent)) {
+ return parent;
+ }
+ parent = parent.parent;
+ }
+};
+
+const isParentThenOrPromiseReturned = (node, testFunctionBody) => {
+ return (
+ testFunctionBody.type === 'CallExpression' ||
+ testFunctionBody.type === 'NewExpression' ||
+ node.parent.parent.type === 'ReturnStatement' ||
+ isPromiseReturnedLater(node, testFunctionBody) ||
+ isThenOrCatch(node.parent.parent)
+ );
+};
+
+const verifyExpectWithReturn = (
+ promiseCallbacks,
+ node,
+ context,
+ testFunctionBody,
+) => {
+ promiseCallbacks.some(promiseCallback => {
+ if (promiseCallback && isFunction(promiseCallback)) {
+ if (
+ isExpectCallPresentInFunction(promiseCallback.body) &&
+ !isParentThenOrPromiseReturned(node, testFunctionBody)
+ ) {
+ reportReturnRequired(context, node);
+ return true;
+ }
+ }
+ });
+};
+
+const isAwaitExpression = node => {
+ return node.parent.parent && node.parent.parent.type === 'AwaitExpression';
+};
+
+const isHavingAsyncCallBackParam = testFunction => {
+ try {
+ return testFunction.params[0].type === 'Identifier';
+ } catch (e) {
+ return false;
+ }
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ MemberExpression(node) {
+ if (
+ node.type === 'MemberExpression' &&
+ isThenOrCatch(node) &&
+ node.parent.type === 'CallExpression' &&
+ !isAwaitExpression(node)
+ ) {
+ const testFunction = getTestFunction(node);
+ if (testFunction && !isHavingAsyncCallBackParam(testFunction)) {
+ const testFunctionBody = getFunctionBody(testFunction);
+ const [
+ fulfillmentCallback,
+ rejectionCallback,
+ ] = node.parent.arguments;
+
+ // then block can have two args, fulfillment & rejection
+ // then block can have one args, fulfillment
+ // catch block can have one args, rejection
+ // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
+ verifyExpectWithReturn(
+ [fulfillmentCallback, rejectionCallback],
+ node,
+ context,
+ testFunctionBody,
+ );
+ }
+ }
+ },
+ };
+ },
+};

src/rules/valid-expect.js

@@ -0,0 +1,130 @@
+'use strict';
+
+/*
+ * This implementation is ported from from eslint-plugin-jasmine.
+ * MIT license, Tom Vincent.
+ */
+
+const { getDocsUrl } = require('./util');
+
+const expectProperties = ['not', 'resolves', 'rejects'];
+
+module.exports = {
+ meta: {
+ docs: {
+ url: getDocsUrl(__filename),
+ },
+ },
+ create(context) {
+ return {
+ CallExpression(node) {
+ const calleeName = node.callee.name;
+
+ if (calleeName === 'expect') {
+ // checking "expect()" arguments
+ if (node.arguments.length > 1) {
+ const secondArgumentLocStart = node.arguments[1].loc.start;
+ const lastArgumentLocEnd =
+ node.arguments[node.arguments.length - 1].loc.end;
+
+ context.report({
+ loc: {
+ end: {
+ column: lastArgumentLocEnd.column - 1,
+ line: lastArgumentLocEnd.line,
+ },
+ start: secondArgumentLocStart,
+ },
+ message: 'More than one argument was passed to expect().',
+ node,
+ });
+ } else if (node.arguments.length === 0) {
+ const expectLength = calleeName.length;
+ context.report({
+ loc: {
+ end: {
+ column: node.loc.start.column + expectLength + 1,
+ line: node.loc.start.line,
+ },
+ start: {
+ column: node.loc.start.column + expectLength,
+ line: node.loc.start.line,
+ },
+ },
+ message: 'No arguments were passed to expect().',
+ node,
+ });
+ }
+
+ // something was called on `expect()`
+ if (
+ node.parent &&
+ node.parent.type === 'MemberExpression' &&
+ node.parent.parent
+ ) {
+ let parentNode = node.parent;
+ let parentProperty = parentNode.property;
+ let propertyName = parentProperty.name;
+ let grandParent = parentNode.parent;
+
+ // a property is accessed, get the next node
+ if (grandParent.type === 'MemberExpression') {
+ // a modifier is used, just get the next one
+ if (expectProperties.indexOf(propertyName) > -1) {
+ grandParent = grandParent.parent;
+ } else {
+ // only a few properties are allowed
+ context.report({
+ // For some reason `endColumn` isn't set in tests if `loc` is
+ // not added
+ loc: parentProperty.loc,
+ message: `"${propertyName}" is not a valid property of expect.`,
+ node: parentProperty,
+ });
+ }
+
+ // this next one should be the matcher
+ parentNode = parentNode.parent;
+ parentProperty = parentNode.property;
+ propertyName = parentProperty.name;
+ }
+
+ // matcher was not called
+ if (grandParent.type === 'ExpressionStatement') {
+ let message;
+ if (expectProperties.indexOf(propertyName) > -1) {
+ message = `"${propertyName}" needs to call a matcher.`;
+ } else {
+ message = `"${propertyName}" was not called.`;
+ }
+
+ context.report({
+ // For some reason `endColumn` isn't set in tests if `loc` is not
+ // added
+ loc: parentProperty.loc,
+ message,
+ node: parentProperty,
+ });
+ }
+ }
+ }
+ },
+
+ // nothing called on "expect()"
+ 'CallExpression:exit'(node) {
+ if (
+ node.callee.name === 'expect' &&
+ node.parent.type === 'ExpressionStatement'
+ ) {
+ context.report({
+ // For some reason `endColumn` isn't set in tests if `loc` is not
+ // added
+ loc: node.loc,
+ message: 'No assertion was called on expect().',
+ node,
+ });
+ }
+ },
+ };
+ },
+};

src/__tests__/rules.test.js

@@ -0,0 +1,31 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const { rules } = require('../');
+
+const ruleNames = Object.keys(rules);
+const numberOfRules = 32;
+
+describe('rules', () => {
+ it('should have a corresponding doc for each rule', () => {
+ ruleNames.forEach(rule => {
+ const docPath = path.resolve(__dirname, '../../docs/rules', `${rule}.md`);
+
+ if (!fs.existsSync(docPath)) {
+ throw new Error(
+ `Could not find documentation file for rule "${rule}" in path "${docPath}"`,
+ );
+ }
+ });
+ });
+
+ it('should have the correct amount of rules', () => {
+ const { length } = ruleNames;
+ if (length !== numberOfRules) {
+ throw new Error(
+ `There should be exactly ${numberOfRules} rules, but there are ${length}. If you've added a new rule, please update this number.`,
+ );
+ }
+ });
+});