diff --git a/CHANGELOG.md b/CHANGELOG.md index 272a57fad2a3..3e70480da8ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - `[jest-runner]` Support default exports for test environments ([#8163](https://github.com/facebook/jest/pull/8163)) - `[pretty-format]` Support React.Suspense ([#8180](https://github.com/facebook/jest/pull/8180)) - `[jest-snapshot]` Indent inline snapshots ([#8198](https://github.com/facebook/jest/pull/8198)) +- `[jest-config]` Support colors in `displayName` configuration ([#8025](https://github.com/facebook/jest/pull/8025)) ### Fixes diff --git a/docs/Configuration.md b/docs/Configuration.md index 859eb539945a..39801978f964 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -288,6 +288,31 @@ The `extract` function should return an iterable (`Array`, `Set`, etc.) with the That module can also contain a `getCacheKey` function to generate a cache key to determine if the logic has changed and any cached artifacts relying on it should be discarded. +### `displayName` [string, object] + +default: `undefined` + +Allows for a label to be printed along side a test while it is running. This becomes more useful in multiproject repositories where there can be many jest configuration files. This visually tells which project a test belongs to. Here are sample valid values. + +```js +module.exports = { + displayName: 'CLIENT', +}; +``` + +or + +```js +module.exports = { + displayName: { + name: 'CLIENT', + color: 'blue', + }, +}; +``` + +As a secondary option, an object with the properties `name` and `color` can be passed. This allows for a custom configuration of the background color of the displayName. `displayName` defaults to white when its value is a string. Jest uses [chalk](https://github.com/chalk/chalk) to provide the color. As such, all of the valid options for colors supported by chalk are also supported by jest. + ### `errorOnDeprecated` [boolean] Default: `false` diff --git a/packages/jest-config/src/ValidConfig.ts b/packages/jest-config/src/ValidConfig.ts index 638d8f811572..6cdd558591b5 100644 --- a/packages/jest-config/src/ValidConfig.ts +++ b/packages/jest-config/src/ValidConfig.ts @@ -39,7 +39,11 @@ const initialOptions: Config.InitialOptions = { }, }, dependencyExtractor: '/dependencyExtractor.js', - displayName: 'project-name', + // @ts-ignore TODO: type this properly + displayName: multipleValidOptions('test-config', { + color: 'blue', + name: 'test-config', + }), errorOnDeprecated: false, expand: false, extraGlobals: [], diff --git a/packages/jest-config/src/__tests__/__snapshots__/normalize.test.js.snap b/packages/jest-config/src/__tests__/__snapshots__/normalize.test.js.snap index ea5a367f3d53..b566ee92df51 100644 --- a/packages/jest-config/src/__tests__/__snapshots__/normalize.test.js.snap +++ b/packages/jest-config/src/__tests__/__snapshots__/normalize.test.js.snap @@ -17,6 +17,70 @@ exports[`Upgrade help logs a warning when \`scriptPreprocessor\` and/or \`prepro " `; +exports[`displayName should throw an error when displayName is is an empty object 1`] = ` +"Validation Error: + + Option \\"displayName\\" must be of type: + + { + name: string; + color: string; + } + + + Configuration Documentation: + https://jestjs.io/docs/configuration.html +" +`; + +exports[`displayName should throw an error when displayName is missing color 1`] = ` +"Validation Error: + + Option \\"displayName\\" must be of type: + + { + name: string; + color: string; + } + + + Configuration Documentation: + https://jestjs.io/docs/configuration.html +" +`; + +exports[`displayName should throw an error when displayName is missing name 1`] = ` +"Validation Error: + + Option \\"displayName\\" must be of type: + + { + name: string; + color: string; + } + + + Configuration Documentation: + https://jestjs.io/docs/configuration.html +" +`; + +exports[`displayName should throw an error when displayName is using invalid values 1`] = ` +"Validation Error: + + Option \\"displayName\\" must be of type: + + { + name: string; + color: string; + } + + + Configuration Documentation: + https://jestjs.io/docs/configuration.html +" +`; + exports[`preset throws when module was found but no "jest-preset.js" or "jest-preset.json" files 1`] = ` "Validation Error: diff --git a/packages/jest-config/src/__tests__/normalize.test.js b/packages/jest-config/src/__tests__/normalize.test.js index e665b1e45886..88e557f1939e 100644 --- a/packages/jest-config/src/__tests__/normalize.test.js +++ b/packages/jest-config/src/__tests__/normalize.test.js @@ -1539,3 +1539,26 @@ describe('Defaults', () => { expect(console.warn).not.toHaveBeenCalled(); }); }); + +describe('displayName', () => { + test.each` + displayName | description + ${{}} | ${'is an empty object'} + ${{name: 'hello'}} | ${'missing color'} + ${{color: 'green'}} | ${'missing name'} + ${{color: 2, name: []}} | ${'using invalid values'} + `( + 'should throw an error when displayName is $description', + ({displayName}) => { + expect(() => { + normalize( + { + rootDir: '/root/', + displayName, + }, + {}, + ); + }).toThrowErrorMatchingSnapshot(); + }, + ); +}); diff --git a/packages/jest-config/src/normalize.ts b/packages/jest-config/src/normalize.ts index 929c3a17b182..819f792b188b 100644 --- a/packages/jest-config/src/normalize.ts +++ b/packages/jest-config/src/normalize.ts @@ -16,6 +16,7 @@ import micromatch from 'micromatch'; import {sync as realpath} from 'realpath-native'; import Resolver from 'jest-resolve'; import {replacePathSepForRegex} from 'jest-regex-util'; +import getType from 'jest-get-type'; import validatePattern from './validatePattern'; import getMaxWorkers from './getMaxWorkers'; import { @@ -738,6 +739,50 @@ export default function normalize( } break; } + case 'displayName': { + const displayName = oldOptions[key] as Config.DisplayName; + if (typeof displayName === 'string') { + value = displayName; + break; + } + /** + * Ensuring that displayName shape is correct here so that the + * reporters can trust the shape of the data + * TODO: Normalize "displayName" such that given a config option + * { + * "displayName": "Test" + * } + * becomes + * { + * displayName: { + * name: "Test", + * color: "white" + * } + * } + * + * This can't be done now since this will be a breaking change + * for custom reporters + */ + if (getType(displayName) === 'object') { + const errorMessage = + ` Option "${chalk.bold('displayName')}" must be of type:\n\n` + + ' {\n' + + ' name: string;\n' + + ' color: string;\n' + + ' }\n'; + const {name, color} = displayName; + if ( + !name || + !color || + typeof name !== 'string' || + typeof color !== 'string' + ) { + throw createConfigError(errorMessage); + } + } + value = oldOptions[key]; + break; + } case 'automock': case 'browser': case 'cache': @@ -749,7 +794,6 @@ export default function normalize( case 'coverageThreshold': case 'detectLeaks': case 'detectOpenHandles': - case 'displayName': case 'errorOnDeprecated': case 'expand': case 'extraGlobals': diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/utils.test.js.snap b/packages/jest-reporters/src/__tests__/__snapshots__/utils.test.js.snap index 4c7556f9f5f0..aea8ba842e74 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/utils.test.js.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/utils.test.js.snap @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`printDisplayName should correctly print the displayName when color and name are valid values 1`] = `" hello "`; + +exports[`printDisplayName should default displayName color to white when color is not a valid value 1`] = `" hello "`; + +exports[`printDisplayName should default displayName color to white when displayName is a string 1`] = `" hello "`; + exports[`trimAndFormatPath() does not trim anything 1`] = `"1234567890/1234567890/1234.js"`; exports[`trimAndFormatPath() split at the path.sep index 1`] = `".../1234.js"`; diff --git a/packages/jest-reporters/src/__tests__/utils.test.js b/packages/jest-reporters/src/__tests__/utils.test.js index d842b3e5a600..cc93b0a4d846 100644 --- a/packages/jest-reporters/src/__tests__/utils.test.js +++ b/packages/jest-reporters/src/__tests__/utils.test.js @@ -8,7 +8,7 @@ import path from 'path'; import chalk from 'chalk'; import stripAnsi from 'strip-ansi'; -import {trimAndFormatPath, wrapAnsiString} from '../utils'; +import {trimAndFormatPath, wrapAnsiString, printDisplayName} from '../utils'; describe('wrapAnsiString()', () => { it('wraps a long string containing ansi chars', () => { @@ -111,3 +111,35 @@ describe('trimAndFormatPath()', () => { expect(stripAnsi(result).length).toBe(columns - pad); }); }); + +describe('printDisplayName', () => { + it('should default displayName color to white when displayName is a string', () => { + const config = { + displayName: 'hello', + }; + + expect(printDisplayName(config)).toMatchSnapshot(); + }); + + it('should default displayName color to white when color is not a valid value', () => { + const config = { + displayName: { + color: 'rubbish', + name: 'hello', + }, + }; + + expect(printDisplayName(config)).toMatchSnapshot(); + }); + + it('should correctly print the displayName when color and name are valid values', () => { + const config = { + displayName: { + color: 'green', + name: 'hello', + }, + }; + + expect(printDisplayName(config)).toMatchSnapshot(); + }); +}); diff --git a/packages/jest-reporters/src/utils.ts b/packages/jest-reporters/src/utils.ts index e4b3c93c876c..6e7860fa68c7 100644 --- a/packages/jest-reporters/src/utils.ts +++ b/packages/jest-reporters/src/utils.ts @@ -17,14 +17,20 @@ const PROGRESS_BAR_WIDTH = 40; export const printDisplayName = (config: Config.ProjectConfig) => { const {displayName} = config; + const white = chalk.reset.inverse.white; + if (!displayName) { + return ''; + } - if (displayName) { - return chalk.supportsColor - ? chalk.reset.inverse.white(` ${displayName} `) - : displayName; + if (typeof displayName === 'string') { + return chalk.supportsColor ? white(` ${displayName} `) : displayName; } - return ''; + const {name, color} = displayName; + const chosenColor = chalk.reset.inverse[color] + ? chalk.reset.inverse[color] + : white; + return chalk.supportsColor ? chosenColor(` ${name} `) : name; }; export const trimAndFormatPath = ( diff --git a/packages/jest-test-result/src/types.ts b/packages/jest-test-result/src/types.ts index 4f012ab806df..b24c117d0834 100644 --- a/packages/jest-test-result/src/types.ts +++ b/packages/jest-test-result/src/types.ts @@ -8,6 +8,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import {CoverageMap, CoverageMapData} from 'istanbul-lib-coverage'; import {ConsoleBuffer} from '@jest/console'; +import {Config} from '@jest/types'; export type SerializableError = { code?: unknown; @@ -102,7 +103,7 @@ export type Suite = { export type TestResult = { console?: ConsoleBuffer | null; coverage?: CoverageMapData; - displayName?: string | null; + displayName?: Config.DisplayName; failureMessage?: string | null; leaks: boolean; memoryUsage?: Bytes; diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index 7cf1b81b76e3..4aed202a21a6 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -104,6 +104,13 @@ export type DefaultOptions = { watchman: boolean; }; +export type DisplayName = + | string + | { + name: string; + color: DisplayNameColor; + }; + export type InitialOptions = { automock?: boolean; bail?: boolean | number; @@ -129,7 +136,7 @@ export type InitialOptions = { dependencyExtractor?: string; detectLeaks?: boolean; detectOpenHandles?: boolean; - displayName?: string; + displayName?: DisplayName; expand?: boolean; extraGlobals?: Array; filter?: Path; @@ -223,6 +230,47 @@ type NotifyMode = | 'success-change' | 'failure-change'; +/** + * Hard coding this until + * https://github.com/chalk/chalk/pull/336 + * gets merged + */ +type DisplayNameColor = + | 'black' + | 'red' + | 'green' + | 'yellow' + | 'blue' + | 'magenta' + | 'cyan' + | 'white' + | 'gray' + | 'grey' + | 'blackBright' + | 'redBright' + | 'greenBright' + | 'yellowBright' + | 'blueBright' + | 'magentaBright' + | 'cyanBright' + | 'whiteBright' + | 'bgBlack' + | 'bgRed' + | 'bgGreen' + | 'bgYellow' + | 'bgBlue' + | 'bgMagenta' + | 'bgCyan' + | 'bgWhite' + | 'bgBlackBright' + | 'bgRedBright' + | 'bgGreenBright' + | 'bgYellowBright' + | 'bgBlueBright' + | 'bgMagentaBright' + | 'bgCyanBright' + | 'bgWhiteBright'; + type CoverageThreshold = { [path: string]: { [key: string]: number; @@ -318,7 +366,7 @@ export type ProjectConfig = { dependencyExtractor?: string; detectLeaks: boolean; detectOpenHandles: boolean; - displayName: string | null | undefined; + displayName?: DisplayName; errorOnDeprecated: boolean; extraGlobals: Array; filter: Path | null | undefined; diff --git a/website/versioned_docs/version-22.x/Configuration.md b/website/versioned_docs/version-22.x/Configuration.md index 8647c4cb58f2..6c33088ea55a 100644 --- a/website/versioned_docs/version-22.x/Configuration.md +++ b/website/versioned_docs/version-22.x/Configuration.md @@ -222,6 +222,12 @@ Jest will fail if: - The `./src/api/very-important-module.js` file has less than 100% coverage. - Every remaining file combined has less than 50% coverage (`global`). +### `displayName` [string] + +default: `undefined` + +Allows for a label to be printed along side a test while it is running. This becomes more useful in multiproject repositories where there can be many jest configuration files. This visually tells which project a test belongs to. + ### `forceCoverageMatch` [array] Default: `['']` diff --git a/website/versioned_docs/version-23.x/Configuration.md b/website/versioned_docs/version-23.x/Configuration.md index 605744066224..b74515407759 100644 --- a/website/versioned_docs/version-23.x/Configuration.md +++ b/website/versioned_docs/version-23.x/Configuration.md @@ -260,6 +260,12 @@ Jest will fail if: - The `./src/api/very-important-module.js` file has less than 100% coverage. - Every remaining file combined has less than 50% coverage (`global`). +### `displayName` [string] + +default: `undefined` + +Allows for a label to be printed along side a test while it is running. This becomes more useful in multiproject repositories where there can be many jest configuration files. This visually tells which project a test belongs to. + ### `errorOnDeprecated` [boolean] Default: `false` diff --git a/website/versioned_docs/version-24.0/Configuration.md b/website/versioned_docs/version-24.0/Configuration.md index a855c76816fb..09c44dcbe421 100644 --- a/website/versioned_docs/version-24.0/Configuration.md +++ b/website/versioned_docs/version-24.0/Configuration.md @@ -289,6 +289,12 @@ The `extract` function should return an iterable (`Array`, `Set`, etc.) with the That module can also contain a `getCacheKey` function to generate a cache key to determine if the logic has changed and any cached artifacts relying on it should be discarded. +### `displayName` [string] + +default: `undefined` + +Allows for a label to be printed along side a test while it is running. This becomes more useful in multiproject repositories where there can be many jest configuration files. This visually tells which project a test belongs to. + ### `errorOnDeprecated` [boolean] Default: `false` diff --git a/website/versioned_docs/version-24.1/Configuration.md b/website/versioned_docs/version-24.1/Configuration.md index 288fac54d070..016c03228dfe 100644 --- a/website/versioned_docs/version-24.1/Configuration.md +++ b/website/versioned_docs/version-24.1/Configuration.md @@ -289,6 +289,12 @@ The `extract` function should return an iterable (`Array`, `Set`, etc.) with the That module can also contain a `getCacheKey` function to generate a cache key to determine if the logic has changed and any cached artifacts relying on it should be discarded. +### `displayName` [string] + +default: `undefined` + +Allows for a label to be printed along side a test while it is running. This becomes more useful in multiproject repositories where there can be many jest configuration files. This visually tells which project a test belongs to. + ### `errorOnDeprecated` [boolean] Default: `false`