eslint-plugin-jest 28.11.2 → 28.13.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.
package/README.md CHANGED
@@ -363,6 +363,7 @@ Manually fixable by
363
363
  | [prefer-called-with](docs/rules/prefer-called-with.md) | Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()` | | | | |
364
364
  | [prefer-comparison-matcher](docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | | 🔧 | |
365
365
  | [prefer-each](docs/rules/prefer-each.md) | Prefer using `.each` rather than manual loops | | | | |
366
+ | [prefer-ending-with-an-expect](docs/rules/prefer-ending-with-an-expect.md) | Prefer having the last statement in a test be an assertion | | | | |
366
367
  | [prefer-equality-matcher](docs/rules/prefer-equality-matcher.md) | Suggest using the built-in equality matchers | | | | 💡 |
367
368
  | [prefer-expect-assertions](docs/rules/prefer-expect-assertions.md) | Suggest using `expect.assertions()` OR `expect.hasAssertions()` | | | | 💡 |
368
369
  | [prefer-expect-resolves](docs/rules/prefer-expect-resolves.md) | Prefer `await expect(...).resolves` over `expect(await ...)` syntax | | | 🔧 | |
@@ -0,0 +1,168 @@
1
+ # Prefer having the last statement in a test be an assertion (`prefer-ending-with-an-expect`)
2
+
3
+ <!-- end auto-generated rule header -->
4
+
5
+ Prefer ending tests with an `expect` assertion.
6
+
7
+ ## Rule details
8
+
9
+ This rule triggers when a test body does not end with an `expect` call, which
10
+ can indicate an unfinished test.
11
+
12
+ Examples of **incorrect** code for this rule:
13
+
14
+ ```js
15
+ it('lets me change the selected option', () => {
16
+ const container = render(MySelect, {
17
+ props: { options: [1, 2, 3], selected: 1 },
18
+ });
19
+
20
+ expect(container).toBeDefined();
21
+ expect(container.toHTML()).toContain('<option value="1" selected>');
22
+
23
+ container.setProp('selected', 2);
24
+ });
25
+ ```
26
+
27
+ Examples of **correct** code for this rule:
28
+
29
+ ```js
30
+ it('lets me change the selected option', () => {
31
+ const container = render(MySelect, {
32
+ props: { options: [1, 2, 3], selected: 1 },
33
+ });
34
+
35
+ expect(container).toBeDefined();
36
+ expect(container.toHTML()).toContain('<option value="1" selected>');
37
+
38
+ container.setProp('selected', 2);
39
+
40
+ expect(container.toHTML()).not.toContain('<option value="1" selected>');
41
+ expect(container.toHTML()).toContain('<option value="2" selected>');
42
+ });
43
+ ```
44
+
45
+ ## Options
46
+
47
+ ```json
48
+ {
49
+ "jest/prefer-ending-with-an-expect": [
50
+ "error",
51
+ {
52
+ "assertFunctionNames": ["expect"],
53
+ "additionalTestBlockFunctions": []
54
+ }
55
+ ]
56
+ }
57
+ ```
58
+
59
+ ### `assertFunctionNames`
60
+
61
+ This array option specifies the names of functions that should be considered to
62
+ be asserting functions. Function names can use wildcards i.e `request.*.expect`,
63
+ `request.**.expect`, `request.*.expect*`
64
+
65
+ Examples of **incorrect** code for the `{ "assertFunctionNames": ["expect"] }`
66
+ option:
67
+
68
+ ```js
69
+ /* eslint jest/prefer-ending-with-an-expect: ["error", { "assertFunctionNames": ["expect"] }] */
70
+
71
+ import { expectSaga } from 'redux-saga-test-plan';
72
+ import { addSaga } from '../src/sagas';
73
+
74
+ test('returns sum', () => {
75
+ expectSaga(addSaga, 1, 1).returns(2).run();
76
+ });
77
+ ```
78
+
79
+ Examples of **correct** code for the
80
+ `{ "assertFunctionNames": ["expect", "expectSaga"] }` option:
81
+
82
+ ```js
83
+ /* eslint jest/prefer-ending-with-an-expect: ["error", { "assertFunctionNames": ["expect", "expectSaga"] }] */
84
+
85
+ import { expectSaga } from 'redux-saga-test-plan';
86
+ import { addSaga } from '../src/sagas';
87
+
88
+ test('returns sum', () => {
89
+ expectSaga(addSaga, 1, 1).returns(2).run();
90
+ });
91
+ ```
92
+
93
+ Since the string is compiled into a regular expression, you'll need to escape
94
+ special characters such as `$` with a double backslash:
95
+
96
+ ```js
97
+ /* eslint jest/prefer-ending-with-an-expect: ["error", { "assertFunctionNames": ["expect\\$"] }] */
98
+
99
+ it('is money-like', () => {
100
+ expect$(1.0);
101
+ });
102
+ ```
103
+
104
+ Examples of **correct** code for working with the HTTP assertions library
105
+ [SuperTest](https://www.npmjs.com/package/supertest) with the
106
+ `{ "assertFunctionNames": ["expect", "request.**.expect"] }` option:
107
+
108
+ ```js
109
+ /* eslint jest/prefer-ending-with-an-expect: ["error", { "assertFunctionNames": ["expect", "request.**.expect"] }] */
110
+ const request = require('supertest');
111
+ const express = require('express');
112
+
113
+ const app = express();
114
+
115
+ describe('GET /user', function () {
116
+ it('responds with json', function (done) {
117
+ doSomething();
118
+
119
+ request(app).get('/user').expect('Content-Type', /json/).expect(200, done);
120
+ });
121
+ });
122
+ ```
123
+
124
+ ### `additionalTestBlockFunctions`
125
+
126
+ This array can be used to specify the names of functions that should also be
127
+ treated as test blocks:
128
+
129
+ ```json
130
+ {
131
+ "rules": {
132
+ "jest/prefer-ending-with-an-expect": [
133
+ "error",
134
+ { "additionalTestBlockFunctions": ["each.test"] }
135
+ ]
136
+ }
137
+ }
138
+ ```
139
+
140
+ The following is _correct_ when using the above configuration:
141
+
142
+ ```js
143
+ each([
144
+ [2, 3],
145
+ [1, 3],
146
+ ]).test(
147
+ 'the selection can change from %d to %d',
148
+ (firstSelection, secondSelection) => {
149
+ const container = render(MySelect, {
150
+ props: { options: [1, 2, 3], selected: firstSelection },
151
+ });
152
+
153
+ expect(container).toBeDefined();
154
+ expect(container.toHTML()).toContain(
155
+ `<option value="${firstSelection}" selected>`,
156
+ );
157
+
158
+ container.setProp('selected', secondSelection);
159
+
160
+ expect(container.toHTML()).not.toContain(
161
+ `<option value="${firstSelection}" selected>`,
162
+ );
163
+ expect(container.toHTML()).toContain(
164
+ `<option value="${secondSelection}" selected>`,
165
+ );
166
+ },
167
+ );
168
+ ```
@@ -13,30 +13,20 @@ var _default = exports.default = (0, _utils.createRule)({
13
13
  },
14
14
  messages: {
15
15
  missingFunction: 'Test is missing function argument',
16
- pending: 'Call to pending()',
17
- pendingSuite: 'Call to pending() within test suite',
18
- pendingTest: 'Call to pending() within test',
19
- disabledSuite: 'Disabled test suite',
20
- disabledTest: 'Disabled test'
16
+ skippedTest: 'Tests should not be skipped'
21
17
  },
22
18
  schema: [],
23
19
  type: 'suggestion'
24
20
  },
25
21
  defaultOptions: [],
26
22
  create(context) {
27
- let suiteDepth = 0;
28
- let testDepth = 0;
29
23
  return {
30
24
  CallExpression(node) {
31
25
  const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
32
26
  if (!jestFnCall) {
33
27
  return;
34
28
  }
35
- if (jestFnCall.type === 'describe') {
36
- suiteDepth++;
37
- }
38
29
  if (jestFnCall.type === 'test') {
39
- testDepth++;
40
30
  if (node.arguments.length < 2 && jestFnCall.members.every(s => (0, _utils.getAccessorValue)(s) !== 'todo')) {
41
31
  context.report({
42
32
  messageId: 'missingFunction',
@@ -48,43 +38,19 @@ var _default = exports.default = (0, _utils.createRule)({
48
38
  // the only jest functions that are with "x" are "xdescribe", "xtest", and "xit"
49
39
  jestFnCall.name.startsWith('x') || jestFnCall.members.some(s => (0, _utils.getAccessorValue)(s) === 'skip')) {
50
40
  context.report({
51
- messageId: jestFnCall.type === 'describe' ? 'disabledSuite' : 'disabledTest',
41
+ messageId: 'skippedTest',
52
42
  node
53
43
  });
54
44
  }
55
45
  },
56
- 'CallExpression:exit'(node) {
57
- const jestFnCall = (0, _utils.parseJestFnCall)(node, context);
58
- if (!jestFnCall) {
59
- return;
60
- }
61
- if (jestFnCall.type === 'describe') {
62
- suiteDepth--;
63
- }
64
- if (jestFnCall.type === 'test') {
65
- testDepth--;
66
- }
67
- },
68
46
  'CallExpression[callee.name="pending"]'(node) {
69
47
  if ((0, _utils.resolveScope)((0, _utils.getScope)(context, node), 'pending')) {
70
48
  return;
71
49
  }
72
- if (testDepth > 0) {
73
- context.report({
74
- messageId: 'pendingTest',
75
- node
76
- });
77
- } else if (suiteDepth > 0) {
78
- context.report({
79
- messageId: 'pendingSuite',
80
- node
81
- });
82
- } else {
83
- context.report({
84
- messageId: 'pending',
85
- node
86
- });
87
- }
50
+ context.report({
51
+ messageId: 'skippedTest',
52
+ node
53
+ });
88
54
  }
89
55
  };
90
56
  }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _utils = require("@typescript-eslint/utils");
8
+ var _utils2 = require("./utils");
9
+ /*
10
+ * This implementation is adapted from eslint-plugin-jasmine.
11
+ * MIT license, Remco Haszing.
12
+ */
13
+
14
+ /**
15
+ * Checks if node names returned by getNodeName matches any of the given star patterns
16
+ * Pattern examples:
17
+ * request.*.expect
18
+ * request.**.expect
19
+ * request.**.expect*
20
+ */
21
+ function matchesAssertFunctionName(nodeName, patterns) {
22
+ return patterns.some(p => new RegExp(`^${p.split('.').map(x => {
23
+ if (x === '**') {
24
+ return '[a-z\\d\\.]*';
25
+ }
26
+ return x.replace(/\*/gu, '[a-z\\d]*');
27
+ }).join('\\.')}(\\.|$)`, 'ui').test(nodeName));
28
+ }
29
+ var _default = exports.default = (0, _utils2.createRule)({
30
+ name: __filename,
31
+ meta: {
32
+ docs: {
33
+ description: 'Prefer having the last statement in a test be an assertion'
34
+ },
35
+ messages: {
36
+ mustEndWithExpect: 'Tests should end with an assertion'
37
+ },
38
+ schema: [{
39
+ type: 'object',
40
+ properties: {
41
+ assertFunctionNames: {
42
+ type: 'array',
43
+ items: {
44
+ type: 'string'
45
+ }
46
+ },
47
+ additionalTestBlockFunctions: {
48
+ type: 'array',
49
+ items: {
50
+ type: 'string'
51
+ }
52
+ }
53
+ },
54
+ additionalProperties: false
55
+ }],
56
+ type: 'suggestion'
57
+ },
58
+ defaultOptions: [{
59
+ assertFunctionNames: ['expect'],
60
+ additionalTestBlockFunctions: []
61
+ }],
62
+ create(context, [{
63
+ assertFunctionNames = ['expect'],
64
+ additionalTestBlockFunctions = []
65
+ }]) {
66
+ function getLastStatement(fn) {
67
+ if (fn.body.type === _utils.AST_NODE_TYPES.BlockStatement) {
68
+ if (fn.body.body.length === 0) {
69
+ return null;
70
+ }
71
+ const lastStatement = fn.body.body[fn.body.body.length - 1];
72
+ if (lastStatement.type === _utils.AST_NODE_TYPES.ExpressionStatement) {
73
+ return lastStatement.expression;
74
+ }
75
+ return lastStatement;
76
+ }
77
+ return fn.body;
78
+ }
79
+ return {
80
+ CallExpression(node) {
81
+ const name = (0, _utils2.getNodeName)(node.callee) ?? '';
82
+ if (!(0, _utils2.isTypeOfJestFnCall)(node, context, ['test']) && !additionalTestBlockFunctions.includes(name)) {
83
+ return;
84
+ }
85
+ if (node.arguments.length < 2 || !(0, _utils2.isFunction)(node.arguments[1])) {
86
+ return;
87
+ }
88
+ const lastStatement = getLastStatement(node.arguments[1]);
89
+ if (lastStatement?.type === _utils.AST_NODE_TYPES.CallExpression && ((0, _utils2.isTypeOfJestFnCall)(lastStatement, context, ['expect']) || matchesAssertFunctionName((0, _utils2.getNodeName)(lastStatement.callee), assertFunctionNames))) {
90
+ return;
91
+ }
92
+ context.report({
93
+ messageId: 'mustEndWithExpect',
94
+ node: node.callee
95
+ });
96
+ }
97
+ };
98
+ }
99
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jest",
3
- "version": "28.11.2",
3
+ "version": "28.13.0",
4
4
  "description": "ESLint rules for Jest",
5
5
  "keywords": [
6
6
  "eslint",