-
Notifications
You must be signed in to change notification settings - Fork 236
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: create
no-unhooked-function-calls
rule
- Loading branch information
Showing
6 changed files
with
272 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# Checks for function calls within describes that are not in a hook (`no-unhooked-function-calls`) | ||
|
||
Often while writing tests you have some setup work that needs to happen before | ||
tests run, and you have some finishing work that needs to happen after tests | ||
run. Jest provides helper functions to handle this. | ||
|
||
It's common when writing tests to need to perform setup work that needs to | ||
happen before tests run, and finishing work after tests run. | ||
|
||
Because Jest executes all `describe` handlers in a test file _before_ it | ||
executes any of the actual tests, it's important to ensure setup and teardown | ||
work is done inside `before*` and `after*` handlers respectively, rather than | ||
inside the `describe` blocks. | ||
|
||
## Rule details | ||
|
||
This rule flags any function calls within test files that are directly within | ||
the body of a `describe`, and suggests wrapping them in one of the four | ||
lifecycle hooks. | ||
|
||
The following patterns are considered warnings: | ||
|
||
```js | ||
describe('cities', () => { | ||
initializeCityDatabase(); | ||
|
||
test('city database has Vienna', () => { | ||
expect(isCity('Vienna')).toBeTruthy(); | ||
}); | ||
|
||
test('city database has San Juan', () => { | ||
expect(isCity('San Juan')).toBeTruthy(); | ||
}); | ||
|
||
clearCityDatabase(); | ||
}); | ||
``` | ||
|
||
The following patterns are **not** considered warnings: | ||
|
||
```js | ||
describe('cities', () => { | ||
beforeEach(() => { | ||
initializeCityDatabase(); | ||
}); | ||
|
||
test('city database has Vienna', () => { | ||
expect(isCity('Vienna')).toBeTruthy(); | ||
}); | ||
|
||
test('city database has San Juan', () => { | ||
expect(isCity('San Juan')).toBeTruthy(); | ||
}); | ||
|
||
afterEach(() => { | ||
clearCityDatabase(); | ||
}); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { TSESLint } from '@typescript-eslint/experimental-utils'; | ||
import dedent from 'dedent'; | ||
import resolveFrom from 'resolve-from'; | ||
import rule from '../no-unhooked-function-calls'; | ||
|
||
const ruleTester = new TSESLint.RuleTester({ | ||
parser: resolveFrom(require.resolve('eslint'), 'espree'), | ||
parserOptions: { | ||
ecmaVersion: 2017, | ||
}, | ||
}); | ||
|
||
ruleTester.run('no-unhooked-function-calls', rule, { | ||
valid: [ | ||
dedent` | ||
test('it', () => { | ||
// | ||
}); | ||
`, | ||
dedent` | ||
describe('some tests', () => { | ||
it('is true', () => { | ||
expect(true).toBe(true); | ||
}); | ||
}); | ||
`, | ||
dedent` | ||
describe('some tests', () => { | ||
it('is true', () => { | ||
expect(true).toBe(true); | ||
}); | ||
describe('more tests', () => { | ||
it('is false', () => { | ||
expect(true).toBe(false); | ||
}); | ||
}); | ||
}); | ||
`, | ||
dedent` | ||
describe('some tests', () => { | ||
let consoleLogSpy; | ||
beforeEach(() => { | ||
consoleLogSpy = jest.spyOn(console, 'log'); | ||
}); | ||
it('prints a message', () => { | ||
printMessage('hello world'); | ||
expect(consoleLogSpy).toHaveBeenCalledWith('hello world'); | ||
}); | ||
}); | ||
`, | ||
dedent` | ||
describe('some tests', () => { | ||
beforeEach(() => { | ||
setup(); | ||
}); | ||
}); | ||
`, | ||
dedent` | ||
beforeEach(() => { | ||
initializeCityDatabase(); | ||
}); | ||
afterEach(() => { | ||
clearCityDatabase(); | ||
}); | ||
test('city database has Vienna', () => { | ||
expect(isCity('Vienna')).toBeTruthy(); | ||
}); | ||
test('city database has San Juan', () => { | ||
expect(isCity('San Juan')).toBeTruthy(); | ||
}); | ||
`, | ||
dedent` | ||
describe('cities', () => { | ||
beforeEach(() => { | ||
initializeCityDatabase(); | ||
}); | ||
test('city database has Vienna', () => { | ||
expect(isCity('Vienna')).toBeTruthy(); | ||
}); | ||
test('city database has San Juan', () => { | ||
expect(isCity('San Juan')).toBeTruthy(); | ||
}); | ||
afterEach(() => { | ||
clearCityDatabase(); | ||
}); | ||
}); | ||
`, | ||
], | ||
invalid: [ | ||
{ | ||
code: dedent` | ||
describe('some tests', () => { | ||
setup(); | ||
}); | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'useHook', | ||
line: 2, | ||
column: 3, | ||
}, | ||
], | ||
}, | ||
{ | ||
code: dedent` | ||
describe('some tests', () => { | ||
setup(); | ||
it('is true', () => { | ||
expect(true).toBe(true); | ||
}); | ||
describe('more tests', () => { | ||
setup(); | ||
it('is false', () => { | ||
expect(true).toBe(false); | ||
}); | ||
}); | ||
}); | ||
`, | ||
errors: [ | ||
{ | ||
messageId: 'useHook', | ||
line: 2, | ||
column: 3, | ||
}, | ||
{ | ||
messageId: 'useHook', | ||
line: 9, | ||
column: 5, | ||
}, | ||
], | ||
}, | ||
], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; | ||
import { | ||
createRule, | ||
isDescribeCall, | ||
isFunction, | ||
isHook, | ||
isTestCaseCall, | ||
} from './utils'; | ||
|
||
export default createRule({ | ||
name: __filename, | ||
meta: { | ||
docs: { | ||
category: 'Best Practices', | ||
description: | ||
'Checks for function calls within describes that are not in a hook', | ||
recommended: false, | ||
}, | ||
messages: { | ||
useHook: 'This should be done within a hook', | ||
}, | ||
type: 'suggestion', | ||
schema: [], | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
CallExpression(node) { | ||
if (!isDescribeCall(node) || node.arguments.length < 2) { | ||
return; | ||
} | ||
|
||
const [, testFn] = node.arguments; | ||
|
||
if ( | ||
!isFunction(testFn) || | ||
testFn.body.type !== AST_NODE_TYPES.BlockStatement | ||
) { | ||
return; | ||
} | ||
|
||
for (const nod of testFn.body.body) { | ||
if ( | ||
nod.type === AST_NODE_TYPES.ExpressionStatement && | ||
nod.expression.type === AST_NODE_TYPES.CallExpression | ||
) { | ||
if ( | ||
isDescribeCall(nod.expression) || | ||
isTestCaseCall(nod.expression) || | ||
isHook(nod.expression) | ||
) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node: nod.expression, | ||
messageId: 'useHook', | ||
}); | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}); |