Skip to content

Commit

Permalink
[jest-validate] support recursive config check (#6802)
Browse files Browse the repository at this point in the history
  • Loading branch information
thymikee authored and mjesun committed Aug 9, 2018
1 parent c9d4474 commit cefc342
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- `[jest-runner]` print stack trace when `process.exit` is called from user code ([#6714](https://github.com/facebook/jest/pull/6714))
- `[jest-each]` introduces `%#` option to add index of the test to its title ([#6414](https://github.com/facebook/jest/pull/6414))
- `[pretty-format]` Support serializing `DocumentFragment` ([#6705](https://github.com/facebook/jest/pull/6705))
- `[jest-validate]` Add `recursive` and `recursiveBlacklist` options for deep config checks ([#6802](https://github.com/facebook/jest/pull/6802))

### Fixes

Expand Down
9 changes: 9 additions & 0 deletions packages/jest-config/src/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,15 @@ export default function normalize(options: InitialOptions, argv: Argv) {
comment: DOCUMENTATION_NOTE,
deprecatedConfig: DEPRECATED_CONFIG,
exampleConfig: VALID_CONFIG,
recursiveBlacklist: [
'collectCoverageOnlyFrom',
// 'coverageThreshold' allows to use 'global' and glob strings on the same
// level, there's currently no way we can deal with such config
'coverageThreshold',
'globals',
'moduleNameMapper',
'transform',
],
});

options = normalizePreprocessor(
Expand Down
25 changes: 20 additions & 5 deletions packages/jest-config/src/valid_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default ({
cache: true,
cacheDirectory: '/tmp/user/jest',
changedFilesWithAncestor: false,
changedSince: '',
changedSince: 'master',
clearMocks: false,
collectCoverage: true,
collectCoverageFrom: ['src', '!public'],
Expand All @@ -34,6 +34,9 @@ export default ({
coverageThreshold: {
global: {
branches: 50,
functions: 100,
lines: 100,
statements: 100,
},
},
displayName: 'project-name',
Expand All @@ -44,8 +47,11 @@ export default ({
forceExit: false,
globalSetup: 'setup.js',
globalTeardown: 'teardown.js',
globals: {},
globals: {__DEV__: true},
haste: {
defaultPlatform: 'ios',
hasteImplModulePath: '<rootDir>/haste_impl.js',
platforms: ['ios', 'android'],
providesModuleNodeModules: ['react', 'react-native'],
},
json: false,
Expand Down Expand Up @@ -87,7 +93,7 @@ export default ({
skipNodeResolution: false,
snapshotSerializers: ['my-serializer-module'],
testEnvironment: 'jest-environment-jsdom',
testEnvironmentOptions: {},
testEnvironmentOptions: {userAgent: 'Agent/007'},
testFailureExitCode: 1,
testLocationInResults: false,
testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
Expand All @@ -107,7 +113,16 @@ export default ({
useStderr: false,
verbose: false,
watch: false,
watchPathIgnorePatterns: [],
watchPlugins: [],
watchPathIgnorePatterns: ['<rootDir>/e2e/'],
watchPlugins: [
'path/to/yourWatchPlugin',
[
'jest-watch-typeahead/filename',
{
key: 'k',
prompt: 'do something with my custom prompt',
},
],
],
watchman: true,
}: InitialOptions);
8 changes: 7 additions & 1 deletion packages/jest-validate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Where `ValidationOptions` are:

```js
type ValidationOptions = {
blacklist?: Array<string>,
comment?: string,
condition?: (option: any, validOption: any) => boolean,
deprecate?: (
Expand All @@ -34,6 +35,7 @@ type ValidationOptions = {
options: ValidationOptions,
) => void,
exampleConfig: Object,
recursive?: boolean,
title?: Title,
unknown?: (
config: Object,
Expand All @@ -60,11 +62,13 @@ Almost anything can be overwritten to suite your needs.

### Options

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

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

Example:
{
"transform": {"^.+\\.js$": "<rootDir>/preprocessor.js"}
"transform": {
"^.+\\.js$": "<rootDir>/preprocessor.js"
}
}

Documentation: http://custom-docs.com
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ exports[`pretty prints valid config for Array 1`] = `
<red></>
<red> Example:</>
<red> {</>
<red> <bold>\\"coverageReporters\\"</>: <bold>[\\"json\\", \\"text\\", \\"lcov\\", \\"clover\\"]</></>
<red> <bold>\\"coverageReporters\\"</>: <bold>[</></>
<red><bold> \\"json\\",</></>
<red><bold> \\"text\\",</></>
<red><bold> \\"lcov\\",</></>
<red><bold> \\"clover\\"</></>
<red><bold> ]</></>
<red> }</>
<red></>"
`;
Expand Down Expand Up @@ -77,7 +82,12 @@ exports[`pretty prints valid config for Object 1`] = `
<red></>
<red> Example:</>
<red> {</>
<red> <bold>\\"haste\\"</>: <bold>{\\"providesModuleNodeModules\\": [\\"react\\", \\"react-native\\"]}</></>
<red> <bold>\\"haste\\"</>: <bold>{</></>
<red><bold> \\"providesModuleNodeModules\\": [</></>
<red><bold> \\"react\\",</></>
<red><bold> \\"react-native\\"</></>
<red><bold> ]</></>
<red><bold> }</></>
<red> }</>
<red></>"
`;
Expand Down Expand Up @@ -122,7 +132,10 @@ exports[`works with custom errors 1`] = `
<red></>
<red> Example:</>
<red> {</>
<red> <bold>\\"test\\"</>: <bold>[1, 2]</></>
<red> <bold>\\"test\\"</>: <bold>[</></>
<red><bold> 1,</></>
<red><bold> 2</></>
<red><bold> ]</></>
<red> }</>
<red></>
<red>My custom comment</>"
Expand Down
76 changes: 61 additions & 15 deletions packages/jest-validate/src/__tests__/validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const {
deprecatedConfig,
} = require('./fixtures/jest_config');

test('validates default Jest config', () => {
test('recursively validates default Jest config', () => {
expect(
validate(defaultConfig, {
exampleConfig: validConfig,
Expand All @@ -29,7 +29,7 @@ test('validates default Jest config', () => {
});
});

test('validates default jest-validate config', () => {
test('recursively validates default jest-validate config', () => {
expect(
validate(jestValidateDefaultConfig, {
exampleConfig: jestValidateExampleConfig,
Expand All @@ -40,19 +40,17 @@ test('validates default jest-validate config', () => {
});
});

[
[{automock: []}, 'Boolean'],
[{coverageReporters: {}}, 'Array'],
[{preset: 1337}, 'String'],
[{haste: 42}, 'Object'],
].forEach(([config, type]) => {
test(`pretty prints valid config for ${type}`, () => {
expect(() =>
validate(config, {
exampleConfig: validConfig,
}),
).toThrowErrorMatchingSnapshot();
});
test.each([
['Boolean', {automock: []}],
['Array', {coverageReporters: {}}],
['String', {preset: 1337}],
['Object', {haste: 42}],
])('pretty prints valid config for %s', (type, config) => {
expect(() =>
validate(config, {
exampleConfig: validConfig,
}),
).toThrowErrorMatchingSnapshot();
});

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

test('recursively omits null and undefined config values', () => {
const config = {
haste: {
providesModuleNodeModules: null,
},
};
expect(
validate(config, {exampleConfig: validConfig, recursive: true}),
).toEqual({
hasDeprecationWarnings: false,
isValid: true,
});
});

test('respects blacklist', () => {
const warn = console.warn;
console.warn = jest.fn();
const config = {
something: {
nested: {
some_random_key: 'value',
some_random_key2: 'value2',
},
},
};
const exampleConfig = {
something: {
nested: {
test: true,
},
},
};

validate(config, {exampleConfig});

expect(console.warn).toBeCalled();

console.warn.mockReset();

validate(config, {
exampleConfig,
recursiveBlacklist: ['something.nested'],
});

expect(console.warn).not.toBeCalled();
console.warn = warn;
});

test('displays warning for unknown config options', () => {
const config = {unkwon: {}};
const validConfig = {unknown: 'string'};
Expand Down
5 changes: 3 additions & 2 deletions packages/jest-validate/src/default_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type {ValidationOptions} from './types';
import {deprecationWarning} from './deprecated';
import {unknownOptionWarning} from './warnings';
import {errorMessage} from './errors';
import exampleConfig from './example_config';
import validationCondition from './condition';
import {ERROR, DEPRECATION, WARNING} from './utils';

Expand All @@ -22,7 +21,9 @@ export default ({
deprecate: deprecationWarning,
deprecatedConfig: {},
error: errorMessage,
exampleConfig,
exampleConfig: {},
recursive: true,
recursiveBlacklist: [],
title: {
deprecation: DEPRECATION,
error: ERROR,
Expand Down
11 changes: 8 additions & 3 deletions packages/jest-validate/src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,27 @@ import type {ValidationOptions} from './types';

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

export const errorMessage = (
option: string,
received: any,
defaultValue: any,
options: ValidationOptions,
path?: Array<string>,
): void => {
const message = ` Option ${chalk.bold(`"${option}"`)} must be of type:
const message = ` Option ${chalk.bold(
`"${path && path.length > 0 ? path.join('.') + '.' : ''}${option}"`,
)} must be of type:
${chalk.bold.green(getType(defaultValue))}
but instead received:
${chalk.bold.red(getType(received))}
Example:
{
${chalk.bold(`"${option}"`)}: ${chalk.bold(format(defaultValue))}
${chalk.bold(`"${option}"`)}: ${chalk.bold(
formatPrettyObject(defaultValue),
)}
}`;

const comment = options.comment;
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-validate/src/example_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const config: ValidationOptions = {
},
error: (option, received, defaultValue, options) => {},
exampleConfig: {key: 'value', test: 'case'},
recursive: true,
recursiveBlacklist: [],
title: {
deprecation: 'Deprecation Warning',
error: 'Validation Error',
Expand Down
4 changes: 4 additions & 0 deletions packages/jest-validate/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ export type ValidationOptions = {
received: any,
defaultValue: any,
options: ValidationOptions,
path?: Array<string>,
) => void,
exampleConfig: Object,
recursive?: boolean,
recursiveBlacklist?: Array<string>,
title?: Title,
unknown?: (
config: Object,
exampleConfig: Object,
option: string,
options: ValidationOptions,
path?: Array<string>,
) => void,
};
7 changes: 7 additions & 0 deletions packages/jest-validate/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export const format = (value: any): string =>
? value.toString()
: prettyFormat(value, {min: true});

export const formatPrettyObject = (value: any): string =>
typeof value === 'function'
? value.toString()
: JSON.stringify(value, null, 2)
.split('\n')
.join('\n ');

export class ValidationError extends Error {
name: string;
message: string;
Expand Down

0 comments on commit cefc342

Please sign in to comment.