Skip to content

Commit

Permalink
feat(rules): add no-large-snapshots
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
anescobar1991 authored and SimenB committed Dec 2, 2017
1 parent 19aac60 commit a42d917
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 3 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const globals = require('./index').environments.globals.globals;

module.exports = {
extends: ['eslint:recommended', 'prettier'],
plugins: ['prettier'],
Expand All @@ -13,4 +15,10 @@ module.exports = {
strict: 'error',
'prettier/prettier': 'error',
},
overrides: [
{
files: ['*.test.js'],
globals,
},
],
};
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ project adheres to [Semantic Versioning](http://semver.org/).

### Added

* Add `no-large-snapshots` rule
([#24](https://github.com/jest-community/eslint-plugin-jest/pull/24))
* Add `prefer-to-be-null` rule
([#10](https://github.com/jest-community/eslint-plugin-jest/pull/10))
* Add `prefer-to-be-undefined` rule
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ config file:
}
```

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

## Rules
Expand All @@ -83,6 +83,7 @@ for more information about extending configuration files.
| [no-disabled-tests](docs/rules/no-disabled-tests.md) | Disallow disabled tests | ![recommended](https://img.shields.io/badge/-recommended-lightgrey.svg) | |
| [no-focused-tests](docs/rules/no-focused-tests.md) | Disallow focused tests | ![recommended](https://img.shields.io/badge/-recommended-lightgrey.svg) | |
| [no-identical-title](docs/rules/no-unhandled-errors.md) | Disallow identical titles | ![recommended](https://img.shields.io/badge/-recommended-lightgrey.svg) | |
| [no-large-snapshots](docs/rules/no-large-snapshots.md) | Disallow large snapshots | | |
| [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) |
| [prefer-to-be-null](docs/rules/prefer-to-be-null.md) | Suggest using toBeNull() | | ![fixable](https://img.shields.io/badge/-fixable-green.svg) |
| [prefer-to-be-undefined](docs/rules/prefer-to-be-undefined.md) | Suggest using toBeUndefined() | | ![fixable](https://img.shields.io/badge/-fixable-green.svg) |
Expand Down
111 changes: 111 additions & 0 deletions docs/rules/no-large-snapshots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# disallow large snapshots (no-large-snapshots)

When using Jest's snapshot capability one should be mindful of the size of
created snapshots. As a best practice snapshots should be limited in size in
order to be more manageable and reviewable. A stored snapshot is only as good as
its review and as such keeping it short, sweet, and readable is important to
allow for thorough reviews.

## Usage

Because Jest snapshots are written with back-ticks (\` \`) which are only valid
with
[ES2015 onwards](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)
you should set `parserOptions` in your config to at least allow ES2015 in order
to use this rule:

```js
parserOptions: {
ecmaVersion: 2015,
},
```

## Rule Details

This rule looks at all Jest snapshot files (files with `.snap` extension) and
validates that each stored snapshot within those files does not exceed 50 lines
(by default, this is configurable as explained in `Options` section below).

Example of **incorrect** code for this rule:

```js
exports[`a large snapshot 1`] = `
line 1
line 2
line 3
line 4
line 5
line 6
line 7
line 8
line 9
line 10
line 11
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
line 21
line 22
line 23
line 24
line 25
line 26
line 27
line 28
line 29
line 30
line 31
line 32
line 33
line 34
line 35
line 36
line 37
line 38
line 39
line 40
line 41
line 42
line 43
line 44
line 45
line 46
line 47
line 48
line 49
line 50
line 51
`;
```

Example of **correct** code for this rule:

```js
exports[`a more manageable and readable snapshot 1`] = `
line 1
line 2
line 3
line 4
`;
```

## Options

This rule has option for modifying the max number of lines allowed for a
snapshot:

In an `eslintrc` file:

```json
...
"rules": {
"jest/no-large-snapshots": ["warn", { "maxSize": 12 }]
}
...
```
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
const noDisabledTests = require('./rules/no_disabled_tests');
const noFocusedTests = require('./rules/no_focused_tests');
const noIdenticalTitle = require('./rules/no_identical_title');
const noLargeSnapshots = require('./rules/no_large_snapshots');
const preferToBeNull = require('./rules/prefer_to_be_null');
const preferToBeUndefined = require('./rules/prefer_to_be_undefined');
const preferToHaveLength = require('./rules/prefer_to_have_length');
const validExpect = require('./rules/valid_expect');

const snapshotProcessor = require('./processors/snapshot-processor');

module.exports = {
configs: {
recommended: {
Expand Down Expand Up @@ -43,10 +46,14 @@ module.exports = {
},
},
},
processors: {
'.snap': snapshotProcessor,
},
rules: {
'no-disabled-tests': noDisabledTests,
'no-focused-tests': noFocusedTests,
'no-identical-title': noIdenticalTitle,
'no-large-snapshots': noLargeSnapshots,
'prefer-to-be-null': preferToBeNull,
'prefer-to-be-undefined': preferToBeUndefined,
'prefer-to-have-length': preferToHaveLength,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"email": "[email protected]",
"url": "jkimbo.com"
},
"files": ["docs/", "rules/", "index.js"],
"files": ["docs/", "rules/", "processors/", "index.js"],
"peerDependencies": {
"eslint": ">=3.6"
},
Expand Down
37 changes: 37 additions & 0 deletions processors/__tests__/snapshot-processor.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

const snapshotProcessor = require('../snapshot-processor');

describe('snapshot-processor', () => {
it('exports an object with preprocess and postprocess functions', () => {
expect(snapshotProcessor).toMatchObject({
preprocess: expect.any(Function),
postprocess: expect.any(Function),
});
});

describe('preprocess function', () => {
it('should pass on untouched source code to source array', () => {
const preprocess = snapshotProcessor.preprocess;
const sourceCode = "const name = 'johnny bravo';";
const result = preprocess(sourceCode);

expect(result).toEqual([sourceCode]);
});
});

describe('postprocess function', () => {
it('should only return messages about snapshot specific rules', () => {
const postprocess = snapshotProcessor.postprocess;
const result = postprocess([
[
{ ruleId: 'no-console' },
{ ruleId: 'global-require' },
{ ruleId: 'jest/no-large-snapshots' },
],
]);

expect(result).toEqual([{ ruleId: 'jest/no-large-snapshots' }]);
});
});
});
15 changes: 15 additions & 0 deletions processors/snapshot-processor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

// https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
const preprocess = source => [source];

const postprocess = messages =>
messages[0].filter(
// snapshot files should only be linted with snapshot specific rules
message => message.ruleId === 'jest/no-large-snapshots'
);

module.exports = {
preprocess,
postprocess,
};
45 changes: 45 additions & 0 deletions rules/__tests__/__snapshots__/no_large_snapshots.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`no-large-snapshots ExpressionStatement function should report if node has more lines of code than number given in sizeThreshold option 1`] = `
Array [
Object {
"data": Object {
"lineCount": 83,
"lineLimit": 70,
},
"message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
"node": Object {
"loc": Object {
"end": Object {
"line": 103,
},
"start": Object {
"line": 20,
},
},
},
},
]
`;

exports[`no-large-snapshots ExpressionStatement function should report if node has more than 50 lines of code and no sizeThreshold option is passed 1`] = `
Array [
Object {
"data": Object {
"lineCount": 52,
"lineLimit": 50,
},
"message": "Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long",
"node": Object {
"loc": Object {
"end": Object {
"line": 53,
},
"start": Object {
"line": 1,
},
},
},
},
]
`;
99 changes: 99 additions & 0 deletions rules/__tests__/no_large_snapshots.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict';

const noLargeSnapshots = require('../no_large_snapshots');

// 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
describe('no-large-snapshots', () => {
it('should return an empty object for non snapshot files', () => {
const mockContext = {
getFilename: () => 'mock-component.jsx',
options: [],
};
const result = noLargeSnapshots(mockContext);

expect(result).toEqual({});
});

it('should return an object with an ExpressionStatement function for snapshot files', () => {
const mockContext = {
getFilename: () => 'mock-component.jsx.snap',
options: [],
};

const result = noLargeSnapshots(mockContext);

expect(result).toMatchObject({
ExpressionStatement: expect.any(Function),
});
});

describe('ExpressionStatement function', () => {
it('should report if node has more than 50 lines of code and no sizeThreshold option is passed', () => {
const mockReport = jest.fn();
const mockContext = {
getFilename: () => 'mock-component.jsx.snap',
options: [],
report: mockReport,
};
const mockNode = {
loc: {
start: {
line: 1,
},
end: {
line: 53,
},
},
};
noLargeSnapshots(mockContext).ExpressionStatement(mockNode);

expect(mockReport).toHaveBeenCalledTimes(1);
expect(mockReport.mock.calls[0]).toMatchSnapshot();
});

it('should report if node has more lines of code than number given in sizeThreshold option', () => {
const mockReport = jest.fn();
const mockContext = {
getFilename: () => 'mock-component.jsx.snap',
options: [{ maxSize: 70 }],
report: mockReport,
};
const mockNode = {
loc: {
start: {
line: 20,
},
end: {
line: 103,
},
},
};
noLargeSnapshots(mockContext).ExpressionStatement(mockNode);

expect(mockReport).toHaveBeenCalledTimes(1);
expect(mockReport.mock.calls[0]).toMatchSnapshot();
});

it('should not report if node has fewer lines of code than limit', () => {
const mockReport = jest.fn();
const mockContext = {
getFilename: () => 'mock-component.jsx.snap',
options: [],
report: mockReport,
};
const mockNode = {
loc: {
start: {
line: 1,
},
end: {
line: 18,
},
},
};
noLargeSnapshots(mockContext).ExpressionStatement(mockNode);

expect(mockReport).not.toHaveBeenCalled();
});
});
});
Loading

0 comments on commit a42d917

Please sign in to comment.