eslint-plugin-jest 25.0.0-next.4 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +71 -49
  3. package/docs/rules/max-nested-describe.md +4 -5
  4. package/docs/rules/no-conditional-expect.md +57 -3
  5. package/docs/rules/no-deprecated-functions.md +5 -0
  6. package/docs/rules/no-done-callback.md +3 -3
  7. package/docs/rules/no-standalone-expect.md +3 -3
  8. package/docs/rules/no-test-return-statement.md +1 -2
  9. package/docs/rules/prefer-expect-resolves.md +53 -0
  10. package/docs/rules/{lowercase-name.md → prefer-lowercase-title.md} +1 -1
  11. package/docs/rules/prefer-to-be.md +53 -0
  12. package/docs/rules/require-hook.md +149 -0
  13. package/docs/rules/require-top-level-describe.md +28 -0
  14. package/docs/rules/{valid-describe.md → valid-describe-callback.md} +1 -1
  15. package/docs/rules/valid-expect-in-promise.md +55 -14
  16. package/docs/rules/valid-title.md +30 -2
  17. package/lib/index.js +2 -3
  18. package/lib/rules/detectJestVersion.js +29 -0
  19. package/lib/rules/no-deprecated-functions.js +9 -27
  20. package/lib/rules/no-identical-title.js +3 -3
  21. package/lib/rules/no-large-snapshots.js +1 -3
  22. package/lib/rules/prefer-expect-resolves.js +48 -0
  23. package/lib/rules/{lowercase-name.js → prefer-lowercase-title.js} +20 -1
  24. package/lib/rules/prefer-to-be.js +129 -0
  25. package/lib/rules/require-hook.js +87 -0
  26. package/lib/rules/require-top-level-describe.js +40 -6
  27. package/lib/rules/utils.js +6 -4
  28. package/lib/rules/{valid-describe.js → valid-describe-callback.js} +0 -0
  29. package/lib/rules/valid-expect-in-promise.js +252 -68
  30. package/lib/rules/valid-expect.js +10 -4
  31. package/lib/rules/valid-title.js +47 -47
  32. package/package.json +5 -5
  33. package/docs/rules/prefer-to-be-null.md +0 -33
  34. package/docs/rules/prefer-to-be-undefined.md +0 -33
  35. package/lib/rules/prefer-to-be-null.js +0 -67
  36. package/lib/rules/prefer-to-be-undefined.js +0 -67
@@ -0,0 +1,149 @@
1
+ # Require setup and teardown code to be within a hook (`require-hook`)
2
+
3
+ Often while writing tests you have some setup work that needs to happen before
4
+ tests run, and you have some finishing work that needs to happen after tests
5
+ run. Jest provides helper functions to handle this.
6
+
7
+ It's common when writing tests to need to perform setup work that needs to
8
+ happen before tests run, and finishing work after tests run.
9
+
10
+ Because Jest executes all `describe` handlers in a test file _before_ it
11
+ executes any of the actual tests, it's important to ensure setup and teardown
12
+ work is done inside `before*` and `after*` handlers respectively, rather than
13
+ inside the `describe` blocks.
14
+
15
+ ## Rule details
16
+
17
+ This rule flags any expression that is either at the toplevel of a test file or
18
+ directly within the body of a `describe`, _except_ for the following:
19
+
20
+ - `import` statements
21
+ - `const` variables
22
+ - `let` _declarations_
23
+ - Types
24
+ - Calls to the standard Jest globals
25
+
26
+ This rule flags any function calls within test files that are directly within
27
+ the body of a `describe`, and suggests wrapping them in one of the four
28
+ lifecycle hooks.
29
+
30
+ Here is a slightly contrived test file showcasing some common cases that would
31
+ be flagged:
32
+
33
+ ```js
34
+ import { database, isCity } from '../database';
35
+ import { Logger } from '../../../src/Logger';
36
+ import { loadCities } from '../api';
37
+
38
+ jest.mock('../api');
39
+
40
+ const initializeCityDatabase = () => {
41
+ database.addCity('Vienna');
42
+ database.addCity('San Juan');
43
+ database.addCity('Wellington');
44
+ };
45
+
46
+ const clearCityDatabase = () => {
47
+ database.clear();
48
+ };
49
+
50
+ initializeCityDatabase();
51
+
52
+ test('that persists cities', () => {
53
+ expect(database.cities.length).toHaveLength(3);
54
+ });
55
+
56
+ test('city database has Vienna', () => {
57
+ expect(isCity('Vienna')).toBeTruthy();
58
+ });
59
+
60
+ test('city database has San Juan', () => {
61
+ expect(isCity('San Juan')).toBeTruthy();
62
+ });
63
+
64
+ describe('when loading cities from the api', () => {
65
+ let consoleWarnSpy = jest.spyOn(console, 'warn');
66
+
67
+ loadCities.mockResolvedValue(['Wellington', 'London']);
68
+
69
+ it('does not duplicate cities', async () => {
70
+ await database.loadCities();
71
+
72
+ expect(database.cities).toHaveLength(4);
73
+ });
74
+
75
+ it('logs any duplicates', async () => {
76
+ await database.loadCities();
77
+
78
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
79
+ 'Ignored duplicate cities: Wellington',
80
+ );
81
+ });
82
+ });
83
+
84
+ clearCityDatabase();
85
+ ```
86
+
87
+ Here is the same slightly contrived test file showcasing the same common cases
88
+ but in ways that would be **not** flagged:
89
+
90
+ ```js
91
+ import { database, isCity } from '../database';
92
+ import { Logger } from '../../../src/Logger';
93
+ import { loadCities } from '../api';
94
+
95
+ jest.mock('../api');
96
+
97
+ const initializeCityDatabase = () => {
98
+ database.addCity('Vienna');
99
+ database.addCity('San Juan');
100
+ database.addCity('Wellington');
101
+ };
102
+
103
+ const clearCityDatabase = () => {
104
+ database.clear();
105
+ };
106
+
107
+ beforeEach(() => {
108
+ initializeCityDatabase();
109
+ });
110
+
111
+ test('that persists cities', () => {
112
+ expect(database.cities.length).toHaveLength(3);
113
+ });
114
+
115
+ test('city database has Vienna', () => {
116
+ expect(isCity('Vienna')).toBeTruthy();
117
+ });
118
+
119
+ test('city database has San Juan', () => {
120
+ expect(isCity('San Juan')).toBeTruthy();
121
+ });
122
+
123
+ describe('when loading cities from the api', () => {
124
+ let consoleWarnSpy;
125
+
126
+ beforeEach(() => {
127
+ consoleWarnSpy = jest.spyOn(console, 'warn');
128
+ loadCities.mockResolvedValue(['Wellington', 'London']);
129
+ });
130
+
131
+ it('does not duplicate cities', async () => {
132
+ await database.loadCities();
133
+
134
+ expect(database.cities).toHaveLength(4);
135
+ });
136
+
137
+ it('logs any duplicates', async () => {
138
+ await database.loadCities();
139
+
140
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
141
+ 'Ignored duplicate cities: Wellington',
142
+ );
143
+ });
144
+ });
145
+
146
+ afterEach(() => {
147
+ clearCityDatabase();
148
+ });
149
+ ```
@@ -47,6 +47,34 @@ describe('test suite', () => {
47
47
  });
48
48
  ```
49
49
 
50
+ You can also enforce a limit on the number of describes allowed at the top-level
51
+ using the `maxNumberOfTopLevelDescribes` option:
52
+
53
+ ```json
54
+ {
55
+ "jest/require-top-level-describe": [
56
+ "error",
57
+ {
58
+ "maxNumberOfTopLevelDescribes": 2
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ Examples of **incorrect** code with the above config:
65
+
66
+ ```js
67
+ describe('test suite', () => {
68
+ it('test', () => {});
69
+ });
70
+
71
+ describe('test suite', () => {});
72
+
73
+ describe('test suite', () => {});
74
+ ```
75
+
76
+ This option defaults to `Infinity`, allowing any number of top-level describes.
77
+
50
78
  ## When Not To Use It
51
79
 
52
80
  Don't use this rule on non-jest test files.
@@ -1,4 +1,4 @@
1
- # Enforce valid `describe()` callback (`valid-describe`)
1
+ # Enforce valid `describe()` callback (`valid-describe-callback`)
2
2
 
3
3
  Using an improper `describe()` callback function can lead to unexpected test
4
4
  errors.
@@ -1,31 +1,72 @@
1
- # Enforce having return statement when testing with promises (`valid-expect-in-promise`)
1
+ # Ensure promises that have expectations in their chain are valid (`valid-expect-in-promise`)
2
2
 
3
- Ensure to return promise when having assertions in `then` or `catch` block of
4
- promise
3
+ Ensure promises that include expectations are returned or awaited.
5
4
 
6
5
  ## Rule details
7
6
 
8
- This rule looks for tests that have assertions in `then` and `catch` methods on
9
- promises that are not returned by the test.
7
+ This rule flags any promises within the body of a test that include expectations
8
+ that have either not been returned or awaited.
10
9
 
11
- ### Default configuration
12
-
13
- The following pattern is considered warning:
10
+ The following patterns is considered warning:
14
11
 
15
12
  ```js
16
- it('promise test', () => {
17
- somePromise.then(data => {
18
- expect(data).toEqual('foo');
13
+ it('promises a person', () => {
14
+ api.getPersonByName('bob').then(person => {
15
+ expect(person).toHaveProperty('name', 'Bob');
16
+ });
17
+ });
18
+
19
+ it('promises a counted person', () => {
20
+ const promise = api.getPersonByName('bob').then(person => {
21
+ expect(person).toHaveProperty('name', 'Bob');
22
+ });
23
+
24
+ promise.then(() => {
25
+ expect(analytics.gottenPeopleCount).toBe(1);
19
26
  });
20
27
  });
28
+
29
+ it('promises multiple people', () => {
30
+ const firstPromise = api.getPersonByName('bob').then(person => {
31
+ expect(person).toHaveProperty('name', 'Bob');
32
+ });
33
+ const secondPromise = api.getPersonByName('alice').then(person => {
34
+ expect(person).toHaveProperty('name', 'Alice');
35
+ });
36
+
37
+ return Promise.any([firstPromise, secondPromise]);
38
+ });
21
39
  ```
22
40
 
23
41
  The following pattern is not warning:
24
42
 
25
43
  ```js
26
- it('promise test', () => {
27
- return somePromise.then(data => {
28
- expect(data).toEqual('foo');
44
+ it('promises a person', async () => {
45
+ await api.getPersonByName('bob').then(person => {
46
+ expect(person).toHaveProperty('name', 'Bob');
29
47
  });
30
48
  });
49
+
50
+ it('promises a counted person', () => {
51
+ let promise = api.getPersonByName('bob').then(person => {
52
+ expect(person).toHaveProperty('name', 'Bob');
53
+ });
54
+
55
+ promise = promise.then(() => {
56
+ expect(analytics.gottenPeopleCount).toBe(1);
57
+ });
58
+
59
+ return promise;
60
+ });
61
+
62
+ it('promises multiple people', () => {
63
+ const firstPromise = api.getPersonByName('bob').then(person => {
64
+ expect(person).toHaveProperty('name', 'Bob');
65
+ });
66
+ const secondPromise = api.getPersonByName('alice').then(person => {
67
+ expect(person).toHaveProperty('name', 'Alice');
68
+ });
69
+
70
+ return Promise.allSettled([firstPromise, secondPromise]);
71
+ });
31
72
  ```
@@ -198,8 +198,9 @@ describe('the proper way to handle things', () => {});
198
198
  Defaults: `{}`
199
199
 
200
200
  Allows enforcing that titles must match or must not match a given Regular
201
- Expression. An object can be provided to apply different Regular Expressions to
202
- specific Jest test function groups (`describe`, `test`, and `it`).
201
+ Expression, with an optional message. An object can be provided to apply
202
+ different Regular Expressions (with optional messages) to specific Jest test
203
+ function groups (`describe`, `test`, and `it`).
203
204
 
204
205
  Examples of **incorrect** code when using `mustMatch`:
205
206
 
@@ -226,3 +227,30 @@ describe('the tests that will be run', () => {});
226
227
  test('that the stuff works', () => {});
227
228
  xtest('that errors that thrown have messages', () => {});
228
229
  ```
230
+
231
+ Optionally you can provide a custom message to show for a particular matcher by
232
+ using a tuple at any level where you can provide a matcher:
233
+
234
+ ```js
235
+ const prefixes = ['when', 'with', 'without', 'if', 'unless', 'for'];
236
+ const prefixesList = prefixes.join(' - \n');
237
+
238
+ module.exports = {
239
+ rules: {
240
+ 'jest/valid-title': [
241
+ 'error',
242
+ {
243
+ mustNotMatch: ['\\.$', 'Titles should not end with a full-stop'],
244
+ mustMatch: {
245
+ describe: [
246
+ new RegExp(`^(?:[A-Z]|\\b(${prefixes.join('|')})\\b`, 'u').source,
247
+ `Describe titles should either start with a capital letter or one of the following prefixes: ${prefixesList}`,
248
+ ],
249
+ test: [/[^A-Z]/u.source],
250
+ it: /[^A-Z]/u.source,
251
+ },
252
+ },
253
+ ],
254
+ },
255
+ };
256
+ ```
package/lib/index.js CHANGED
@@ -25,7 +25,7 @@ const importDefault = moduleName => // eslint-disable-next-line @typescript-esli
25
25
  interopRequireDefault(require(moduleName)).default;
26
26
 
27
27
  const rulesDir = (0, _path.join)(__dirname, 'rules');
28
- const excludedFiles = ['__tests__', 'utils'];
28
+ const excludedFiles = ['__tests__', 'detectJestVersion', 'utils'];
29
29
  const rules = (0, _fs.readdirSync)(rulesDir).map(rule => (0, _path.parse)(rule).name).filter(rule => !excludedFiles.includes(rule)).reduce((acc, curr) => ({ ...acc,
30
30
  [curr]: importDefault((0, _path.join)(rulesDir, curr))
31
31
  }), {});
@@ -52,8 +52,7 @@ module.exports = {
52
52
  plugins: ['jest'],
53
53
  rules: {
54
54
  'jest/no-alias-methods': 'warn',
55
- 'jest/prefer-to-be-null': 'error',
56
- 'jest/prefer-to-be-undefined': 'error',
55
+ 'jest/prefer-to-be': 'error',
57
56
  'jest/prefer-to-contain': 'error',
58
57
  'jest/prefer-to-have-length': 'error'
59
58
  }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.detectJestVersion = void 0;
7
+ let cachedJestVersion = null;
8
+
9
+ const detectJestVersion = () => {
10
+ if (cachedJestVersion) {
11
+ return cachedJestVersion;
12
+ }
13
+
14
+ try {
15
+ const jestPath = require.resolve('jest/package.json');
16
+
17
+ const jestPackageJson = // eslint-disable-next-line @typescript-eslint/no-require-imports
18
+ require(jestPath);
19
+
20
+ if (jestPackageJson.version) {
21
+ const [majorVersion] = jestPackageJson.version.split('.');
22
+ return cachedJestVersion = parseInt(majorVersion, 10);
23
+ }
24
+ } catch {}
25
+
26
+ throw new Error('Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly');
27
+ };
28
+
29
+ exports.detectJestVersion = detectJestVersion;
@@ -3,39 +3,21 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = exports._clearCachedJestVersion = void 0;
6
+ exports.default = void 0;
7
7
 
8
8
  var _experimentalUtils = require("@typescript-eslint/experimental-utils");
9
9
 
10
- var _utils = require("./utils");
11
-
12
- let cachedJestVersion = null;
13
- /** @internal */
10
+ var _detectJestVersion = require("./detectJestVersion");
14
11
 
15
- const _clearCachedJestVersion = () => cachedJestVersion = null;
16
-
17
- exports._clearCachedJestVersion = _clearCachedJestVersion;
12
+ var _utils = require("./utils");
18
13
 
19
- const detectJestVersion = () => {
20
- if (cachedJestVersion) {
21
- return cachedJestVersion;
14
+ const parseJestVersion = rawVersion => {
15
+ if (typeof rawVersion === 'number') {
16
+ return rawVersion;
22
17
  }
23
18
 
24
- try {
25
- const jestPath = require.resolve('jest/package.json', {
26
- paths: [process.cwd()]
27
- });
28
-
29
- const jestPackageJson = // eslint-disable-next-line @typescript-eslint/no-require-imports
30
- require(jestPath);
31
-
32
- if (jestPackageJson.version) {
33
- const [majorVersion] = jestPackageJson.version.split('.');
34
- return cachedJestVersion = parseInt(majorVersion, 10);
35
- }
36
- } catch {}
37
-
38
- throw new Error('Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly');
19
+ const [majorVersion] = rawVersion.split('.');
20
+ return parseInt(majorVersion, 10);
39
21
  };
40
22
 
41
23
  var _default = (0, _utils.createRule)({
@@ -58,7 +40,7 @@ var _default = (0, _utils.createRule)({
58
40
  create(context) {
59
41
  var _context$settings, _context$settings$jes;
60
42
 
61
- const jestVersion = ((_context$settings = context.settings) === null || _context$settings === void 0 ? void 0 : (_context$settings$jes = _context$settings.jest) === null || _context$settings$jes === void 0 ? void 0 : _context$settings$jes.version) || detectJestVersion();
43
+ const jestVersion = parseJestVersion(((_context$settings = context.settings) === null || _context$settings === void 0 ? void 0 : (_context$settings$jes = _context$settings.jest) === null || _context$settings$jes === void 0 ? void 0 : _context$settings$jes.version) || (0, _detectJestVersion.detectJestVersion)());
62
44
  const deprecations = { ...(jestVersion >= 15 && {
63
45
  'jest.resetModuleRegistry': 'jest.resetModules'
64
46
  }),
@@ -5,8 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
- var _experimentalUtils = require("@typescript-eslint/experimental-utils");
9
-
10
8
  var _utils = require("./utils");
11
9
 
12
10
  const newDescribeContext = () => ({
@@ -35,13 +33,15 @@ var _default = (0, _utils.createRule)({
35
33
  const contexts = [newDescribeContext()];
36
34
  return {
37
35
  CallExpression(node) {
36
+ var _getNodeName;
37
+
38
38
  const currentLayer = contexts[contexts.length - 1];
39
39
 
40
40
  if ((0, _utils.isDescribeCall)(node)) {
41
41
  contexts.push(newDescribeContext());
42
42
  }
43
43
 
44
- if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
44
+ if ((_getNodeName = (0, _utils.getNodeName)(node.callee)) !== null && _getNodeName !== void 0 && _getNodeName.endsWith('.each')) {
45
45
  return;
46
46
  }
47
47
 
@@ -100,8 +100,6 @@ var _default = (0, _utils.createRule)({
100
100
 
101
101
  return {
102
102
  CallExpression(node) {
103
- var _matcher$node$parent;
104
-
105
103
  if (!(0, _utils.isExpectCall)(node)) {
106
104
  return;
107
105
  }
@@ -110,7 +108,7 @@ var _default = (0, _utils.createRule)({
110
108
  matcher
111
109
  } = (0, _utils.parseExpectCall)(node);
112
110
 
113
- if ((matcher === null || matcher === void 0 ? void 0 : (_matcher$node$parent = matcher.node.parent) === null || _matcher$node$parent === void 0 ? void 0 : _matcher$node$parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
111
+ if ((matcher === null || matcher === void 0 ? void 0 : matcher.node.parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
114
112
  return;
115
113
  }
116
114
 
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _experimentalUtils = require("@typescript-eslint/experimental-utils");
9
+
10
+ var _utils = require("./utils");
11
+
12
+ var _default = (0, _utils.createRule)({
13
+ name: __filename,
14
+ meta: {
15
+ docs: {
16
+ category: 'Best Practices',
17
+ description: 'Prefer `await expect(...).resolves` over `expect(await ...)` syntax',
18
+ recommended: false
19
+ },
20
+ fixable: 'code',
21
+ messages: {
22
+ expectResolves: 'Use `await expect(...).resolves instead.'
23
+ },
24
+ schema: [],
25
+ type: 'suggestion'
26
+ },
27
+ defaultOptions: [],
28
+ create: context => ({
29
+ CallExpression(node) {
30
+ const [awaitNode] = node.arguments;
31
+
32
+ if ((0, _utils.isExpectCall)(node) && (awaitNode === null || awaitNode === void 0 ? void 0 : awaitNode.type) === _experimentalUtils.AST_NODE_TYPES.AwaitExpression) {
33
+ context.report({
34
+ node: node.arguments[0],
35
+ messageId: 'expectResolves',
36
+
37
+ fix(fixer) {
38
+ return [fixer.insertTextBefore(node, 'await '), fixer.removeRange([awaitNode.range[0], awaitNode.argument.range[0]]), fixer.insertTextAfter(node, '.resolves')];
39
+ }
40
+
41
+ });
42
+ }
43
+ }
44
+
45
+ })
46
+ });
47
+
48
+ exports.default = _default;
@@ -21,6 +21,24 @@ const findNodeNameAndArgument = node => {
21
21
  return [(0, _utils.getNodeName)(node).split('.')[0], node.arguments[0]];
22
22
  };
23
23
 
24
+ const populateIgnores = ignore => {
25
+ const ignores = [];
26
+
27
+ if (ignore.includes(_utils.DescribeAlias.describe)) {
28
+ ignores.push(...Object.keys(_utils.DescribeAlias));
29
+ }
30
+
31
+ if (ignore.includes(_utils.TestCaseName.test)) {
32
+ ignores.push(...Object.keys(_utils.TestCaseName));
33
+ }
34
+
35
+ if (ignore.includes(_utils.TestCaseName.it)) {
36
+ ignores.push(...Object.keys(_utils.TestCaseName));
37
+ }
38
+
39
+ return ignores;
40
+ };
41
+
24
42
  var _default = (0, _utils.createRule)({
25
43
  name: __filename,
26
44
  meta: {
@@ -70,6 +88,7 @@ var _default = (0, _utils.createRule)({
70
88
  allowedPrefixes = [],
71
89
  ignoreTopLevelDescribe
72
90
  }]) {
91
+ const ignores = populateIgnores(ignore);
73
92
  let numberOfDescribeBlocks = 0;
74
93
  return {
75
94
  CallExpression(node) {
@@ -96,7 +115,7 @@ var _default = (0, _utils.createRule)({
96
115
 
97
116
  const firstCharacter = description.charAt(0);
98
117
 
99
- if (!firstCharacter || firstCharacter === firstCharacter.toLowerCase() || ignore.includes(name)) {
118
+ if (!firstCharacter || firstCharacter === firstCharacter.toLowerCase() || ignores.includes(name)) {
100
119
  return;
101
120
  }
102
121