diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d622017f4ca..6108f055edae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - `[jest-config]` [**BREAKING**] Set default display name color based on runner ([#8689](https://github.com/facebook/jest/pull/8689)) - `[jest-diff]` Add options for colors and symbols ([#8841](https://github.com/facebook/jest/pull/8841)) - `[jest-diff]` [**BREAKING**] Export as ECMAScript module ([#8873](https://github.com/facebook/jest/pull/8873)) +- `[jest-diff]` Add `includeChangeCounts` and rename `Indicator` options ([#8881](https://github.com/facebook/jest/pull/8881)) - `[jest-runner]` Warn if a worker had to be force exited ([#8206](https://github.com/facebook/jest/pull/8206)) - `[@jest/test-result]` Create method to create empty `TestResult` ([#8867](https://github.com/facebook/jest/pull/8867)) - `[jest-worker]` [**BREAKING**] Return a promise from `end()`, resolving with the information whether workers exited gracefully ([#8206](https://github.com/facebook/jest/pull/8206)) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index 822e6c2d619b..0ac49f1f894b 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -40,7 +40,7 @@ const difference = diffLinesUnified(a, b); The returned **string** consists of: -- annotation lines: describe the two change symbols with labels, and a blank line +- annotation lines: describe the two change indicators with labels, and a blank line - comparison lines: similar to “unified” view on GitHub, but `Expected` lines are green, `Received` lines are red, and common lines are dim (by default, see Options) ```diff @@ -90,7 +90,7 @@ const difference = diffStringsUnified(a, b); The returned **string** consists of: -- annotation lines: describe the two change symbols with labels, and a blank line +- annotation lines: describe the two change indicators with labels, and a blank line - comparison lines: similar to “unified” view on GitHub, and **changed substrings** have **inverted** foreground and background colors (which the following example does not show) ```diff @@ -107,9 +107,9 @@ The returned **string** consists of: Here are edge cases for the return value: - both `a` and `b` are empty strings: no comparison lines -- only `a` is empty string: all comparison lines have `bColor` and `bSymbol` (see Options) -- only `b` is empty string: all comparison lines have `aColor` and `aSymbol` (see Options) -- `a` and `b` are equal non-empty strings: all comparison lines have `commonColor` and `commonSymbol` (see Options) +- only `a` is empty string: all comparison lines have `bColor` and `bIndicator` (see Options) +- only `b` is empty string: all comparison lines have `aColor` and `aIndicator` (see Options) +- `a` and `b` are equal non-empty strings: all comparison lines have `commonColor` and `commonIndicator` (see Options) ### Performance of diffStringsUnified @@ -184,7 +184,7 @@ diffs[4][1] === 'm' */ ``` -## Advanced import for diffStringsRaw +### Advanced import for diffStringsRaw Here are all the named imports for the `diffStringsRaw` function: @@ -216,14 +216,15 @@ For other applications, you can provide an options object as a third argument: | :-------------------- | :------------ | | `aAnnotation` | `'Expected'` | | `aColor` | `chalk.green` | -| `aSymbol` | `'-'` | +| `aIndicator` | `'-'` | | `bAnnotation` | `'Received'` | | `bColor` | `chalk.red` | -| `bSymbol` | `'+'` | +| `bIndicator` | `'+'` | | `commonColor` | `chalk.dim` | -| `commonSymbol` | `' '` | +| `commonIndicator` | `' '` | | `contextLines` | `5` | | `expand` | `true` | +| `includeChangeCounts` | `false` | | `omitAnnotationLines` | `false` | ### Example of options for labels @@ -264,18 +265,18 @@ const options = { }; ``` -### Example of options for symbols +### Example of options for indicators -For consistency with the `diff` command, you might replace the symbols: +For consistency with the `diff` command, you might replace the indicators: ```js const options = { - aSymbol: '<', - bSymbol: '>', + aIndicator: '<', + bIndicator: '>', }; ``` -The `jest-diff` package assumes (but does not enforce) that the 3 symbols have equal length. +The `jest-diff` package assumes (but does not enforce) that the 3 indicators have equal length. ### Example of options to limit common lines @@ -292,6 +293,32 @@ const options = { A patch mark like `@@ -12,7 +12,9 @@` accounts for omitted common lines. +### Example of option to include change counts + +To display the number of change lines at the right of annotation lines: + +```js +const a = ['change from', 'common']; +const b = ['change to', 'insert', 'common']; +const options = { + includeChangeCounts: true, +}; + +const difference = diffLinesUnified(a, b, options); +``` + +```diff +- Expected 1 - ++ Received 2 + + + Array [ +- "change from", ++ "change to", ++ "insert", + "common", + ] +``` + ### Example of option to omit annotation lines To display only the comparison lines: diff --git a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap index c7e1b19669fc..493fd9df66c8 100644 --- a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap +++ b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap @@ -100,8 +100,8 @@ exports[`color of text (expanded) 1`] = ` `; exports[`context number of lines: -1 (5 default) 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + @@ -6,9 +6,9 @@ 4, @@ -117,8 +117,8 @@ exports[`context number of lines: -1 (5 default) 1`] = ` `; exports[`context number of lines: 0 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + @@ -11,1 +11,0 @@ - 9, @@ -127,8 +127,8 @@ exports[`context number of lines: 0 1`] = ` `; exports[`context number of lines: 1 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + @@ -10,4 +10,4 @@ 8, @@ -139,8 +139,8 @@ exports[`context number of lines: 1 1`] = ` `; exports[`context number of lines: 2 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + @@ -9,6 +9,6 @@ 7, @@ -153,8 +153,8 @@ exports[`context number of lines: 2 1`] = ` `; exports[`context number of lines: 3.1 (5 default) 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + @@ -6,9 +6,9 @@ 4, @@ -170,8 +170,8 @@ exports[`context number of lines: 3.1 (5 default) 1`] = ` `; exports[`context number of lines: undefined (5 default) 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + @@ -6,9 +6,9 @@ 4, @@ -187,36 +187,36 @@ exports[`context number of lines: undefined (5 default) 1`] = ` `; exports[`diffStringsUnified edge cases empty both a and b 1`] = ` -"- Expected -+ Received +"- Expected 0 - ++ Received 0 + " `; exports[`diffStringsUnified edge cases empty only a 1`] = ` -"- Expected -+ Received +"- Expected 0 - ++ Received 1 + + one-line string" `; exports[`diffStringsUnified edge cases empty only b 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 0 + - one-line string" `; exports[`diffStringsUnified edge cases equal both non-empty 1`] = ` -"- Expected -+ Received +"- Expected 0 - ++ Received 0 + one-line string" `; exports[`diffStringsUnified edge cases multiline has no common after clean up chaff 1`] = ` -"- Expected -+ Received +"- Expected 2 - ++ Received 2 + - delete - two @@ -225,16 +225,16 @@ exports[`diffStringsUnified edge cases multiline has no common after clean up ch `; exports[`diffStringsUnified edge cases one-line has no common after clean up chaff 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + - delete + insert" `; exports[`falls back to not call toJSON if it throws and then objects have differences 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + Object { - \\"line\\": 1, @@ -247,8 +247,8 @@ exports[`falls back to not call toJSON if serialization has no differences but t "Compared values serialize to the same structure. Printing internal object structure without calling \`toJSON\` instead. -- Expected -+ Received +- Expected 1 - ++ Received 1 + Object { - \\"line\\": 1, @@ -273,24 +273,24 @@ exports[`highlight only the last in odd length of leading spaces (expanded) 1`] `; exports[`oneline strings 1`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + - ab + aa" `; exports[`oneline strings 2`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 1 + - 123456789 + 234567890" `; exports[`oneline strings 3`] = ` -"- Expected -+ Received +"- Expected 1 - ++ Received 2 + - oneline + multi @@ -298,8 +298,8 @@ exports[`oneline strings 3`] = ` `; exports[`oneline strings 4`] = ` -"- Expected -+ Received +"- Expected 2 - ++ Received 1 + - multi - line @@ -322,7 +322,7 @@ exports[`options 7980 diffStringsUnified 1`] = ` + \`\${Ti.App.getName()} \${Ti.App.getVersion()} \${Ti.Platform.getName()} \${Ti.Platform.getVersion()}\`" `; -exports[`options change symbols diff 1`] = ` +exports[`options change indicators diff 1`] = ` "< Expected > Received @@ -348,7 +348,74 @@ exports[`options common diff 1`] = ` = ]" `; -exports[`options omitAnnotationLines diff 1`] = ` +exports[`options includeChangeCounts false diffLinesUnified 1`] = ` +"- Expected ++ Received + + Array [ +- \\"delete\\", +- \\"change from\\", ++ \\"change to\\", ++ \\"insert\\", + \\"common\\", + ]" +`; + +exports[`options includeChangeCounts false diffStringsUnified 1`] = ` +"- Expected ++ Received + +- change from ++ change to + common" +`; + +exports[`options includeChangeCounts true padding diffLinesUnified a has 2 digits 1`] = ` +"- Before 10 - ++ After 1 + + + common +- a +- a +- a +- a +- a +- a +- a +- a +- a +- a ++ b" +`; + +exports[`options includeChangeCounts true padding diffLinesUnified b has 2 digits 1`] = ` +"- Before 1 - ++ After 10 + + + common +- a ++ b ++ b ++ b ++ b ++ b ++ b ++ b ++ b ++ b ++ b" +`; + +exports[`options includeChangeCounts true padding diffStringsUnified 1`] = ` +"- Before 1 - ++ After 1 + + +- change from ++ change to + common" +`; + +exports[`options omitAnnotationLines true diff 1`] = ` " Array [ - \\"delete\\", - \\"change from\\", @@ -358,4 +425,10 @@ exports[`options omitAnnotationLines diff 1`] = ` ]" `; -exports[`options omitAnnotationLines diffStringsUnified empty strings 1`] = `""`; +exports[`options omitAnnotationLines true diffStringsUnified and includeChangeCounts true 1`] = ` +"- change from ++ change to + common" +`; + +exports[`options omitAnnotationLines true diffStringsUnified empty strings 1`] = `""`; diff --git a/packages/jest-diff/src/__tests__/diff.test.ts b/packages/jest-diff/src/__tests__/diff.test.ts index 32d1b4f6d714..4ad245bd119f 100644 --- a/packages/jest-diff/src/__tests__/diff.test.ts +++ b/packages/jest-diff/src/__tests__/diff.test.ts @@ -12,6 +12,10 @@ import diff from '../'; import {diffStringsUnified} from '../printDiffs'; import {DiffOptions} from '../types'; +const optionsCounts = { + includeChangeCounts: true, +}; + const NO_DIFF_MESSAGE = 'Compared values have no visual difference.'; const stripped = (a: unknown, b: unknown, options?: DiffOptions) => @@ -88,10 +92,10 @@ describe('no visual difference', () => { }); test('oneline strings', () => { - expect(diff('ab', 'aa')).toMatchSnapshot(); - expect(diff('123456789', '234567890')).toMatchSnapshot(); - expect(diff('oneline', 'multi\nline')).toMatchSnapshot(); - expect(diff('multi\nline', 'oneline')).toMatchSnapshot(); + expect(diff('ab', 'aa', optionsCounts)).toMatchSnapshot(); + expect(diff('123456789', '234567890', optionsCounts)).toMatchSnapshot(); + expect(diff('oneline', 'multi\nline', optionsCounts)).toMatchSnapshot(); + expect(diff('multi\nline', 'oneline', optionsCounts)).toMatchSnapshot(); }); describe('falls back to not call toJSON', () => { @@ -103,7 +107,7 @@ describe('falls back to not call toJSON', () => { test('but then objects have differences', () => { const a = {line: 1, toJSON}; const b = {line: 2, toJSON}; - expect(diff(a, b)).toMatchSnapshot(); + expect(diff(a, b, optionsCounts)).toMatchSnapshot(); }); test('and then objects have no differences', () => { const a = {line: 2, toJSON}; @@ -119,7 +123,7 @@ describe('falls back to not call toJSON', () => { test('and then objects have differences', () => { const a = {line: 1, toJSON}; const b = {line: 2, toJSON}; - expect(diff(a, b)).toMatchSnapshot(); + expect(diff(a, b, optionsCounts)).toMatchSnapshot(); }); test('and then objects have no differences', () => { const a = {line: 2, toJSON}; @@ -849,6 +853,7 @@ describe('context', () => { { contextLines, expand: false, + ...optionsCounts, }, ); expect(result).toMatchSnapshot(); @@ -868,42 +873,42 @@ describe('diffStringsUnified edge cases', () => { const a = ''; const b = ''; - expect(diffStringsUnified(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b, optionsCounts)).toMatchSnapshot(); }); test('empty only a', () => { const a = ''; const b = 'one-line string'; - expect(diffStringsUnified(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b, optionsCounts)).toMatchSnapshot(); }); test('empty only b', () => { const a = 'one-line string'; const b = ''; - expect(diffStringsUnified(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b, optionsCounts)).toMatchSnapshot(); }); test('equal both non-empty', () => { const a = 'one-line string'; const b = 'one-line string'; - expect(diffStringsUnified(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b, optionsCounts)).toMatchSnapshot(); }); test('multiline has no common after clean up chaff', () => { const a = 'delete\ntwo'; const b = 'insert\n2'; - expect(diffStringsUnified(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b, optionsCounts)).toMatchSnapshot(); }); test('one-line has no common after clean up chaff', () => { const a = 'delete'; const b = 'insert'; - expect(diffStringsUnified(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b, optionsCounts)).toMatchSnapshot(); }); }); @@ -933,10 +938,13 @@ describe('options', () => { const a = ['delete', 'change from', 'common']; const b = ['change to', 'insert', 'common']; - describe('change symbols', () => { + const aString = 'change from\ncommon'; // without delete + const bString = 'change to\ncommon'; // without insert + + describe('change indicators', () => { const options = { - aSymbol: '<', - bSymbol: '>', + aIndicator: '<', + bIndicator: '>', }; test('diff', () => { @@ -947,7 +955,7 @@ describe('options', () => { describe('common', () => { const options = { commonColor: line => line, - commonSymbol: '=', + commonIndicator: '=', }; test('diff', () => { @@ -955,7 +963,45 @@ describe('options', () => { }); }); - describe('omitAnnotationLines', () => { + describe('includeChangeCounts false', () => { + const options = { + includeChangeCounts: false, + }; + + test('diffLinesUnified', () => { + expect(diff(a, b, options)).toMatchSnapshot(); + }); + + test('diffStringsUnified', () => { + expect(diffStringsUnified(aString, bString, options)).toMatchSnapshot(); + }); + }); + + describe('includeChangeCounts true padding', () => { + const options = { + aAnnotation: 'Before', + bAnnotation: 'After', + includeChangeCounts: true, + }; + + test('diffLinesUnified a has 2 digits', () => { + const has2 = 'common\na\na\na\na\na\na\na\na\na\na'; + const has1 = 'common\nb'; + expect(diff(has2, has1, options)).toMatchSnapshot(); + }); + + test('diffLinesUnified b has 2 digits', () => { + const has1 = 'common\na'; + const has2 = 'common\nb\nb\nb\nb\nb\nb\nb\nb\nb\nb'; + expect(diff(has1, has2, options)).toMatchSnapshot(); + }); + + test('diffStringsUnified', () => { + expect(diffStringsUnified(aString, bString, options)).toMatchSnapshot(); + }); + }); + + describe('omitAnnotationLines true', () => { const options = { omitAnnotationLines: true, }; @@ -964,6 +1010,12 @@ describe('options', () => { expect(diff(a, b, options)).toMatchSnapshot(); }); + test('diffStringsUnified and includeChangeCounts true', () => { + const options2 = {...options, includeChangeCounts: true}; + + expect(diffStringsUnified(aString, bString, options2)).toMatchSnapshot(); + }); + test('diffStringsUnified empty strings', () => { expect(diffStringsUnified('', '', options)).toMatchSnapshot(); }); diff --git a/packages/jest-diff/src/diffLines.ts b/packages/jest-diff/src/diffLines.ts index a718470caf65..305b8a19fd16 100644 --- a/packages/jest-diff/src/diffLines.ts +++ b/packages/jest-diff/src/diffLines.ts @@ -8,7 +8,7 @@ import chalk, {Chalk} from 'chalk'; import diff, {Callbacks} from 'diff-sequences'; import {NO_DIFF_MESSAGE} from './constants'; -import {createPatchMark, printAnnotation} from './printDiffs'; +import {ChangeCounts, createPatchMark, printAnnotation} from './printDiffs'; import {DiffOptionsNormalized} from './types'; type Original = { @@ -46,7 +46,7 @@ const formatDelete = ( aEnd: number, aLinesUn: Array, aLinesIn: Array, - {aColor, aSymbol}: DiffOptionsNormalized, + {aColor, aIndicator}: DiffOptionsNormalized, put: Put, ) => { const highlightSpaces = getHighlightSpaces(aLinesUn !== aLinesIn); @@ -56,7 +56,9 @@ const formatDelete = ( const indentation = aLineIn.slice(0, aLineIn.length - aLineUn.length); put( - aColor(aSymbol + ' ' + indentation + highlightSpaces(aLineUn, bgInverse)), + aColor( + aIndicator + ' ' + indentation + highlightSpaces(aLineUn, bgInverse), + ), ); } }; @@ -67,7 +69,7 @@ const formatInsert = ( bEnd: number, bLinesUn: Array, bLinesIn: Array, - {bColor, bSymbol}: DiffOptionsNormalized, + {bColor, bIndicator}: DiffOptionsNormalized, put: Put, ) => { const highlightSpaces = getHighlightSpaces(bLinesUn !== bLinesIn); @@ -77,7 +79,9 @@ const formatInsert = ( const indentation = bLineIn.slice(0, bLineIn.length - bLineUn.length); put( - bColor(bSymbol + ' ' + indentation + highlightSpaces(bLineUn, bgInverse)), + bColor( + bIndicator + ' ' + indentation + highlightSpaces(bLineUn, bgInverse), + ), ); } }; @@ -92,7 +96,7 @@ const formatCommon = ( aLinesIn: Array, bLinesUn: Array, bLinesIn: Array, - {commonColor, commonSymbol}: DiffOptionsNormalized, + {commonColor, commonIndicator}: DiffOptionsNormalized, put: Put, ) => { const highlightSpaces = getHighlightSpaces(bLinesUn !== bLinesIn); @@ -109,7 +113,7 @@ const formatCommon = ( const fg = hasSameIndentation ? commonColor : fgIndent; const bg = hasSameIndentation ? bgCommon : bgInverse; - put(fg(commonSymbol + ' ' + indentation + highlightSpaces(bLineUn, bg))); + put(fg(commonIndicator + ' ' + indentation + highlightSpaces(bLineUn, bg))); } }; @@ -130,6 +134,11 @@ const diffExpand = ( array.push(line); }; + const changeCounts: ChangeCounts = { + a: 0, + b: 0, + }; + let aStart = 0; let bStart = 0; @@ -138,6 +147,8 @@ const diffExpand = ( aCommon, bCommon, ) => { + changeCounts.a += aCommon - aStart; + changeCounts.b += bCommon - bStart; formatDelete(aStart, aCommon, aLinesUn, aLinesIn, options, put); formatInsert(bStart, bCommon, bLinesUn, bLinesIn, options, put); formatCommon( @@ -160,10 +171,12 @@ const diffExpand = ( diff(aLength, bLength, isCommon, foundSubsequence); // After the last common subsequence, format remaining change lines. + changeCounts.a += aLength - aStart; + changeCounts.b += bLength - bStart; formatDelete(aStart, aLength, aLinesUn, aLinesIn, options, put); formatInsert(bStart, bLength, bLinesUn, bLinesIn, options, put); - return array.join('\n'); + return printAnnotation(options, changeCounts) + array.join('\n'); }; // jest --no-expand @@ -191,6 +204,10 @@ const diffNoExpand = ( const bLength = bLinesUn.length; const nContextLines = options.contextLines; const nContextLines2 = nContextLines + nContextLines; + const changeCounts: ChangeCounts = { + a: 0, + b: 0, + }; // Initialize the first patch for changes at the start, // especially for edge case in which there is no common subsequence. @@ -232,6 +249,8 @@ const diffNoExpand = ( } // Format preceding change lines. + changeCounts.a += aStartCommon - aEnd; + changeCounts.b += bStartCommon - bEnd; formatDelete(aEnd, aStartCommon, aLinesUn, aLinesIn, options, put); formatInsert(bEnd, bStartCommon, bLinesUn, bLinesIn, options, put); aEnd = aStartCommon; @@ -302,6 +321,8 @@ const diffNoExpand = ( // If no common subsequence or last was not at end, format remaining change lines. if (!isAtEnd) { + changeCounts.a += aLength - aEnd; + changeCounts.b += bLength - bEnd; formatDelete(aEnd, aLength, aLinesUn, aLinesIn, options, put); formatInsert(bEnd, bLength, bLinesUn, bLinesIn, options, put); aEnd = aLength; @@ -314,7 +335,7 @@ const diffNoExpand = ( array[iPatchMark] = createPatchMark(aStart, aEnd, bStart, bEnd); } - return array.join('\n'); + return printAnnotation(options, changeCounts) + array.join('\n'); }; export default ( @@ -350,10 +371,7 @@ export default ( } } - return ( - printAnnotation(options) + - (options.expand - ? diffExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn, options) - : diffNoExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn, options)) - ); + return options.expand + ? diffExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn, options) + : diffNoExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn, options); }; diff --git a/packages/jest-diff/src/normalizeDiffOptions.ts b/packages/jest-diff/src/normalizeDiffOptions.ts index 1430d7041d73..efdeab6701ab 100644 --- a/packages/jest-diff/src/normalizeDiffOptions.ts +++ b/packages/jest-diff/src/normalizeDiffOptions.ts @@ -14,14 +14,15 @@ const DIFF_CONTEXT_DEFAULT = 5; const OPTIONS_DEFAULT: DiffOptionsNormalized = { aAnnotation: 'Expected', aColor: chalk.green, - aSymbol: '-', + aIndicator: '-', bAnnotation: 'Received', bColor: chalk.red, - bSymbol: '+', + bIndicator: '+', commonColor: chalk.dim, - commonSymbol: ' ', + commonIndicator: ' ', contextLines: DIFF_CONTEXT_DEFAULT, expand: true, + includeChangeCounts: false, omitAnnotationLines: false, }; diff --git a/packages/jest-diff/src/printDiffs.ts b/packages/jest-diff/src/printDiffs.ts index 07d6c4d2ad4f..1abad0c3b416 100644 --- a/packages/jest-diff/src/printDiffs.ts +++ b/packages/jest-diff/src/printDiffs.ts @@ -7,7 +7,13 @@ import chalk from 'chalk'; -import {DIFF_EQUAL, Diff, cleanupSemantic} from './cleanupSemantic'; +import { + DIFF_DELETE, + DIFF_EQUAL, + DIFF_INSERT, + Diff, + cleanupSemantic, +} from './cleanupSemantic'; import diffLines from './diffLines'; import diffStrings from './diffStrings'; import getAlignedDiffs from './getAlignedDiffs'; @@ -50,30 +56,34 @@ const replaceSpacesAtEnd = (line: string): string => export const printDeleteLine = ( line: string, - {aColor, aSymbol}: DiffOptionsNormalized, + {aColor, aIndicator}: DiffOptionsNormalized, ): string => aColor( - line.length !== 0 ? aSymbol + ' ' + replaceSpacesAtEnd(line) : aSymbol, + line.length !== 0 + ? aIndicator + ' ' + replaceSpacesAtEnd(line) + : aIndicator, ); export const printInsertLine = ( line: string, - {bColor, bSymbol}: DiffOptionsNormalized, + {bColor, bIndicator}: DiffOptionsNormalized, ): string => bColor( - line.length !== 0 ? bSymbol + ' ' + replaceSpacesAtEnd(line) : bSymbol, + line.length !== 0 + ? bIndicator + ' ' + replaceSpacesAtEnd(line) + : bIndicator, ); // Prevent visually ambiguous empty line as the first or the last. export const printCommonLine = ( line: string, isFirstOrLast: boolean, - {commonColor, commonSymbol}: DiffOptionsNormalized, + {commonColor, commonIndicator}: DiffOptionsNormalized, ): string => line.length !== 0 - ? commonColor(commonSymbol + ' ' + replaceSpacesAtEnd(line)) + ? commonColor(commonIndicator + ' ' + replaceSpacesAtEnd(line)) : isFirstOrLast - ? commonColor(commonSymbol + ' ' + NEWLINE_SYMBOL) + ? commonColor(commonIndicator + ' ' + NEWLINE_SYMBOL) : ''; export const hasCommonDiff = (diffs: Array, isMultiline: boolean) => { @@ -88,21 +98,73 @@ export const hasCommonDiff = (diffs: Array, isMultiline: boolean) => { return diffs.some(diff => diff[0] === DIFF_EQUAL); }; -export const printAnnotation = ({ - aAnnotation, - aColor, - aSymbol, - bAnnotation, - bColor, - bSymbol, - omitAnnotationLines, -}: DiffOptionsNormalized): string => - omitAnnotationLines - ? '' - : aColor(aSymbol + ' ' + aAnnotation) + - '\n' + - bColor(bSymbol + ' ' + bAnnotation) + - '\n\n'; +export type ChangeCounts = { + a: number; + b: number; +}; + +const countChanges = (diffs: Array): ChangeCounts => { + let a = 0; + let b = 0; + + diffs.forEach(diff => { + switch (diff[0]) { + case DIFF_DELETE: + a += 1; + break; + + case DIFF_INSERT: + b += 1; + break; + } + }); + + return {a, b}; +}; + +export const printAnnotation = ( + { + aAnnotation, + aColor, + aIndicator, + bAnnotation, + bColor, + bIndicator, + includeChangeCounts, + omitAnnotationLines, + }: DiffOptionsNormalized, + changeCounts: ChangeCounts, +): string => { + if (omitAnnotationLines) { + return ''; + } + + let aRest = ''; + let bRest = ''; + + if (includeChangeCounts) { + const aCount = String(changeCounts.a); + const bCount = String(changeCounts.b); + + const aPadding = + Math.max(bAnnotation.length - aAnnotation.length, 0) + + Math.max(bCount.length - aCount.length, 0); + const bPadding = + Math.max(aAnnotation.length - bAnnotation.length, 0) + + Math.max(aCount.length - bCount.length, 0); + + // Separate annotation from count by padding plus margin of 2 spaces. + aRest = ' '.repeat(aPadding + 2) + aCount + ' ' + aIndicator; + bRest = ' '.repeat(bPadding + 2) + bCount + ' ' + bIndicator; + } + + return ( + aColor(aIndicator + ' ' + aAnnotation + aRest) + + '\n' + + bColor(bIndicator + ' ' + bAnnotation + bRest) + + '\n\n' + ); +}; // In GNU diff format, indexes are one-based instead of zero-based. export const createPatchMark = ( @@ -126,32 +188,43 @@ export const diffStringsUnified = ( if (a.length === 0 || b.length === 0) { const lines: Array = []; + const changeCounts: ChangeCounts = { + a: 0, + b: 0, + }; - // All comparison lines have aColor and aSymbol. if (a.length !== 0) { + // All comparison lines have aColor and aIndicator. a.split('\n').forEach(line => { lines.push(printDeleteLine(line, optionsNormalized)); }); + changeCounts.a = lines.length; } - // All comparison lines have bColor and bSymbol. if (b.length !== 0) { + // All comparison lines have bColor and bIndicator. b.split('\n').forEach(line => { lines.push(printInsertLine(line, optionsNormalized)); }); + changeCounts.b = lines.length; } - // If both are empty strings, there are no comparison lines. - return printAnnotation(optionsNormalized) + lines.join('\n'); + // Else if both are empty strings, there are no comparison lines. + + return printAnnotation(optionsNormalized, changeCounts) + lines.join('\n'); } if (a === b) { const lines = a.split('\n'); const iLast = lines.length - 1; + const changeCounts = { + a: 0, + b: 0, + }; - // All comparison lines have commonColor and commonSymbol. + // All comparison lines have commonColor and commonIndicator. return ( - printAnnotation(optionsNormalized) + + printAnnotation(optionsNormalized, changeCounts) + lines .map((line, i) => printCommonLine(line, i === 0 || i === iLast, optionsNormalized), @@ -172,7 +245,7 @@ export const diffStringsUnified = ( if (hasCommonDiff(diffs, isMultiline)) { const lines = getAlignedDiffs(diffs); return ( - printAnnotation(optionsNormalized) + + printAnnotation(optionsNormalized, countChanges(lines)) + (optionsNormalized.expand ? joinAlignedDiffsExpand(lines, optionsNormalized) : joinAlignedDiffsNoExpand(lines, optionsNormalized)) diff --git a/packages/jest-diff/src/types.ts b/packages/jest-diff/src/types.ts index 2ffd17280fdd..5647286e30fe 100644 --- a/packages/jest-diff/src/types.ts +++ b/packages/jest-diff/src/types.ts @@ -10,27 +10,29 @@ type DiffOptionsColor = (arg: string) => string; // subset of Chalk type export type DiffOptions = { aAnnotation?: string; aColor?: DiffOptionsColor; - aSymbol?: string; + aIndicator?: string; bAnnotation?: string; bColor?: DiffOptionsColor; - bSymbol?: string; + bIndicator?: string; commonColor?: DiffOptionsColor; - commonSymbol?: string; + commonIndicator?: string; contextLines?: number; expand?: boolean; + includeChangeCounts?: boolean; omitAnnotationLines?: boolean; }; export type DiffOptionsNormalized = { aAnnotation: string; aColor: DiffOptionsColor; - aSymbol: string; + aIndicator: string; bAnnotation: string; bColor: DiffOptionsColor; - bSymbol: string; + bIndicator: string; commonColor: DiffOptionsColor; - commonSymbol: string; + commonIndicator: string; contextLines: number; expand: boolean; + includeChangeCounts: boolean; omitAnnotationLines: boolean; };