Skip to content

Commit cefc342

Browse files
thymikeemjesun
authored andcommitted
[jest-validate] support recursive config check (#6802)
1 parent c9d4474 commit cefc342

13 files changed

Lines changed: 188 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- `[jest-runner]` print stack trace when `process.exit` is called from user code ([#6714](https://github.com/facebook/jest/pull/6714))
77
- `[jest-each]` introduces `%#` option to add index of the test to its title ([#6414](https://github.com/facebook/jest/pull/6414))
88
- `[pretty-format]` Support serializing `DocumentFragment` ([#6705](https://github.com/facebook/jest/pull/6705))
9+
- `[jest-validate]` Add `recursive` and `recursiveBlacklist` options for deep config checks ([#6802](https://github.com/facebook/jest/pull/6802))
910

1011
### Fixes
1112

packages/jest-config/src/normalize.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,15 @@ export default function normalize(options: InitialOptions, argv: Argv) {
335335
comment: DOCUMENTATION_NOTE,
336336
deprecatedConfig: DEPRECATED_CONFIG,
337337
exampleConfig: VALID_CONFIG,
338+
recursiveBlacklist: [
339+
'collectCoverageOnlyFrom',
340+
// 'coverageThreshold' allows to use 'global' and glob strings on the same
341+
// level, there's currently no way we can deal with such config
342+
'coverageThreshold',
343+
'globals',
344+
'moduleNameMapper',
345+
'transform',
346+
],
338347
});
339348

340349
options = normalizePreprocessor(

packages/jest-config/src/valid_config.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default ({
2121
cache: true,
2222
cacheDirectory: '/tmp/user/jest',
2323
changedFilesWithAncestor: false,
24-
changedSince: '',
24+
changedSince: 'master',
2525
clearMocks: false,
2626
collectCoverage: true,
2727
collectCoverageFrom: ['src', '!public'],
@@ -34,6 +34,9 @@ export default ({
3434
coverageThreshold: {
3535
global: {
3636
branches: 50,
37+
functions: 100,
38+
lines: 100,
39+
statements: 100,
3740
},
3841
},
3942
displayName: 'project-name',
@@ -44,8 +47,11 @@ export default ({
4447
forceExit: false,
4548
globalSetup: 'setup.js',
4649
globalTeardown: 'teardown.js',
47-
globals: {},
50+
globals: {__DEV__: true},
4851
haste: {
52+
defaultPlatform: 'ios',
53+
hasteImplModulePath: '<rootDir>/haste_impl.js',
54+
platforms: ['ios', 'android'],
4955
providesModuleNodeModules: ['react', 'react-native'],
5056
},
5157
json: false,
@@ -87,7 +93,7 @@ export default ({
8793
skipNodeResolution: false,
8894
snapshotSerializers: ['my-serializer-module'],
8995
testEnvironment: 'jest-environment-jsdom',
90-
testEnvironmentOptions: {},
96+
testEnvironmentOptions: {userAgent: 'Agent/007'},
9197
testFailureExitCode: 1,
9298
testLocationInResults: false,
9399
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
@@ -107,7 +113,16 @@ export default ({
107113
useStderr: false,
108114
verbose: false,
109115
watch: false,
110-
watchPathIgnorePatterns: [],
111-
watchPlugins: [],
116+
watchPathIgnorePatterns: ['<rootDir>/e2e/'],
117+
watchPlugins: [
118+
'path/to/yourWatchPlugin',
119+
[
120+
'jest-watch-typeahead/filename',
121+
{
122+
key: 'k',
123+
prompt: 'do something with my custom prompt',
124+
},
125+
],
126+
],
112127
watchman: true,
113128
}: InitialOptions);

packages/jest-validate/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Where `ValidationOptions` are:
1818

1919
```js
2020
type ValidationOptions = {
21+
blacklist?: Array<string>,
2122
comment?: string,
2223
condition?: (option: any, validOption: any) => boolean,
2324
deprecate?: (
@@ -34,6 +35,7 @@ type ValidationOptions = {
3435
options: ValidationOptions,
3536
) => void,
3637
exampleConfig: Object,
38+
recursive?: boolean,
3739
title?: Title,
3840
unknown?: (
3941
config: Object,
@@ -60,11 +62,13 @@ Almost anything can be overwritten to suite your needs.
6062

6163
### Options
6264

65+
- `recursiveBlacklist` – optional array of string keyPaths that should be excluded from deep (recursive) validation.
6366
- `comment` – optional string to be rendered below error/warning message.
6467
- `condition` – an optional function with validation condition.
6568
- `deprecate`, `error`, `unknown` – optional functions responsible for displaying warning and error messages.
6669
- `deprecatedConfig` – optional object with deprecated config keys.
6770
- `exampleConfig` – the only **required** option with configuration against which you'd like to test.
71+
- `recursive` - optional boolean determining whether recursively compare `exampleConfig` to `config` (default: `true`).
6872
- `title` – optional object of titles for errors and messages.
6973

7074
You will find examples of `condition`, `deprecate`, `error`, `unknown`, and `deprecatedConfig` inside source of this repository, named respectively.
@@ -116,7 +120,9 @@ This will output:
116120

117121
Example:
118122
{
119-
"transform": {"^.+\\.js$": "<rootDir>/preprocessor.js"}
123+
"transform": {
124+
"^.+\\.js$": "<rootDir>/preprocessor.js"
125+
}
120126
}
121127

122128
Documentation: http://custom-docs.com

packages/jest-validate/src/__tests__/__snapshots__/validate.test.js.snap

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ exports[`pretty prints valid config for Array 1`] = `
3232
<red></>
3333
<red> Example:</>
3434
<red> {</>
35-
<red> <bold>\\"coverageReporters\\"</>: <bold>[\\"json\\", \\"text\\", \\"lcov\\", \\"clover\\"]</></>
35+
<red> <bold>\\"coverageReporters\\"</>: <bold>[</></>
36+
<red><bold> \\"json\\",</></>
37+
<red><bold> \\"text\\",</></>
38+
<red><bold> \\"lcov\\",</></>
39+
<red><bold> \\"clover\\"</></>
40+
<red><bold> ]</></>
3641
<red> }</>
3742
<red></>"
3843
`;
@@ -77,7 +82,12 @@ exports[`pretty prints valid config for Object 1`] = `
7782
<red></>
7883
<red> Example:</>
7984
<red> {</>
80-
<red> <bold>\\"haste\\"</>: <bold>{\\"providesModuleNodeModules\\": [\\"react\\", \\"react-native\\"]}</></>
85+
<red> <bold>\\"haste\\"</>: <bold>{</></>
86+
<red><bold> \\"providesModuleNodeModules\\": [</></>
87+
<red><bold> \\"react\\",</></>
88+
<red><bold> \\"react-native\\"</></>
89+
<red><bold> ]</></>
90+
<red><bold> }</></>
8191
<red> }</>
8292
<red></>"
8393
`;
@@ -122,7 +132,10 @@ exports[`works with custom errors 1`] = `
122132
<red></>
123133
<red> Example:</>
124134
<red> {</>
125-
<red> <bold>\\"test\\"</>: <bold>[1, 2]</></>
135+
<red> <bold>\\"test\\"</>: <bold>[</></>
136+
<red><bold> 1,</></>
137+
<red><bold> 2</></>
138+
<red><bold> ]</></>
126139
<red> }</>
127140
<red></>
128141
<red>My custom comment</>"

packages/jest-validate/src/__tests__/validate.test.js

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const {
1818
deprecatedConfig,
1919
} = require('./fixtures/jest_config');
2020

21-
test('validates default Jest config', () => {
21+
test('recursively validates default Jest config', () => {
2222
expect(
2323
validate(defaultConfig, {
2424
exampleConfig: validConfig,
@@ -29,7 +29,7 @@ test('validates default Jest config', () => {
2929
});
3030
});
3131

32-
test('validates default jest-validate config', () => {
32+
test('recursively validates default jest-validate config', () => {
3333
expect(
3434
validate(jestValidateDefaultConfig, {
3535
exampleConfig: jestValidateExampleConfig,
@@ -40,19 +40,17 @@ test('validates default jest-validate config', () => {
4040
});
4141
});
4242

43-
[
44-
[{automock: []}, 'Boolean'],
45-
[{coverageReporters: {}}, 'Array'],
46-
[{preset: 1337}, 'String'],
47-
[{haste: 42}, 'Object'],
48-
].forEach(([config, type]) => {
49-
test(`pretty prints valid config for ${type}`, () => {
50-
expect(() =>
51-
validate(config, {
52-
exampleConfig: validConfig,
53-
}),
54-
).toThrowErrorMatchingSnapshot();
55-
});
43+
test.each([
44+
['Boolean', {automock: []}],
45+
['Array', {coverageReporters: {}}],
46+
['String', {preset: 1337}],
47+
['Object', {haste: 42}],
48+
])('pretty prints valid config for %s', (type, config) => {
49+
expect(() =>
50+
validate(config, {
51+
exampleConfig: validConfig,
52+
}),
53+
).toThrowErrorMatchingSnapshot();
5654
});
5755

5856
test(`pretty prints valid config for Function`, () => {
@@ -76,6 +74,54 @@ test('omits null and undefined config values', () => {
7674
});
7775
});
7876

77+
test('recursively omits null and undefined config values', () => {
78+
const config = {
79+
haste: {
80+
providesModuleNodeModules: null,
81+
},
82+
};
83+
expect(
84+
validate(config, {exampleConfig: validConfig, recursive: true}),
85+
).toEqual({
86+
hasDeprecationWarnings: false,
87+
isValid: true,
88+
});
89+
});
90+
91+
test('respects blacklist', () => {
92+
const warn = console.warn;
93+
console.warn = jest.fn();
94+
const config = {
95+
something: {
96+
nested: {
97+
some_random_key: 'value',
98+
some_random_key2: 'value2',
99+
},
100+
},
101+
};
102+
const exampleConfig = {
103+
something: {
104+
nested: {
105+
test: true,
106+
},
107+
},
108+
};
109+
110+
validate(config, {exampleConfig});
111+
112+
expect(console.warn).toBeCalled();
113+
114+
console.warn.mockReset();
115+
116+
validate(config, {
117+
exampleConfig,
118+
recursiveBlacklist: ['something.nested'],
119+
});
120+
121+
expect(console.warn).not.toBeCalled();
122+
console.warn = warn;
123+
});
124+
79125
test('displays warning for unknown config options', () => {
80126
const config = {unkwon: {}};
81127
const validConfig = {unknown: 'string'};

packages/jest-validate/src/default_config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type {ValidationOptions} from './types';
1212
import {deprecationWarning} from './deprecated';
1313
import {unknownOptionWarning} from './warnings';
1414
import {errorMessage} from './errors';
15-
import exampleConfig from './example_config';
1615
import validationCondition from './condition';
1716
import {ERROR, DEPRECATION, WARNING} from './utils';
1817

@@ -22,7 +21,9 @@ export default ({
2221
deprecate: deprecationWarning,
2322
deprecatedConfig: {},
2423
error: errorMessage,
25-
exampleConfig,
24+
exampleConfig: {},
25+
recursive: true,
26+
recursiveBlacklist: [],
2627
title: {
2728
deprecation: DEPRECATION,
2829
error: ERROR,

packages/jest-validate/src/errors.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,27 @@ import type {ValidationOptions} from './types';
1111

1212
import chalk from 'chalk';
1313
import getType from 'jest-get-type';
14-
import {format, ValidationError, ERROR} from './utils';
14+
import {formatPrettyObject, ValidationError, ERROR} from './utils';
1515

1616
export const errorMessage = (
1717
option: string,
1818
received: any,
1919
defaultValue: any,
2020
options: ValidationOptions,
21+
path?: Array<string>,
2122
): void => {
22-
const message = ` Option ${chalk.bold(`"${option}"`)} must be of type:
23+
const message = ` Option ${chalk.bold(
24+
`"${path && path.length > 0 ? path.join('.') + '.' : ''}${option}"`,
25+
)} must be of type:
2326
${chalk.bold.green(getType(defaultValue))}
2427
but instead received:
2528
${chalk.bold.red(getType(received))}
2629
2730
Example:
2831
{
29-
${chalk.bold(`"${option}"`)}: ${chalk.bold(format(defaultValue))}
32+
${chalk.bold(`"${option}"`)}: ${chalk.bold(
33+
formatPrettyObject(defaultValue),
34+
)}
3035
}`;
3136

3237
const comment = options.comment;

packages/jest-validate/src/example_config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const config: ValidationOptions = {
1818
},
1919
error: (option, received, defaultValue, options) => {},
2020
exampleConfig: {key: 'value', test: 'case'},
21+
recursive: true,
22+
recursiveBlacklist: [],
2123
title: {
2224
deprecation: 'Deprecation Warning',
2325
error: 'Validation Error',

packages/jest-validate/src/types.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ export type ValidationOptions = {
2828
received: any,
2929
defaultValue: any,
3030
options: ValidationOptions,
31+
path?: Array<string>,
3132
) => void,
3233
exampleConfig: Object,
34+
recursive?: boolean,
35+
recursiveBlacklist?: Array<string>,
3336
title?: Title,
3437
unknown?: (
3538
config: Object,
3639
exampleConfig: Object,
3740
option: string,
3841
options: ValidationOptions,
42+
path?: Array<string>,
3943
) => void,
4044
};

0 commit comments

Comments
 (0)