Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jest-validate] support recursive config check #6802

Merged
merged 10 commits into from
Aug 9, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## master

### Features

- `[jest-validate]` Add `recursive` and `blacklist` options for deep config checks ([#6802](https://github.com/facebook/jest/pull/6802))

### Fixes

- `[jest-config]` Fix `--coverage` with `--findRelatedTests` overwriting `collectCoverageFrom` options ([#6736](https://github.com/facebook/jest/pull/6736))
Expand Down
7 changes: 7 additions & 0 deletions packages/jest-config/src/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,16 @@ const showTestPathPatternError = (testPathPattern: string) => {

export default function normalize(options: InitialOptions, argv: Argv) {
const {hasDeprecationWarnings} = validate(options, {
blacklist: [
'moduleNameMapper',
'transform',
'globals',
'collectCoverageOnlyFrom',
],
comment: DOCUMENTATION_NOTE,
deprecatedConfig: DEPRECATED_CONFIG,
exampleConfig: VALID_CONFIG,
recursive: true,
});

options = normalizePreprocessor(
Expand Down
6 changes: 6 additions & 0 deletions packages/jest-config/src/valid_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export default ({
coverageThreshold: {
global: {
branches: 50,
functions: 100,
lines: 100,
statements: 100,
},
},
displayName: 'project-name',
Expand All @@ -46,6 +49,9 @@ export default ({
globalTeardown: 'teardown.js',
globals: {},
haste: {
defaultPlatform: 'ios',
hasteImplModulePath: '<rootDir>/haste_impl.js',
platforms: ['ios', 'android'],
providesModuleNodeModules: ['react', 'react-native'],
},
json: false,
Expand Down
9 changes: 8 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

- `blacklist` – 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`.
- `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 All @@ -84,6 +88,7 @@ validate(config, {
comment: ' Documentation: http://custom-docs.com',
deprecatedConfig,
exampleConfig,
recursive: true,
title: {
deprecation: 'Custom Deprecation',
// leaving 'error' and 'warning' as default
Expand Down Expand Up @@ -116,7 +121,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
84 changes: 69 additions & 15 deletions packages/jest-validate/src/__tests__/validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,41 @@ const {
deprecatedConfig,
} = require('./fixtures/jest_config');

test('validates default Jest config', () => {
test('recursively validates default Jest config', () => {
expect(
validate(defaultConfig, {
exampleConfig: validConfig,
recursive: true,
}),
).toEqual({
hasDeprecationWarnings: false,
isValid: true,
});
});

test('validates default jest-validate config', () => {
test('recursively validates default jest-validate config', () => {
expect(
validate(jestValidateDefaultConfig, {
exampleConfig: jestValidateExampleConfig,
recursive: true,
}),
).toEqual({
hasDeprecationWarnings: false,
isValid: true,
});
});

[
[{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 @@ -61,6 +61,7 @@ test(`pretty prints valid config for Function`, () => {
expect(() =>
validate(config, {
exampleConfig: validConfig,
recursive: true,
}),
).toThrowErrorMatchingSnapshot();
});
Expand All @@ -76,6 +77,58 @@ 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,
recursive: true,
});

expect(console.warn).toBeCalled();

console.warn.mockReset();

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

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

test('displays warning for unknown config options', () => {
const config = {unkwon: {}};
const validConfig = {unknown: 'string'};
Expand All @@ -97,6 +150,7 @@ test('displays warning for deprecated config options', () => {
validate(config, {
deprecatedConfig,
exampleConfig: validConfig,
recursive: true,
}),
).toEqual({
hasDeprecationWarnings: true,
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,17 +12,18 @@ 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';

export default ({
blacklist: [],
comment: '',
condition: validationCondition,
deprecate: deprecationWarning,
deprecatedConfig: {},
error: errorMessage,
exampleConfig,
exampleConfig: {},
recursive: false,
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 @@ -10,6 +10,7 @@
import type {ValidationOptions} from './types';

const config: ValidationOptions = {
blacklist: [],
comment: ' A comment',
condition: (option, validOption) => true,
deprecate: (config, option, deprecatedOptions, options) => false,
Expand All @@ -18,6 +19,7 @@ const config: ValidationOptions = {
},
error: (option, received, defaultValue, options) => {},
exampleConfig: {key: 'value', test: 'case'},
recursive: true,
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 @@ -14,6 +14,7 @@ type Title = {|
|};

export type ValidationOptions = {
blacklist?: Array<string>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recursiveBlacklist? Or something like that, I think it should be tied to what the blacklist is for, so I don't have to look up the docs

comment?: string,
condition?: (option: any, validOption: any) => boolean,
deprecate?: (
Expand All @@ -28,13 +29,16 @@ export type ValidationOptions = {
received: any,
defaultValue: any,
options: ValidationOptions,
path?: Array<string>,
) => void,
exampleConfig: Object,
recursive?: boolean,
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