-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 🎸 new no-wait-snapshot rule
✅ Closes: #214
- Loading branch information
Showing
5 changed files
with
188 additions
and
0 deletions.
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,44 @@ | ||
# Ensures no snapshot is generated inside of a `wait` call' (no-wait-for-snapshot) | ||
|
||
Ensure that no calls to `toMatchSnapshot` are made from within a `waitFor` method (or any of the other async utility methods). | ||
|
||
## Rule Details | ||
|
||
The `waitFor()` method runs in a timer loop. So it'll retry every n amount of time. | ||
If a snapshot is generated inside the wait condition, jest will generate one snapshot per loop. | ||
|
||
The problem then is the amount of loop ran until the condition is met will vary between different computers (or CI machines.) This leads to tests that will regenerate a lot of snapshots until the condition is match when devs run those tests locally updating the snapshots; e.g devs cannot run `jest -u` locally or it'll generate a lot of invalid snapshots who'll fail during CI. | ||
|
||
Note that this lint rule prevents from generating a snapshot from within any of the [async utility methods](https://testing-library.com/docs/dom-testing-library/api-async). | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
const foo = async () => { | ||
// ... | ||
await waitFor(() => expect(container).toMatchSnapshot()); | ||
// ... | ||
}; | ||
|
||
const bar = async () => { | ||
// ... | ||
await wait(() => { | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
// ... | ||
}; | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
const foo = () => { | ||
// ... | ||
expect(container).toMatchSnapshot(); | ||
// ... | ||
}; | ||
``` | ||
|
||
## Further Reading | ||
|
||
- [Async Utilities](https://testing-library.com/docs/dom-testing-library/api-async) |
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,67 @@ | ||
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; | ||
import { getDocsUrl, ASYNC_UTILS, LIBRARY_MODULES } from '../utils'; | ||
import { findClosestCallNode } from '../node-utils'; | ||
|
||
export const RULE_NAME = 'no-wait-for-snapshot'; | ||
export type MessageIds = 'noWaitForSnapshot'; | ||
type Options = []; | ||
|
||
function isWithinImportedAsyncUtil( | ||
importedAsyncUtils: string[], | ||
node: TSESTree.Node | ||
) { | ||
return importedAsyncUtils.some( | ||
asyncUtil => findClosestCallNode(node, asyncUtil) != null | ||
); | ||
} | ||
|
||
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({ | ||
name: RULE_NAME, | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: | ||
'Ensures no snapshot is generated inside of a `waitFor` call', | ||
category: 'Best Practices', | ||
recommended: 'warn', | ||
}, | ||
messages: { | ||
noWaitForSnapshot: | ||
"A snapshot can't be generated inside of a `waitFor` call", | ||
}, | ||
fixable: null, | ||
schema: [], | ||
}, | ||
defaultOptions: [], | ||
|
||
create(context) { | ||
const importedAsyncUtils: string[] = []; | ||
|
||
return { | ||
'ImportDeclaration > ImportSpecifier,ImportNamespaceSpecifier'( | ||
node: TSESTree.Node | ||
) { | ||
const parent = node.parent as TSESTree.ImportDeclaration; | ||
|
||
if (!LIBRARY_MODULES.includes(parent.source.value.toString())) return; | ||
|
||
let name; | ||
if (node.type === 'ImportSpecifier') { | ||
name = node.imported.name; | ||
} | ||
|
||
if (node.type === 'ImportNamespaceSpecifier') { | ||
name = node.local.name; | ||
} | ||
|
||
if (!ASYNC_UTILS.includes(name)) return; | ||
|
||
importedAsyncUtils.push(name); | ||
}, | ||
[`Identifier[name='toMatchSnapshot']`](node: TSESTree.Identifier) { | ||
if (isWithinImportedAsyncUtil(importedAsyncUtils, node)) | ||
context.report({ node, messageId: 'noWaitForSnapshot' }); | ||
}, | ||
}; | ||
}, | ||
}); |
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,71 @@ | ||
import { createRuleTester } from '../test-utils'; | ||
import rule, { RULE_NAME } from '../../../lib/rules/no-wait-for-snapshot'; | ||
import { ASYNC_UTILS } from '../../../lib/utils'; | ||
|
||
const ruleTester = createRuleTester(); | ||
|
||
ruleTester.run(RULE_NAME, rule, { | ||
valid: [ | ||
...ASYNC_UTILS.map(asyncUtil => ({ | ||
code: ` | ||
test('snapshot calls outside of ${asyncUtil} are valid', () => { | ||
expect(foo).toMatchSnapshot() | ||
await ${asyncUtil}(() => expect(foo).toBeDefined()) | ||
expect(foo).toMatchSnapshot() | ||
}) | ||
`, | ||
})), | ||
...ASYNC_UTILS.map(asyncUtil => ({ | ||
code: ` | ||
import { ${asyncUtil} } from '@testing-library/dom'; | ||
test('snapshot calls outside of ${asyncUtil} are valid', () => { | ||
expect(foo).toMatchSnapshot() | ||
await ${asyncUtil}(() => { | ||
expect(foo).toBeDefined() | ||
}) | ||
expect(foo).toMatchSnapshot() | ||
}) | ||
`, | ||
})), | ||
...ASYNC_UTILS.map(asyncUtil => ({ | ||
code: ` | ||
import { ${asyncUtil} } from 'a-different-library'; | ||
test('snapshot calls within ${asyncUtil} are not valid', async () => { | ||
await ${asyncUtil}(() => expect(foo).toMatchSnapshot()); | ||
}); | ||
`, | ||
})), | ||
...ASYNC_UTILS.map(asyncUtil => ({ | ||
code: ` | ||
import { ${asyncUtil} } from 'a-different-library'; | ||
test('snapshot calls within ${asyncUtil} are not valid', async () => { | ||
await ${asyncUtil}(() => { | ||
expect(foo).toMatchSnapshot() | ||
}); | ||
}); | ||
`, | ||
})), | ||
], | ||
invalid: [ | ||
...ASYNC_UTILS.map(asyncUtil => ({ | ||
code: ` | ||
import { ${asyncUtil} } from '@testing-library/dom'; | ||
test('snapshot calls within ${asyncUtil} are not valid', async () => { | ||
await ${asyncUtil}(() => expect(foo).toMatchSnapshot()); | ||
}); | ||
`, | ||
errors: [{ line: 4, messageId: 'noWaitForSnapshot' }], | ||
})), | ||
...ASYNC_UTILS.map(asyncUtil => ({ | ||
code: ` | ||
import { ${asyncUtil} } from '@testing-library/dom'; | ||
test('snapshot calls within ${asyncUtil} are not valid', async () => { | ||
await ${asyncUtil}(() => { | ||
expect(foo).toMatchSnapshot() | ||
}); | ||
}); | ||
`, | ||
errors: [{ line: 5, messageId: 'noWaitForSnapshot' }], | ||
})), | ||
], | ||
}); |