Skip to content

Commit c4f907b

Browse files
committed
feat(no-standalone-expect): support additionalTestBlockFunctions
1 parent 325ec77 commit c4f907b

File tree

3 files changed

+134
-6
lines changed

3 files changed

+134
-6
lines changed

docs/rules/no-standalone-expect.md

+30
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,36 @@ describe('a test', () => {
6464
thought the `expect` will not execute. Rely on a rule like no-unused-vars for
6565
this case.
6666

67+
### Options
68+
69+
#### `additionalTestBlockFunctions`
70+
71+
This array can be used to specify the names of functions that should also be
72+
treated as test blocks:
73+
74+
```json
75+
{
76+
"rules": {
77+
"jest/no-standalone-expect": [
78+
"error",
79+
{ "additionalTestBlockFunctions": ["each.test"] }
80+
]
81+
}
82+
}
83+
```
84+
85+
The following is _correct_ when using the above configuration:
86+
87+
```js
88+
each([
89+
[1, 1, 2],
90+
[1, 2, 3],
91+
[2, 1, 3],
92+
]).test('returns the result of adding %d to %d', (a, b, expected) => {
93+
expect(a + b).toBe(expected);
94+
});
95+
```
96+
6797
## When Not To Use It
6898

6999
Don't use this rule on non-jest test files.

src/rules/__tests__/no-standalone-expect.test.ts

+78
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,86 @@ ruleTester.run('no-standalone-expect', rule, {
3434
'it.only("an only", value => { expect(value).toBe(true); });',
3535
'it.concurrent("an concurrent", value => { expect(value).toBe(true); });',
3636
'describe.each([1, true])("trues", value => { it("an it", () => expect(value).toBe(true) ); });',
37+
{
38+
code: `
39+
describe('scenario', () => {
40+
const t = Math.random() ? it.only : it;
41+
t('testing', () => expect(true));
42+
});
43+
`,
44+
options: [{ additionalTestBlockFunctions: ['t'] }],
45+
},
46+
{
47+
code: `
48+
each([
49+
[1, 1, 2],
50+
[1, 2, 3],
51+
[2, 1, 3],
52+
]).test('returns the result of adding %d to %d', (a, b, expected) => {
53+
expect(a + b).toBe(expected);
54+
});
55+
`,
56+
options: [{ additionalTestBlockFunctions: ['each.test'] }],
57+
},
3758
],
3859
invalid: [
60+
{
61+
code: `
62+
describe('scenario', () => {
63+
const t = Math.random() ? it.only : it;
64+
t('testing', () => expect(true));
65+
});
66+
`,
67+
errors: [{ endColumn: 42, column: 30, messageId: 'unexpectedExpect' }],
68+
},
69+
{
70+
code: `
71+
describe('scenario', () => {
72+
const t = Math.random() ? it.only : it;
73+
t('testing', () => expect(true));
74+
});
75+
`,
76+
options: [{ additionalTestBlockFunctions: undefined }],
77+
errors: [{ endColumn: 42, column: 30, messageId: 'unexpectedExpect' }],
78+
},
79+
{
80+
code: `
81+
each([
82+
[1, 1, 2],
83+
[1, 2, 3],
84+
[2, 1, 3],
85+
]).test('returns the result of adding %d to %d', (a, b, expected) => {
86+
expect(a + b).toBe(expected);
87+
});
88+
`,
89+
errors: [{ endColumn: 24, column: 11, messageId: 'unexpectedExpect' }],
90+
},
91+
{
92+
code: `
93+
each([
94+
[1, 1, 2],
95+
[1, 2, 3],
96+
[2, 1, 3],
97+
]).test('returns the result of adding %d to %d', (a, b, expected) => {
98+
expect(a + b).toBe(expected);
99+
});
100+
`,
101+
options: [{ additionalTestBlockFunctions: ['each'] }],
102+
errors: [{ endColumn: 24, column: 11, messageId: 'unexpectedExpect' }],
103+
},
104+
{
105+
code: `
106+
each([
107+
[1, 1, 2],
108+
[1, 2, 3],
109+
[2, 1, 3],
110+
]).test('returns the result of adding %d to %d', (a, b, expected) => {
111+
expect(a + b).toBe(expected);
112+
});
113+
`,
114+
options: [{ additionalTestBlockFunctions: ['test'] }],
115+
errors: [{ endColumn: 24, column: 11, messageId: 'unexpectedExpect' }],
116+
},
39117
{
40118
code: 'describe("a test", () => { expect(1).toBe(1); });',
41119
errors: [{ endColumn: 37, column: 28, messageId: 'unexpectedExpect' }],

src/rules/no-standalone-expect.ts

+26-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
DescribeAlias,
77
TestCaseName,
88
createRule,
9+
getNodeName,
910
isDescribe,
1011
isExpectCall,
1112
isFunction,
@@ -56,7 +57,10 @@ type callStackEntry =
5657
| 'arrowFunc'
5758
| 'template';
5859

59-
export default createRule({
60+
export default createRule<
61+
[{ additionalTestBlockFunctions: string[] }],
62+
'unexpectedExpect'
63+
>({
6064
name: __filename,
6165
meta: {
6266
docs: {
@@ -68,12 +72,27 @@ export default createRule({
6872
unexpectedExpect: 'Expect must be inside of a test block.',
6973
},
7074
type: 'suggestion',
71-
schema: [],
75+
schema: [
76+
{
77+
properties: {
78+
additionalTestBlockFunctions: {
79+
type: 'array',
80+
items: { type: 'string' },
81+
},
82+
},
83+
additionalProperties: false,
84+
},
85+
],
7286
},
73-
defaultOptions: [],
74-
create(context) {
87+
defaultOptions: [{ additionalTestBlockFunctions: [] }],
88+
create(context, [{ additionalTestBlockFunctions = [] }]) {
7589
const callStack: callStackEntry[] = [];
7690

91+
const isCustomTestBlockFunction = (
92+
node: TSESTree.CallExpression,
93+
): boolean =>
94+
additionalTestBlockFunctions.includes(getNodeName(node) || '');
95+
7796
return {
7897
CallExpression(node) {
7998
if (isExpectCall(node)) {
@@ -83,7 +102,7 @@ export default createRule({
83102
}
84103
return;
85104
}
86-
if (isTestCase(node)) {
105+
if (isTestCase(node) || isCustomTestBlockFunction(node)) {
87106
callStack.push(TestCaseName.test);
88107
}
89108
if (node.callee.type === AST_NODE_TYPES.TaggedTemplateExpression) {
@@ -92,8 +111,9 @@ export default createRule({
92111
},
93112
'CallExpression:exit'(node: TSESTree.CallExpression) {
94113
const top = callStack[callStack.length - 1];
114+
95115
if (
96-
(((isTestCase(node) &&
116+
((((isTestCase(node) || isCustomTestBlockFunction(node)) &&
97117
node.callee.type !== AST_NODE_TYPES.MemberExpression) ||
98118
isEach(node)) &&
99119
top === TestCaseName.test) ||

0 commit comments

Comments
 (0)