Skip to content

Commit a42d917

Browse files
anescobar1991SimenB
authored andcommitted
feat(rules): add no-large-snapshots
* feat(rules): add no-large-snapshots * chore(changelog): update * docs(README): remove rule from rule config example * feat(no-large-snapshots-rule): rename threshold option
1 parent 19aac60 commit a42d917

File tree

11 files changed

+354
-3
lines changed

11 files changed

+354
-3
lines changed

.eslintrc.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const globals = require('./index').environments.globals.globals;
4+
35
module.exports = {
46
extends: ['eslint:recommended', 'prettier'],
57
plugins: ['prettier'],
@@ -13,4 +15,10 @@ module.exports = {
1315
strict: 'error',
1416
'prettier/prettier': 'error',
1517
},
18+
overrides: [
19+
{
20+
files: ['*.test.js'],
21+
globals,
22+
},
23+
],
1624
};

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ project adheres to [Semantic Versioning](http://semver.org/).
1313

1414
### Added
1515

16+
* Add `no-large-snapshots` rule
17+
([#24](https://github.com/jest-community/eslint-plugin-jest/pull/24))
1618
* Add `prefer-to-be-null` rule
1719
([#10](https://github.com/jest-community/eslint-plugin-jest/pull/10))
1820
* Add `prefer-to-be-undefined` rule

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ config file:
7272
}
7373
```
7474

75-
See [ESLint
76-
documentation](http://eslint.org/docs/user-guide/configuring#extending-configuration-files)
75+
See
76+
[ESLint documentation](http://eslint.org/docs/user-guide/configuring#extending-configuration-files)
7777
for more information about extending configuration files.
7878

7979
## Rules
@@ -83,6 +83,7 @@ for more information about extending configuration files.
8383
| [no-disabled-tests](docs/rules/no-disabled-tests.md) | Disallow disabled tests | ![recommended](https://img.shields.io/badge/-recommended-lightgrey.svg) | |
8484
| [no-focused-tests](docs/rules/no-focused-tests.md) | Disallow focused tests | ![recommended](https://img.shields.io/badge/-recommended-lightgrey.svg) | |
8585
| [no-identical-title](docs/rules/no-unhandled-errors.md) | Disallow identical titles | ![recommended](https://img.shields.io/badge/-recommended-lightgrey.svg) | |
86+
| [no-large-snapshots](docs/rules/no-large-snapshots.md) | Disallow large snapshots | | |
8687
| [prefer-to-have-length](docs/rules/prefer-to-have-length.md) | Suggest using toHaveLength() | ![recommended](https://img.shields.io/badge/-recommended-lightgrey.svg) | ![fixable](https://img.shields.io/badge/-fixable-green.svg) |
8788
| [prefer-to-be-null](docs/rules/prefer-to-be-null.md) | Suggest using toBeNull() | | ![fixable](https://img.shields.io/badge/-fixable-green.svg) |
8889
| [prefer-to-be-undefined](docs/rules/prefer-to-be-undefined.md) | Suggest using toBeUndefined() | | ![fixable](https://img.shields.io/badge/-fixable-green.svg) |

docs/rules/no-large-snapshots.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# disallow large snapshots (no-large-snapshots)
2+
3+
When using Jest's snapshot capability one should be mindful of the size of
4+
created snapshots. As a best practice snapshots should be limited in size in
5+
order to be more manageable and reviewable. A stored snapshot is only as good as
6+
its review and as such keeping it short, sweet, and readable is important to
7+
allow for thorough reviews.
8+
9+
## Usage
10+
11+
Because Jest snapshots are written with back-ticks (\` \`) which are only valid
12+
with
13+
[ES2015 onwards](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)
14+
you should set `parserOptions` in your config to at least allow ES2015 in order
15+
to use this rule:
16+
17+
```js
18+
parserOptions: {
19+
ecmaVersion: 2015,
20+
},
21+
```
22+
23+
## Rule Details
24+
25+
This rule looks at all Jest snapshot files (files with `.snap` extension) and
26+
validates that each stored snapshot within those files does not exceed 50 lines
27+
(by default, this is configurable as explained in `Options` section below).
28+
29+
Example of **incorrect** code for this rule:
30+
31+
```js
32+
exports[`a large snapshot 1`] = `
33+
line 1
34+
line 2
35+
line 3
36+
line 4
37+
line 5
38+
line 6
39+
line 7
40+
line 8
41+
line 9
42+
line 10
43+
line 11
44+
line 12
45+
line 13
46+
line 14
47+
line 15
48+
line 16
49+
line 17
50+
line 18
51+
line 19
52+
line 20
53+
line 21
54+
line 22
55+
line 23
56+
line 24
57+
line 25
58+
line 26
59+
line 27
60+
line 28
61+
line 29
62+
line 30
63+
line 31
64+
line 32
65+
line 33
66+
line 34
67+
line 35
68+
line 36
69+
line 37
70+
line 38
71+
line 39
72+
line 40
73+
line 41
74+
line 42
75+
line 43
76+
line 44
77+
line 45
78+
line 46
79+
line 47
80+
line 48
81+
line 49
82+
line 50
83+
line 51
84+
`;
85+
```
86+
87+
Example of **correct** code for this rule:
88+
89+
```js
90+
exports[`a more manageable and readable snapshot 1`] = `
91+
line 1
92+
line 2
93+
line 3
94+
line 4
95+
`;
96+
```
97+
98+
## Options
99+
100+
This rule has option for modifying the max number of lines allowed for a
101+
snapshot:
102+
103+
In an `eslintrc` file:
104+
105+
```json
106+
...
107+
"rules": {
108+
"jest/no-large-snapshots": ["warn", { "maxSize": 12 }]
109+
}
110+
...
111+
```

index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
const noDisabledTests = require('./rules/no_disabled_tests');
44
const noFocusedTests = require('./rules/no_focused_tests');
55
const noIdenticalTitle = require('./rules/no_identical_title');
6+
const noLargeSnapshots = require('./rules/no_large_snapshots');
67
const preferToBeNull = require('./rules/prefer_to_be_null');
78
const preferToBeUndefined = require('./rules/prefer_to_be_undefined');
89
const preferToHaveLength = require('./rules/prefer_to_have_length');
910
const validExpect = require('./rules/valid_expect');
1011

12+
const snapshotProcessor = require('./processors/snapshot-processor');
13+
1114
module.exports = {
1215
configs: {
1316
recommended: {
@@ -43,10 +46,14 @@ module.exports = {
4346
},
4447
},
4548
},
49+
processors: {
50+
'.snap': snapshotProcessor,
51+
},
4652
rules: {
4753
'no-disabled-tests': noDisabledTests,
4854
'no-focused-tests': noFocusedTests,
4955
'no-identical-title': noIdenticalTitle,
56+
'no-large-snapshots': noLargeSnapshots,
5057
'prefer-to-be-null': preferToBeNull,
5158
'prefer-to-be-undefined': preferToBeUndefined,
5259
'prefer-to-have-length': preferToHaveLength,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"email": "[email protected]",
1111
"url": "jkimbo.com"
1212
},
13-
"files": ["docs/", "rules/", "index.js"],
13+
"files": ["docs/", "rules/", "processors/", "index.js"],
1414
"peerDependencies": {
1515
"eslint": ">=3.6"
1616
},
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
const snapshotProcessor = require('../snapshot-processor');
4+
5+
describe('snapshot-processor', () => {
6+
it('exports an object with preprocess and postprocess functions', () => {
7+
expect(snapshotProcessor).toMatchObject({
8+
preprocess: expect.any(Function),
9+
postprocess: expect.any(Function),
10+
});
11+
});
12+
13+
describe('preprocess function', () => {
14+
it('should pass on untouched source code to source array', () => {
15+
const preprocess = snapshotProcessor.preprocess;
16+
const sourceCode = "const name = 'johnny bravo';";
17+
const result = preprocess(sourceCode);
18+
19+
expect(result).toEqual([sourceCode]);
20+
});
21+
});
22+
23+
describe('postprocess function', () => {
24+
it('should only return messages about snapshot specific rules', () => {
25+
const postprocess = snapshotProcessor.postprocess;
26+
const result = postprocess([
27+
[
28+
{ ruleId: 'no-console' },
29+
{ ruleId: 'global-require' },
30+
{ ruleId: 'jest/no-large-snapshots' },
31+
],
32+
]);
33+
34+
expect(result).toEqual([{ ruleId: 'jest/no-large-snapshots' }]);
35+
});
36+
});
37+
});

processors/snapshot-processor.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
// https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
4+
const preprocess = source => [source];
5+
6+
const postprocess = messages =>
7+
messages[0].filter(
8+
// snapshot files should only be linted with snapshot specific rules
9+
message => message.ruleId === 'jest/no-large-snapshots'
10+
);
11+
12+
module.exports = {
13+
preprocess,
14+
postprocess,
15+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`no-large-snapshots ExpressionStatement function should report if node has more lines of code than number given in sizeThreshold option 1`] = `
4+
Array [
5+
Object {
6+
"data": Object {
7+
"lineCount": 83,
8+
"lineLimit": 70,
9+
},
10+
"message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
11+
"node": Object {
12+
"loc": Object {
13+
"end": Object {
14+
"line": 103,
15+
},
16+
"start": Object {
17+
"line": 20,
18+
},
19+
},
20+
},
21+
},
22+
]
23+
`;
24+
25+
exports[`no-large-snapshots ExpressionStatement function should report if node has more than 50 lines of code and no sizeThreshold option is passed 1`] = `
26+
Array [
27+
Object {
28+
"data": Object {
29+
"lineCount": 52,
30+
"lineLimit": 50,
31+
},
32+
"message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
33+
"node": Object {
34+
"loc": Object {
35+
"end": Object {
36+
"line": 53,
37+
},
38+
"start": Object {
39+
"line": 1,
40+
},
41+
},
42+
},
43+
},
44+
]
45+
`;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use strict';
2+
3+
const noLargeSnapshots = require('../no_large_snapshots');
4+
5+
// was not able to use https://eslint.org/docs/developer-guide/nodejs-api#ruletester for these because there is no way to configure RuleTester to run non .js files
6+
describe('no-large-snapshots', () => {
7+
it('should return an empty object for non snapshot files', () => {
8+
const mockContext = {
9+
getFilename: () => 'mock-component.jsx',
10+
options: [],
11+
};
12+
const result = noLargeSnapshots(mockContext);
13+
14+
expect(result).toEqual({});
15+
});
16+
17+
it('should return an object with an ExpressionStatement function for snapshot files', () => {
18+
const mockContext = {
19+
getFilename: () => 'mock-component.jsx.snap',
20+
options: [],
21+
};
22+
23+
const result = noLargeSnapshots(mockContext);
24+
25+
expect(result).toMatchObject({
26+
ExpressionStatement: expect.any(Function),
27+
});
28+
});
29+
30+
describe('ExpressionStatement function', () => {
31+
it('should report if node has more than 50 lines of code and no sizeThreshold option is passed', () => {
32+
const mockReport = jest.fn();
33+
const mockContext = {
34+
getFilename: () => 'mock-component.jsx.snap',
35+
options: [],
36+
report: mockReport,
37+
};
38+
const mockNode = {
39+
loc: {
40+
start: {
41+
line: 1,
42+
},
43+
end: {
44+
line: 53,
45+
},
46+
},
47+
};
48+
noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
49+
50+
expect(mockReport).toHaveBeenCalledTimes(1);
51+
expect(mockReport.mock.calls[0]).toMatchSnapshot();
52+
});
53+
54+
it('should report if node has more lines of code than number given in sizeThreshold option', () => {
55+
const mockReport = jest.fn();
56+
const mockContext = {
57+
getFilename: () => 'mock-component.jsx.snap',
58+
options: [{ maxSize: 70 }],
59+
report: mockReport,
60+
};
61+
const mockNode = {
62+
loc: {
63+
start: {
64+
line: 20,
65+
},
66+
end: {
67+
line: 103,
68+
},
69+
},
70+
};
71+
noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
72+
73+
expect(mockReport).toHaveBeenCalledTimes(1);
74+
expect(mockReport.mock.calls[0]).toMatchSnapshot();
75+
});
76+
77+
it('should not report if node has fewer lines of code than limit', () => {
78+
const mockReport = jest.fn();
79+
const mockContext = {
80+
getFilename: () => 'mock-component.jsx.snap',
81+
options: [],
82+
report: mockReport,
83+
};
84+
const mockNode = {
85+
loc: {
86+
start: {
87+
line: 1,
88+
},
89+
end: {
90+
line: 18,
91+
},
92+
},
93+
};
94+
noLargeSnapshots(mockContext).ExpressionStatement(mockNode);
95+
96+
expect(mockReport).not.toHaveBeenCalled();
97+
});
98+
});
99+
});

0 commit comments

Comments
 (0)