From d375c26366954936f3b25c10b2b605a3ad257fd4 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 16 Aug 2019 15:33:30 -0400 Subject: [PATCH 01/16] jest-diff: Add options for colors and symbols --- packages/jest-diff/README.md | 239 ++++++++++++++++++ .../__tests__/__snapshots__/diff.test.ts.snap | 181 ++++++++++++- packages/jest-diff/src/__tests__/diff.test.ts | 186 +++++++++++++- .../src/__tests__/getAlignedDiffs.test.ts | 14 +- .../src/__tests__/joinAlignedDiffs.test.ts | 31 ++- packages/jest-diff/src/diffLines.ts | 118 ++++++--- packages/jest-diff/src/getAlignedDiffs.ts | 8 +- packages/jest-diff/src/index.ts | 23 +- packages/jest-diff/src/joinAlignedDiffs.ts | 27 +- .../jest-diff/src/normalizeDiffOptions.ts | 34 +++ packages/jest-diff/src/printDiffs.ts | 210 ++++++++------- packages/jest-diff/src/types.ts | 23 +- .../printDiffOrStringify.test.ts.snap | 15 ++ .../__tests__/printDiffOrStringify.test.ts | 12 + packages/jest-matcher-utils/src/index.ts | 45 ++-- .../printDiffOrStringified.test.ts.snap | 32 +++ .../__tests__/printDiffOrStringified.test.ts | 30 +++ packages/jest-snapshot/src/print.ts | 58 +++-- 18 files changed, 1073 insertions(+), 213 deletions(-) create mode 100644 packages/jest-diff/README.md create mode 100644 packages/jest-diff/src/normalizeDiffOptions.ts diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md new file mode 100644 index 000000000000..2b435a01b319 --- /dev/null +++ b/packages/jest-diff/README.md @@ -0,0 +1,239 @@ +# jest-diff + +Display differences clearly so people can review changes confidently. + +The default export serializes JavaScript **values** and compares them line-by-line. + +Two named exports compare **strings** character-by-character: + +- `diffStringsAligned` returns a string which includes comparison lines. +- `diffStringsUnaligned` returns an array of 2 strings. + +## Installation + +To add this package as a dependency of a project, run either of the following commands: + +- `npm install jest-diff` +- `yarn add jest-diff` + +## Usage of default export + +Given values and optional options, `diffLines(a, b, options?)` does the following: + +- **serialize** the values as strings using the `pretty-format` package +- **compare** the strings line-by-line using the `diff-sequences` package +- **format** the changed or common lines using the `chalk` package + +To use `diffLines` as the function name, write either of the following: + +- `const diffLines = require('jest-diff');` in a CommonJS module +- `import diffLines from 'jest-diff';` in an ECMAScript module + +### Example of default export + +```js +const a = ['delete', 'change from', 'common']; +const b = ['change to', 'insert', 'common']; + +const difference = diffLines(a, b); +``` + +The returned **string** consists of: + +- annotation lines which describe the change symbols with labels +- blank line +- comparison lines in which `Expected` lines are green, `Received` lines are red, and common lines are dim + +```diff +- Expected ++ Received + + Array [ +- "delete", +- "change from", ++ "change to", ++ "insert", + "common", + ] +``` + +### Edge cases of default export + +Here are edge cases for the return value: + +- `' Comparing two different types of values. …'` if the arguments have **different types** according to the `jest-get-type` package (instances of different classes have the same `'object'` type) +- `'Compared values have no visual difference.'` if the arguments have either **referential identity** according to `Object.is` method or **same serialization** according to the `pretty-format` package +- `null` if either argument is a so-called **asymmetric matcher** in Jasmine or Jest + +## Usage of diffStringsAligned + +Given strings and optional options, `diffStringsAligned(a, b, options?)` does the following: + +- **compare** the strings character-by-character using the `diff-sequences` package +- **clean up** small (often coincidental) common substrings, known as “chaff” +- **format** the changed or common lines using the `chalk` package + +Although the function is mainly for **multiline** strings, it compares any strings. + +Write either of the following: + +- `const {diffStringsAligned} = require('jest-diff');` in a CommonJS module +- `import {diffStringsAligned} from 'jest-diff';` in an ECMAScript module + +### Example of diffStringsAligned + +```js +const a = 'change from\ncommon'; +const b = 'change to\ncommon'; + +const difference = diffStringsAligned(a, b); +``` + +The returned **string** consists of: + +- annotation lines which describe the change symbols with labels +- blank line +- comparison lines in which **changed substrings** have **inverted** foreground and background colors (for example, `from` is white-on-green and `to` is white-on-red) + +```diff +- Expected ++ Received + +- change from ++ change to + common +``` + +### Edge cases of diffStringsAligned + +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) + +### Performance of diffStringsAligned + +To get the benefit of **changed substrings** within the comparison lines, a character-by-character comparison has a higher computational cost (in time and space) than a line-by-line comparison. + +If the input strings can have **arbitrary length**, we recommend that the calling code set a limit, beyond which it calls the default export instead. For example, Jest falls back to line-by-line comparison if either string has length greater than 20K characters. + +## Usage of diffStringsUnaligned + +Given strings, `diffStringsUnaligned(a, b)` does the following: + +- **compare** the strings character-by-character using the `diff-sequences` package +- **clean up** small (often coincidental) common substrings, known as “chaff” +- **format** the changed substrings using `inverse` from the `chalk` package + +Although the function is mainly for **one-line** strings, it compares any strings. + +Write either of the following: + +- `const {diffStringsUnaligned} = require('jest-diff');` in a CommonJS module +- `import {diffStringsUnaligned} from 'jest-diff';` in an ECMAScript module + +### Example of diffStringsUnaligned + +```js +const [a, b] = diffStringsUnaligned('change from', 'change to'); + +// a === 'change ' + chalk.inverse('from') +// b === 'change ' + chalk.inverse('to') +``` + +The returned **array** of **two strings** corresponds to the two arguments. + +Because the caller is responsible to format the returned strings, there are no options. + +## Options + +The default options are for the report when an assertion fails from the `expect` package used by Jest. + +For other applications, you can provide an options object as a third argument: + +- `diffLines(a, b, options)` +- `diffStringsAligned(a, b, options)` + +### Properties of options object + + +| name | default | +| :------------- | :------------ | +| `aAnnotation` | `'Expected'` | +| `aColor` | `chalk.green` | +| `aSymbol` | `'-'` | +| `bAnnotation` | `'Received'` | +| `bColor` | `chalk.red` | +| `bSymbol` | `'+'` | +| `commonColor` | `chalk.dim` | +| `commonSymbol` | `' '` | +| `contextLines` | `5` | +| `expand` | `true` | + +### Example of options for labels + +If the application is code modification, you might replace the labels: + +```js +const options = { + aAnnotation: 'Original', + bAnnotation: 'Modified', +}; +``` + +The `jest-diff` package does not assume that the 2 labels have equal length. + +### Example of options for colors + +For consistency with most diff tools, you might exchange the colors: + +```js +import chalk from 'chalk'; + +const options = { + aColor: chalk.red, + bColor: chalk.green, +}; +``` + +### Example of option to keep the default color + +The value of a color option is a function, which given a string, returns a string. + +For common lines to keep the default (usually black) color, you might provide an identity function: + +```js +const options = { + commonColor: line => line, +}; +``` + +### Example of options for symbols + +For consistency with the `diff` command, you might replace the symbols: + +```js +const options = { + aSymbol: '<', + bSymbol: '>', +}; +``` + +The `jest-diff` package assumes (but does not enforce) that the 3 symbols have equal length. + +### Example of options to limit common lines + +By default, the output includes all common lines. + +To emphasize the changes, you might limit the number of common “context” lines: + +```js +const options = { + contextLines: 1, + expand: false, +}; +``` + +A patch mark like `@@ -12,7 +12,9 @@` accounts for omitted common 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 d02b263a2476..96d0ad54d4ad 100644 --- a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap +++ b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap @@ -152,7 +152,7 @@ exports[`context number of lines: 2 1`] = ` }" `; -exports[`context number of lines: null (5 default) 1`] = ` +exports[`context number of lines: 3.1 (5 default) 1`] = ` "- Expected + Received @@ -169,6 +169,143 @@ exports[`context number of lines: null (5 default) 1`] = ` }" `; +exports[`context number of lines: undefined (5 default) 1`] = ` +"- Expected ++ Received + +@@ -6,9 +6,9 @@ + 4, + 5, + 6, + 7, + 8, +- 9, + 10, ++ 9, + ], + }" +`; + +exports[`diffStringsAligned edge cases empty both a and b 1`] = ` +"- Expected ++ Received + +" +`; + +exports[`diffStringsAligned edge cases empty only a 1`] = ` +"- Expected ++ Received + ++ one-line string" +`; + +exports[`diffStringsAligned edge cases empty only b 1`] = ` +"- Expected ++ Received + +- one-line string" +`; + +exports[`diffStringsAligned edge cases equal both non-empty 1`] = ` +"- Expected ++ Received + + one-line string" +`; + +exports[`diffStringsAligned edge cases multiline has no common after clean up chaff 1`] = ` +"- Expected ++ Received + +- delete +- two ++ insert ++ 2" +`; + +exports[`diffStringsAligned edge cases one-line has no common after clean up chaff 1`] = ` +"- Expected ++ Received + +- delete ++ insert" +`; + +exports[`diffStringsUnaligned edge cases empty both a and b 1`] = ` +Array [ + "", + "", +] +`; + +exports[`diffStringsUnaligned edge cases empty only a 1`] = ` +Array [ + "", + "one-line string", +] +`; + +exports[`diffStringsUnaligned edge cases empty only b 1`] = ` +Array [ + "one-line string", + "", +] +`; + +exports[`diffStringsUnaligned edge cases equal both non-empty 1`] = ` +Array [ + "one-line string", + "one-line string", +] +`; + +exports[`diffStringsUnaligned edge cases multiline has changes 1`] = ` +Array [ + "change from +common", + "change to +common", +] +`; + +exports[`diffStringsUnaligned edge cases multiline has no common after clean up chaff 1`] = ` +Array [ + "delete +two", + "insert +2", +] +`; + +exports[`diffStringsUnaligned edge cases one-line has no common after clean up chaff 1`] = ` +Array [ + "delete", + "insert", +] +`; + +exports[`diffStringsUnaligned normal cases change 1`] = ` +Array [ + "change from", + "change to", +] +`; + +exports[`diffStringsUnaligned normal cases delete 1`] = ` +Array [ + "delete common", + "common", +] +`; + +exports[`diffStringsUnaligned normal cases insert 1`] = ` +Array [ + "common", + "insert common", +] +`; + exports[`falls back to not call toJSON if it throws and then objects have differences 1`] = ` "- Expected + Received @@ -242,3 +379,45 @@ exports[`oneline strings 4`] = ` - line + oneline" `; + +exports[`options 7980 diff 1`] = ` +"- Original ++ Modified + +- \`\${Ti.App.name} \${Ti.App.version} \${Ti.Platform.name} \${Ti.Platform.version}\` ++ \`\${Ti.App.getName()} \${Ti.App.getVersion()} \${Ti.Platform.getName()} \${Ti.Platform.getVersion()}\`" +`; + +exports[`options 7980 diffStringsAligned 1`] = ` +"- Original ++ Modified + +- \`\${Ti.App.name} \${Ti.App.version} \${Ti.Platform.name} \${Ti.Platform.version}\` ++ \`\${Ti.App.getName()} \${Ti.App.getVersion()} \${Ti.Platform.getName()} \${Ti.Platform.getVersion()}\`" +`; + +exports[`options change symbols diff 1`] = ` +"< Expected +> Received + + Array [ +< \\"delete\\", +< \\"change from\\", +> \\"change to\\", +> \\"insert\\", + \\"common\\", + ]" +`; + +exports[`options common diff 1`] = ` +"- Expected ++ Received + += Array [ +- \\"delete\\", +- \\"change from\\", ++ \\"change to\\", ++ \\"insert\\", += \\"common\\", += ]" +`; diff --git a/packages/jest-diff/src/__tests__/diff.test.ts b/packages/jest-diff/src/__tests__/diff.test.ts index 090813e61f70..f5e8ef2105fa 100644 --- a/packages/jest-diff/src/__tests__/diff.test.ts +++ b/packages/jest-diff/src/__tests__/diff.test.ts @@ -5,9 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +import chalk from 'chalk'; import stripAnsi from 'strip-ansi'; import diff from '../'; +import {diffStringsAligned, diffStringsUnaligned} from '../printDiffs'; import {DiffOptions} from '../types'; const NO_DIFF_MESSAGE = 'Compared values have no visual difference.'; @@ -833,9 +835,13 @@ test('collapses big diffs to patch format', () => { describe('context', () => { const testDiffContextLines = (contextLines?: number) => { test(`number of lines: ${ - typeof contextLines === 'number' ? contextLines : 'null' + typeof contextLines === 'number' ? contextLines : 'undefined' } ${ - typeof contextLines !== 'number' || contextLines < 0 ? '(5 default)' : '' + typeof contextLines === 'number' && + Number.isSafeInteger(contextLines) && + contextLines >= 0 + ? '' + : '(5 default)' }`, () => { const result = diff( {test: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}, @@ -849,9 +855,177 @@ describe('context', () => { }); }; - testDiffContextLines(); // 5 by default - testDiffContextLines(2); - testDiffContextLines(1); + testDiffContextLines(-1); // (5 default) testDiffContextLines(0); - testDiffContextLines(-1); // Will use default + testDiffContextLines(1); + testDiffContextLines(2); + testDiffContextLines(3.1); // (5 default) + testDiffContextLines(); // (5 default) +}); + +describe('diffStringsAligned edge cases', () => { + test('empty both a and b', () => { + const a = ''; + const b = ''; + + expect(diffStringsAligned(a, b)).toMatchSnapshot(); + }); + + test('empty only a', () => { + const a = ''; + const b = 'one-line string'; + + expect(diffStringsAligned(a, b)).toMatchSnapshot(); + }); + + test('empty only b', () => { + const a = 'one-line string'; + const b = ''; + + expect(diffStringsAligned(a, b)).toMatchSnapshot(); + }); + + test('equal both non-empty', () => { + const a = 'one-line string'; + const b = 'one-line string'; + + expect(diffStringsAligned(a, b)).toMatchSnapshot(); + }); + + test('multiline has no common after clean up chaff', () => { + const a = 'delete\ntwo'; + const b = 'insert\n2'; + + expect(diffStringsAligned(a, b)).toMatchSnapshot(); + }); + + test('one-line has no common after clean up chaff', () => { + const a = 'delete'; + const b = 'insert'; + + expect(diffStringsAligned(a, b)).toMatchSnapshot(); + }); +}); + +describe('diffStringsUnaligned edge cases', () => { + test('empty both a and b', () => { + const a = ''; + const b = ''; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('empty only a', () => { + const a = ''; + const b = 'one-line string'; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('empty only b', () => { + const a = 'one-line string'; + const b = ''; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('equal both non-empty', () => { + const a = 'one-line string'; + const b = 'one-line string'; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('multiline has changes', () => { + const a = ['change from', 'common'].join('\n'); + const b = ['change to', 'common'].join('\n'); + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('multiline has no common after clean up chaff', () => { + const a = 'delete\ntwo'; + const b = 'insert\n2'; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('one-line has no common after clean up chaff', () => { + const a = 'delete'; + const b = 'insert'; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); +}); + +describe('diffStringsUnaligned normal cases', () => { + test('change', () => { + const a = 'change from'; + const b = 'change to'; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('delete', () => { + const a = 'delete common'; + const b = 'common'; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); + + test('insert', () => { + const a = 'common'; + const b = 'insert common'; + + expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); + }); +}); + +describe('options 7980', () => { + const a = + '`${Ti.App.name} ${Ti.App.version} ${Ti.Platform.name} ${Ti.Platform.version}`'; + const b = + '`${Ti.App.getName()} ${Ti.App.getVersion()} ${Ti.Platform.getName()} ${Ti.Platform.getVersion()}`'; + + const options = { + aAnnotation: 'Original', + aColor: chalk.red, + bAnnotation: 'Modified', + bColor: chalk.green, + }; + + test('diff', () => { + expect(diff(a, b, options)).toMatchSnapshot(); + }); + + test('diffStringsAligned', () => { + expect(diffStringsAligned(a, b, options)).toMatchSnapshot(); + }); +}); + +describe('options', () => { + const a = ['delete', 'change from', 'common']; + const b = ['change to', 'insert', 'common']; + + describe('change symbols', () => { + const options = { + aSymbol: '<', + bSymbol: '>', + }; + + test('diff', () => { + expect(diff(a, b, options)).toMatchSnapshot(); + }); + }); + + describe('common', () => { + const options = { + commonColor: line => line, + commonSymbol: '=', + }; + + test('diff', () => { + expect(diff(a, b, options)).toMatchSnapshot(); + }); + }); }); diff --git a/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts b/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts index 3eb197f45be3..60c685525df4 100644 --- a/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts +++ b/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts @@ -5,12 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import {computeStringDiffs, printMultilineStringDiffs} from '../printDiffs'; - -const testAlignedDiffs = (a: string, b: string): string => { - const {diffs} = computeStringDiffs(a, b); - return printMultilineStringDiffs(diffs, true); -}; +import {diffStringsAligned} from '../printDiffs'; + +// Slice annotation lines and blank line until there is an option to omit them. +const testAlignedDiffs = (a: string, b: string): string => + diffStringsAligned(a, b) + .split('\n') + .slice(3) + .join('\n'); describe('getAlignedDiffs', () => { describe('lines', () => { diff --git a/packages/jest-diff/src/__tests__/joinAlignedDiffs.test.ts b/packages/jest-diff/src/__tests__/joinAlignedDiffs.test.ts index f763b5f9de3e..51ceeb874390 100644 --- a/packages/jest-diff/src/__tests__/joinAlignedDiffs.test.ts +++ b/packages/jest-diff/src/__tests__/joinAlignedDiffs.test.ts @@ -11,6 +11,7 @@ import { joinAlignedDiffsExpand, joinAlignedDiffsNoExpand, } from '../joinAlignedDiffs'; +import {normalizeDiffOptions} from '../normalizeDiffOptions'; const diffsCommonStartEnd = [ new Diff(DIFF_EQUAL, ''), @@ -53,28 +54,46 @@ const diffsChangeStartEnd = [ describe('joinAlignedDiffsExpand', () => { test('first line is empty common', () => { - expect(joinAlignedDiffsExpand(diffsCommonStartEnd)).toMatchSnapshot(); + const options = normalizeDiffOptions(); + expect( + joinAlignedDiffsExpand(diffsCommonStartEnd, options), + ).toMatchSnapshot(); }); }); describe('joinAlignedDiffsNoExpand', () => { test('patch 0 with context 1 and change at start and end', () => { - expect(joinAlignedDiffsNoExpand(diffsChangeStartEnd, 1)).toMatchSnapshot(); + const options = normalizeDiffOptions({contextLines: 1, expand: false}); + expect( + joinAlignedDiffsNoExpand(diffsChangeStartEnd, options), + ).toMatchSnapshot(); }); test('patch 0 with context 5 and first line is empty common', () => { - expect(joinAlignedDiffsNoExpand(diffsCommonStartEnd)).toMatchSnapshot(); + const options = normalizeDiffOptions({expand: false}); + expect( + joinAlignedDiffsNoExpand(diffsCommonStartEnd, options), + ).toMatchSnapshot(); }); test('patch 1 with context 4 and last line is empty common', () => { - expect(joinAlignedDiffsNoExpand(diffsCommonStartEnd, 4)).toMatchSnapshot(); + const options = normalizeDiffOptions({contextLines: 4, expand: false}); + expect( + joinAlignedDiffsNoExpand(diffsCommonStartEnd, options), + ).toMatchSnapshot(); }); test('patch 2 with context 3', () => { - expect(joinAlignedDiffsNoExpand(diffsCommonStartEnd, 3)).toMatchSnapshot(); + const options = normalizeDiffOptions({contextLines: 3, expand: false}); + expect( + joinAlignedDiffsNoExpand(diffsCommonStartEnd, options), + ).toMatchSnapshot(); }); test('patch 3 with context 2 and omit excess common at start', () => { - expect(joinAlignedDiffsNoExpand(diffsCommonStartEnd, 2)).toMatchSnapshot(); + const options = normalizeDiffOptions({contextLines: 2, expand: false}); + expect( + joinAlignedDiffsNoExpand(diffsCommonStartEnd, options), + ).toMatchSnapshot(); }); }); diff --git a/packages/jest-diff/src/diffLines.ts b/packages/jest-diff/src/diffLines.ts index 155fef041c19..a718470caf65 100644 --- a/packages/jest-diff/src/diffLines.ts +++ b/packages/jest-diff/src/diffLines.ts @@ -9,18 +9,13 @@ import chalk, {Chalk} from 'chalk'; import diff, {Callbacks} from 'diff-sequences'; import {NO_DIFF_MESSAGE} from './constants'; import {createPatchMark, printAnnotation} from './printDiffs'; -import {DiffOptions} from './types'; - -const DIFF_CONTEXT_DEFAULT = 5; +import {DiffOptionsNormalized} from './types'; type Original = { a: string; b: string; }; -const fgDelete = chalk.green; -const fgInsert = chalk.red; -const fgCommon = chalk.dim; // common lines (even indentation same) const fgIndent = chalk.cyan; // common lines (only indentation different) const bgCommon = chalk.bgYellow; // edge spaces in common line (even indentation same) const bgInverse = chalk.inverse; // edge spaces in any other lines @@ -51,6 +46,7 @@ const formatDelete = ( aEnd: number, aLinesUn: Array, aLinesIn: Array, + {aColor, aSymbol}: DiffOptionsNormalized, put: Put, ) => { const highlightSpaces = getHighlightSpaces(aLinesUn !== aLinesIn); @@ -59,7 +55,9 @@ const formatDelete = ( const aLineIn = aLinesIn[aIndex]; const indentation = aLineIn.slice(0, aLineIn.length - aLineUn.length); - put(fgDelete('- ' + indentation + highlightSpaces(aLineUn, bgInverse))); + put( + aColor(aSymbol + ' ' + indentation + highlightSpaces(aLineUn, bgInverse)), + ); } }; @@ -69,6 +67,7 @@ const formatInsert = ( bEnd: number, bLinesUn: Array, bLinesIn: Array, + {bColor, bSymbol}: DiffOptionsNormalized, put: Put, ) => { const highlightSpaces = getHighlightSpaces(bLinesUn !== bLinesIn); @@ -77,7 +76,9 @@ const formatInsert = ( const bLineIn = bLinesIn[bIndex]; const indentation = bLineIn.slice(0, bLineIn.length - bLineUn.length); - put(fgInsert('+ ' + indentation + highlightSpaces(bLineUn, bgInverse))); + put( + bColor(bSymbol + ' ' + indentation + highlightSpaces(bLineUn, bgInverse)), + ); } }; @@ -91,6 +92,7 @@ const formatCommon = ( aLinesIn: Array, bLinesUn: Array, bLinesIn: Array, + {commonColor, commonSymbol}: DiffOptionsNormalized, put: Put, ) => { const highlightSpaces = getHighlightSpaces(bLinesUn !== bLinesIn); @@ -104,10 +106,10 @@ const formatCommon = ( // Color shows whether expected and received line has same indentation. const hasSameIndentation = aLinesIn[aCommon].length === bLineInLength; - const fg = hasSameIndentation ? fgCommon : fgIndent; + const fg = hasSameIndentation ? commonColor : fgIndent; const bg = hasSameIndentation ? bgCommon : bgInverse; - put(fg(' ' + indentation + highlightSpaces(bLineUn, bg))); + put(fg(commonSymbol + ' ' + indentation + highlightSpaces(bLineUn, bg))); } }; @@ -118,6 +120,7 @@ const diffExpand = ( bLinesUn: Array, aLinesIn: Array, bLinesIn: Array, + options: DiffOptionsNormalized, ): string => { const isCommon: Callbacks['isCommon'] = (aIndex, bIndex) => aLinesUn[aIndex] === bLinesUn[bIndex]; @@ -135,9 +138,18 @@ const diffExpand = ( aCommon, bCommon, ) => { - formatDelete(aStart, aCommon, aLinesUn, aLinesIn, put); - formatInsert(bStart, bCommon, bLinesUn, bLinesIn, put); - formatCommon(nCommon, aCommon, bCommon, aLinesIn, bLinesUn, bLinesIn, put); + formatDelete(aStart, aCommon, aLinesUn, aLinesIn, options, put); + formatInsert(bStart, bCommon, bLinesUn, bLinesIn, options, put); + formatCommon( + nCommon, + aCommon, + bCommon, + aLinesIn, + bLinesUn, + bLinesIn, + options, + put, + ); aStart = aCommon + nCommon; bStart = bCommon + nCommon; }; @@ -148,19 +160,12 @@ const diffExpand = ( diff(aLength, bLength, isCommon, foundSubsequence); // After the last common subsequence, format remaining change lines. - formatDelete(aStart, aLength, aLinesUn, aLinesIn, put); - formatInsert(bStart, bLength, bLinesUn, bLinesIn, put); + formatDelete(aStart, aLength, aLinesUn, aLinesIn, options, put); + formatInsert(bStart, bLength, bLinesUn, bLinesIn, options, put); return array.join('\n'); }; -const getContextLines = (options?: DiffOptions): number => - options && - typeof options.contextLines === 'number' && - options.contextLines >= 0 - ? options.contextLines - : DIFF_CONTEXT_DEFAULT; - // jest --no-expand // Return joined string of formatted diff for all change lines, // but if some common lines are omitted because there are more than the context, @@ -170,7 +175,7 @@ const diffNoExpand = ( bLinesUn: Array, aLinesIn: Array, bLinesIn: Array, - nContextLines: number, + options: DiffOptionsNormalized, ): string => { const isCommon: Callbacks['isCommon'] = (aIndex, bIndex) => aLinesUn[aIndex] === bLinesUn[bIndex]; @@ -184,6 +189,7 @@ const diffNoExpand = ( let isAtEnd = false; const aLength = aLinesUn.length; const bLength = bLinesUn.length; + const nContextLines = options.contextLines; const nContextLines2 = nContextLines + nContextLines; // Initialize the first patch for changes at the start, @@ -210,15 +216,24 @@ const diffNoExpand = ( aStart = aEndCommon - nLines; bStart = bEndCommon - nLines; - formatCommon(nLines, aStart, bStart, aLinesIn, bLinesUn, bLinesIn, put); + formatCommon( + nLines, + aStart, + bStart, + aLinesIn, + bLinesUn, + bLinesIn, + options, + put, + ); aEnd = aEndCommon; bEnd = bEndCommon; return; } // Format preceding change lines. - formatDelete(aEnd, aStartCommon, aLinesUn, aLinesIn, put); - formatInsert(bEnd, bStartCommon, bLinesUn, bLinesIn, put); + formatDelete(aEnd, aStartCommon, aLinesUn, aLinesIn, options, put); + formatInsert(bEnd, bStartCommon, bLinesUn, bLinesIn, options, put); aEnd = aStartCommon; bEnd = bStartCommon; @@ -228,14 +243,32 @@ const diffNoExpand = ( if (nCommon <= maxContextLines) { // The patch includes all lines in the common subsequence. - formatCommon(nCommon, aEnd, bEnd, aLinesIn, bLinesUn, bLinesIn, put); + formatCommon( + nCommon, + aEnd, + bEnd, + aLinesIn, + bLinesUn, + bLinesIn, + options, + put, + ); aEnd += nCommon; bEnd += nCommon; return; } // The patch ends because context is less than number of common lines. - formatCommon(nContextLines, aEnd, bEnd, aLinesIn, bLinesUn, bLinesIn, put); + formatCommon( + nContextLines, + aEnd, + bEnd, + aLinesIn, + bLinesUn, + bLinesIn, + options, + put, + ); aEnd += nContextLines; bEnd += nContextLines; @@ -250,7 +283,16 @@ const diffNoExpand = ( aStart = aEndCommon - nLines; bStart = bEndCommon - nLines; - formatCommon(nLines, aStart, bStart, aLinesIn, bLinesUn, bLinesIn, put); + formatCommon( + nLines, + aStart, + bStart, + aLinesIn, + bLinesUn, + bLinesIn, + options, + put, + ); aEnd = aEndCommon; bEnd = bEndCommon; } @@ -260,8 +302,8 @@ const diffNoExpand = ( // If no common subsequence or last was not at end, format remaining change lines. if (!isAtEnd) { - formatDelete(aEnd, aLength, aLinesUn, aLinesIn, put); - formatInsert(bEnd, bLength, bLinesUn, bLinesIn, put); + formatDelete(aEnd, aLength, aLinesUn, aLinesIn, options, put); + formatInsert(bEnd, bLength, bLinesUn, bLinesIn, options, put); aEnd = aLength; bEnd = bLength; } @@ -278,7 +320,7 @@ const diffNoExpand = ( export default ( a: string, b: string, - options?: DiffOptions, + options: DiffOptionsNormalized, original?: Original, ): string => { if (a === b) { @@ -310,14 +352,8 @@ export default ( return ( printAnnotation(options) + - (options && options.expand === false - ? diffNoExpand( - aLinesUn, - bLinesUn, - aLinesIn, - bLinesIn, - getContextLines(options), - ) - : diffExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn)) + (options.expand + ? diffExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn, options) + : diffNoExpand(aLinesUn, bLinesUn, aLinesIn, bLinesIn, options)) ); }; diff --git a/packages/jest-diff/src/getAlignedDiffs.ts b/packages/jest-diff/src/getAlignedDiffs.ts index 57dd52040a92..8c19a2e6625d 100644 --- a/packages/jest-diff/src/getAlignedDiffs.ts +++ b/packages/jest-diff/src/getAlignedDiffs.ts @@ -6,7 +6,7 @@ */ import {Diff, DIFF_DELETE, DIFF_INSERT} from './cleanupSemantic'; -import {MULTILINE_REGEXP, getHighlightedString} from './printDiffs'; +import {invertChangedSubstrings} from './printDiffs'; // Encapsulate change lines until either a common newline or the end. class ChangeBuffer { @@ -28,7 +28,7 @@ class ChangeBuffer { // Assume call only if line has at least one diff, // therefore an empty line must have a diff which has an empty string. this.lines.push( - new Diff(this.op, getHighlightedString(this.op, this.line)), + new Diff(this.op, invertChangedSubstrings(this.op, this.line)), ); this.line.length = 0; } @@ -46,7 +46,7 @@ class ChangeBuffer { align(diff: Diff): void { const string = diff[1]; - if (MULTILINE_REGEXP.test(string)) { + if (string.includes('\n')) { const substrings = string.split('\n'); const iLast = substrings.length - 1; substrings.forEach((substring, i) => { @@ -117,7 +117,7 @@ class CommonBuffer { const op = diff[0]; const string = diff[1]; - if (MULTILINE_REGEXP.test(string)) { + if (string.includes('\n')) { const substrings = string.split('\n'); const iLast = substrings.length - 1; substrings.forEach((substring, i) => { diff --git a/packages/jest-diff/src/index.ts b/packages/jest-diff/src/index.ts index 3b3f37bffa66..827c878d3e05 100644 --- a/packages/jest-diff/src/index.ts +++ b/packages/jest-diff/src/index.ts @@ -9,9 +9,10 @@ import prettyFormat from 'pretty-format'; import chalk from 'chalk'; import getType from 'jest-get-type'; import diffLines from './diffLines'; -import {getStringDiff} from './printDiffs'; +import {normalizeDiffOptions} from './normalizeDiffOptions'; +import {diffStringsAligned, diffStringsUnaligned} from './printDiffs'; import {NO_DIFF_MESSAGE, SIMILAR_MESSAGE} from './constants'; -import {DiffOptions as JestDiffOptions} from './types'; +import {DiffOptions as JestDiffOptions, DiffOptionsNormalized} from './types'; const { AsymmetricMatcher, @@ -78,25 +79,26 @@ function diff(a: any, b: any, options?: JestDiffOptions): string | null { return null; } + const optionsNormalized = normalizeDiffOptions(options); switch (aType) { case 'string': - return diffLines(a, b, options); + return diffLines(a, b, optionsNormalized); case 'boolean': case 'number': - return comparePrimitive(a, b, options); + return comparePrimitive(a, b, optionsNormalized); case 'map': - return compareObjects(sortMap(a), sortMap(b), options); + return compareObjects(sortMap(a), sortMap(b), optionsNormalized); case 'set': - return compareObjects(sortSet(a), sortSet(b), options); + return compareObjects(sortSet(a), sortSet(b), optionsNormalized); default: - return compareObjects(a, b, options); + return compareObjects(a, b, optionsNormalized); } } function comparePrimitive( a: number | boolean, b: number | boolean, - options?: JestDiffOptions, + options: DiffOptionsNormalized, ) { return diffLines( prettyFormat(a, FORMAT_OPTIONS), @@ -116,7 +118,7 @@ function sortSet(set: Set) { function compareObjects( a: Record, b: Record, - options?: JestDiffOptions, + options: DiffOptionsNormalized, ) { let diffMessage; let hasThrown = false; @@ -160,6 +162,7 @@ namespace diff { export type DiffOptions = JestDiffOptions; } -diff.getStringDiff = getStringDiff; +diff.diffStringsAligned = diffStringsAligned; +diff.diffStringsUnaligned = diffStringsUnaligned; export = diff; diff --git a/packages/jest-diff/src/joinAlignedDiffs.ts b/packages/jest-diff/src/joinAlignedDiffs.ts index 186f3c393e0b..db00e5720bc5 100644 --- a/packages/jest-diff/src/joinAlignedDiffs.ts +++ b/packages/jest-diff/src/joinAlignedDiffs.ts @@ -12,8 +12,7 @@ import { printDeleteLine, printInsertLine, } from './printDiffs'; - -const DIFF_CONTEXT_DEFAULT = 5; // same as diffLines +import {DiffOptionsNormalized} from './types'; // jest --no-expand // @@ -21,9 +20,10 @@ const DIFF_CONTEXT_DEFAULT = 5; // same as diffLines // return joined lines with diff formatting (and patch marks, if needed). export const joinAlignedDiffsNoExpand = ( diffs: Array, - nContextLines: number = DIFF_CONTEXT_DEFAULT, + options: DiffOptionsNormalized, ): string => { const iLength = diffs.length; + const nContextLines = options.contextLines; const nContextLines2 = nContextLines + nContextLines; // First pass: count output lines and see if it has patches. @@ -89,18 +89,18 @@ export const joinAlignedDiffsNoExpand = ( const pushCommonLine = (line: string): void => { const j = lines.length; - lines.push(printCommonLine(line, j === 0 || j === jLast)); + lines.push(printCommonLine(line, j === 0 || j === jLast, options)); aEnd += 1; bEnd += 1; }; const pushDeleteLine = (line: string): void => { - lines.push(printDeleteLine(line)); + lines.push(printDeleteLine(line, options)); aEnd += 1; }; const pushInsertLine = (line: string): void => { - lines.push(printInsertLine(line)); + lines.push(printInsertLine(line, options)); bEnd += 1; }; @@ -187,20 +187,27 @@ export const joinAlignedDiffsNoExpand = ( // // Given array of aligned strings with inverse highlight formatting, // return joined lines with diff formatting. -export const joinAlignedDiffsExpand = (diffs: Array) => +export const joinAlignedDiffsExpand = ( + diffs: Array, + options: DiffOptionsNormalized, +) => diffs .map((diff: Diff, i: number, diffs: Array): string => { const line = diff[1]; switch (diff[0]) { case DIFF_DELETE: - return printDeleteLine(line); + return printDeleteLine(line, options); case DIFF_INSERT: - return printInsertLine(line); + return printInsertLine(line, options); default: - return printCommonLine(line, i === 0 || i === diffs.length - 1); + return printCommonLine( + line, + i === 0 || i === diffs.length - 1, + options, + ); } }) .join('\n'); diff --git a/packages/jest-diff/src/normalizeDiffOptions.ts b/packages/jest-diff/src/normalizeDiffOptions.ts new file mode 100644 index 000000000000..2c4d4e0ac0c5 --- /dev/null +++ b/packages/jest-diff/src/normalizeDiffOptions.ts @@ -0,0 +1,34 @@ +import chalk from 'chalk'; + +import {DiffOptions, DiffOptionsNormalized} from './types'; + +const DIFF_CONTEXT_DEFAULT = 5; + +const OPTIONS_DEFAULT: DiffOptionsNormalized = { + aAnnotation: 'Expected', + aColor: chalk.green, + aSymbol: '-', + bAnnotation: 'Received', + bColor: chalk.red, + bSymbol: '+', + commonColor: chalk.dim, + commonSymbol: ' ', + contextLines: DIFF_CONTEXT_DEFAULT, + expand: true, +}; + +const getContextLines = (contextLines?: number): number => + typeof contextLines === 'number' && + Number.isSafeInteger(contextLines) && + contextLines >= 0 + ? contextLines + : DIFF_CONTEXT_DEFAULT; + +// Pure function returns options with all properties. +export const normalizeDiffOptions = ( + options: DiffOptions = {}, +): DiffOptionsNormalized => ({ + ...OPTIONS_DEFAULT, + ...options, + contextLines: getContextLines(options.contextLines), +}); diff --git a/packages/jest-diff/src/printDiffs.ts b/packages/jest-diff/src/printDiffs.ts index aa2eec6729ae..e7cbc38e7ce7 100644 --- a/packages/jest-diff/src/printDiffs.ts +++ b/packages/jest-diff/src/printDiffs.ts @@ -14,25 +14,27 @@ import { DIFF_INSERT, Diff, } from './cleanupSemantic'; +import diffLines from './diffLines'; import diffStrings from './diffStrings'; import getAlignedDiffs from './getAlignedDiffs'; import { joinAlignedDiffsExpand, joinAlignedDiffsNoExpand, } from './joinAlignedDiffs'; -import {DiffOptions} from './types'; +import {normalizeDiffOptions} from './normalizeDiffOptions'; +import {DiffOptions, DiffOptionsNormalized} from './types'; -export const DIM_COLOR = chalk.dim; -export const EXPECTED_COLOR = chalk.green; -export const INVERTED_COLOR = chalk.inverse; -export const RECEIVED_COLOR = chalk.red; +export const INVERTED_COLOR = chalk.inverse; // export for joinAlignedDiffs test const PATCH_COLOR = chalk.yellow; // Given change op and array of diffs, return concatenated string: // * include common strings // * include change strings which have argument op (inverse highlight) // * exclude change strings which have opposite op -export const getHighlightedString = (op: number, diffs: Array): string => +export const invertChangedSubstrings = ( + op: number, + diffs: Array, +): string => diffs.reduce( (reduced: string, diff: Diff): string => reduced + @@ -44,13 +46,11 @@ export const getHighlightedString = (op: number, diffs: Array): string => '', ); -export const getExpectedString = (diffs: Array): string => - getHighlightedString(DIFF_DELETE, diffs); - -export const getReceivedString = (diffs: Array): string => - getHighlightedString(DIFF_INSERT, diffs); +const getExpectedString = (diffs: Array): string => + invertChangedSubstrings(DIFF_DELETE, diffs); -export const MULTILINE_REGEXP = /\n/; +const getReceivedString = (diffs: Array): string => + invertChangedSubstrings(DIFF_INSERT, diffs); const NEWLINE_SYMBOL = '\u{21B5}'; // downwards arrow with corner leftwards const SPACE_SYMBOL = '\u{00B7}'; // middle dot @@ -60,36 +60,34 @@ const SPACE_SYMBOL = '\u{00B7}'; // middle dot const replaceSpacesAtEnd = (line: string): string => line.replace(/\s+$/, spaces => SPACE_SYMBOL.repeat(spaces.length)); -export const printDeleteLine = (line: string) => - EXPECTED_COLOR(line.length !== 0 ? '- ' + replaceSpacesAtEnd(line) : '-'); +export const printDeleteLine = ( + line: string, + {aColor, aSymbol}: DiffOptionsNormalized, +): string => + aColor( + line.length !== 0 ? aSymbol + ' ' + replaceSpacesAtEnd(line) : aSymbol, + ); -export const printInsertLine = (line: string) => - RECEIVED_COLOR(line.length !== 0 ? '+ ' + replaceSpacesAtEnd(line) : '+'); +export const printInsertLine = ( + line: string, + {bColor, bSymbol}: DiffOptionsNormalized, +): string => + bColor( + line.length !== 0 ? bSymbol + ' ' + replaceSpacesAtEnd(line) : bSymbol, + ); // Prevent visually ambiguous empty line as the first or the last. -export const printCommonLine = (line: string, isFirstOrLast: boolean = false) => +export const printCommonLine = ( + line: string, + isFirstOrLast: boolean, + {commonColor, commonSymbol}: DiffOptionsNormalized, +): string => line.length !== 0 - ? DIM_COLOR(' ' + replaceSpacesAtEnd(line)) + ? commonColor(commonSymbol + ' ' + replaceSpacesAtEnd(line)) : isFirstOrLast - ? DIM_COLOR(' ' + NEWLINE_SYMBOL) + ? commonColor(commonSymbol + ' ' + NEWLINE_SYMBOL) : ''; -export const computeStringDiffs = (expected: string, received: string) => { - const isMultiline = - MULTILINE_REGEXP.test(expected) || MULTILINE_REGEXP.test(received); - - // getAlignedDiffs assumes that a newline was appended to the strings. - if (isMultiline) { - expected += '\n'; - received += '\n'; - } - - const diffs = diffStrings(expected, received); - cleanupSemantic(diffs); // impure function - - return {diffs, isMultiline}; -}; - export const hasCommonDiff = (diffs: Array, isMultiline: boolean) => { if (isMultiline) { // Important: Ignore common newline that was appended to multiline strings! @@ -102,10 +100,17 @@ export const hasCommonDiff = (diffs: Array, isMultiline: boolean) => { return diffs.some(diff => diff[0] === DIFF_EQUAL); }; -export const printAnnotation = (options?: DiffOptions): string => - EXPECTED_COLOR('- ' + ((options && options.aAnnotation) || 'Expected')) + +export const printAnnotation = ({ + aAnnotation, + aColor, + aSymbol, + bAnnotation, + bColor, + bSymbol, +}: DiffOptionsNormalized): string => + aColor(aSymbol + ' ' + aAnnotation) + '\n' + - RECEIVED_COLOR('+ ' + ((options && options.bAnnotation) || 'Received')) + + bColor(bSymbol + ' ' + bAnnotation) + '\n\n'; // In GNU diff format, indexes are one-based instead of zero-based. @@ -119,63 +124,88 @@ export const createPatchMark = ( `@@ -${aStart + 1},${aEnd - aStart} +${bStart + 1},${bEnd - bStart} @@`, ); -// Return formatted diff lines without labels. -export const printMultilineStringDiffs = ( - diffs: Array, - expand: boolean, +// Given two string arguments, compare them character-by-character. +// Format as comparison lines in which changed substrings have inverse colors. +export const diffStringsAligned = ( + a: string, + b: string, + options?: DiffOptions, ): string => { - const lines = getAlignedDiffs(diffs); - return expand - ? joinAlignedDiffsExpand(lines) - : joinAlignedDiffsNoExpand(lines); -}; + const optionsNormalized = normalizeDiffOptions(options); + + if (a.length === 0 || b.length === 0) { + const lines: Array = []; + + // All comparison lines have aColor and aSymbol. + if (a.length !== 0) { + a.split('\n').forEach(line => { + lines.push(printDeleteLine(line, optionsNormalized)); + }); + } + + // All comparison lines have bColor and bSymbol. + if (b.length !== 0) { + b.split('\n').forEach(line => { + lines.push(printInsertLine(line, optionsNormalized)); + }); + } + + // If both are empty strings, there are no comparison lines. + return printAnnotation(optionsNormalized) + lines.join('\n'); + } -const MAX_DIFF_STRING_LENGTH = 20000; - -type StringDiffResult = - | {isMultiline: true; annotatedDiff: string} - | {isMultiline: false; a: string; b: string} - | null; - -// Print specific substring diff for strings only: -// * if strings are not equal -// * if neither string is empty -// * if neither string is too long -// * if there is a common string after semantic cleanup -export const getStringDiff = ( - expected: string, - received: string, - options?: DiffOptions, -): StringDiffResult => { - if ( - expected === received || - expected.length === 0 || - received.length === 0 || - expected.length > MAX_DIFF_STRING_LENGTH || - received.length > MAX_DIFF_STRING_LENGTH - ) { - return null; + if (a === b) { + const lines = a.split('\n'); + const iLast = lines.length - 1; + + // All comparison lines have commonColor and commonSymbol. + return ( + printAnnotation(optionsNormalized) + + lines + .map((line, i) => + printCommonLine(line, i === 0 || i === iLast, optionsNormalized), + ) + .join('\n') + ); } - const {diffs, isMultiline} = computeStringDiffs(expected, received); + const isMultiline = a.includes('\n') || b.includes('\n'); + + // getAlignedDiffs assumes that a newline was appended to the strings. + const diffs = diffStrings( + isMultiline ? a + '\n' : a, + isMultiline ? b + '\n' : b, + ); + cleanupSemantic(diffs); // impure function + + if (hasCommonDiff(diffs, isMultiline)) { + const lines = getAlignedDiffs(diffs); + return ( + printAnnotation(optionsNormalized) + + (optionsNormalized.expand + ? joinAlignedDiffsExpand(lines, optionsNormalized) + : joinAlignedDiffsNoExpand(lines, optionsNormalized)) + ); + } + + // Fall back to line-by-line diff. + // Given strings, it returns a string, not null. + return diffLines(a, b, optionsNormalized) as string; +}; + +// Given two string arguments, compare them character-by-character. +// Format the changed substrings using inverse from the chalk package. +// Return an array of two strings which correspond to the two arguments. +export const diffStringsUnaligned = ( + a: string, + b: string, +): [string, string] => { + const diffs = diffStrings(a, b); + cleanupSemantic(diffs); // impure function - if (!hasCommonDiff(diffs, isMultiline)) { - return null; + if (hasCommonDiff(diffs, false)) { + return [getExpectedString(diffs), getReceivedString(diffs)]; } - return isMultiline - ? { - annotatedDiff: - printAnnotation(options) + - printMultilineStringDiffs( - diffs, - options === undefined || options.expand !== false, - ), - isMultiline, - } - : { - a: getExpectedString(diffs), - b: getReceivedString(diffs), - isMultiline, - }; + return [a, b]; }; diff --git a/packages/jest-diff/src/types.ts b/packages/jest-diff/src/types.ts index 66edf604d0c0..90d4c9f60cd7 100644 --- a/packages/jest-diff/src/types.ts +++ b/packages/jest-diff/src/types.ts @@ -5,9 +5,30 @@ * LICENSE file in the root directory of this source tree. */ +type DiffOptionsColor = (arg: string) => string; // subset of Chalk type + export type DiffOptions = { aAnnotation?: string; + aColor?: DiffOptionsColor; + aSymbol?: string; bAnnotation?: string; - expand?: boolean; + bColor?: DiffOptionsColor; + bSymbol?: string; + commonColor?: DiffOptionsColor; + commonSymbol?: string; contextLines?: number; + expand?: boolean; +}; + +export type DiffOptionsNormalized = { + aAnnotation: string; + aColor: DiffOptionsColor; + aSymbol: string; + bAnnotation: string; + bColor: DiffOptionsColor; + bSymbol: string; + commonColor: DiffOptionsColor; + commonSymbol: string; + contextLines: number; + expand: boolean; }; diff --git a/packages/jest-matcher-utils/src/__tests__/__snapshots__/printDiffOrStringify.test.ts.snap b/packages/jest-matcher-utils/src/__tests__/__snapshots__/printDiffOrStringify.test.ts.snap index 4a197fa3dce2..ccb387fcd85c 100644 --- a/packages/jest-matcher-utils/src/__tests__/__snapshots__/printDiffOrStringify.test.ts.snap +++ b/packages/jest-matcher-utils/src/__tests__/__snapshots__/printDiffOrStringify.test.ts.snap @@ -27,3 +27,18 @@ exports[`printDiffOrStringify expected is multi line and received is empty 1`] = line\\" Received: \\"\\"" `; + +exports[`printDiffOrStringify has no common after clean up chaff multiline 1`] = ` +"- Expected ++ Received + +- delete +- two ++ insert ++ 2" +`; + +exports[`printDiffOrStringify has no common after clean up chaff one-line 1`] = ` +"Expected: \\"delete\\" +Received: \\"insert\\"" +`; diff --git a/packages/jest-matcher-utils/src/__tests__/printDiffOrStringify.test.ts b/packages/jest-matcher-utils/src/__tests__/printDiffOrStringify.test.ts index a5c513405301..a8e985dd5329 100644 --- a/packages/jest-matcher-utils/src/__tests__/printDiffOrStringify.test.ts +++ b/packages/jest-matcher-utils/src/__tests__/printDiffOrStringify.test.ts @@ -35,6 +35,18 @@ describe('printDiffOrStringify', () => { expect(testDiffOrStringify(expected, received)).toMatchSnapshot(); }); + test('has no common after clean up chaff multiline', () => { + const expected = 'delete\ntwo'; + const received = 'insert\n2'; + expect(testDiffOrStringify(expected, received)).toMatchSnapshot(); + }); + + test('has no common after clean up chaff one-line', () => { + const expected = 'delete'; + const received = 'insert'; + expect(testDiffOrStringify(expected, received)).toMatchSnapshot(); + }); + test('received is multiline longer than max', () => { const expected = 'multi\nline'; const received = 'multi' + '\n123456789'.repeat(2000); // 5 + 20K chars diff --git a/packages/jest-matcher-utils/src/index.ts b/packages/jest-matcher-utils/src/index.ts index 984f031c871a..d9c38a7ea75a 100644 --- a/packages/jest-matcher-utils/src/index.ts +++ b/packages/jest-matcher-utils/src/index.ts @@ -6,7 +6,11 @@ */ import chalk from 'chalk'; -import jestDiff, {DiffOptions, getStringDiff} from 'jest-diff'; +import jestDiff, { + DiffOptions, + diffStringsAligned, + diffStringsUnaligned, +} from 'jest-diff'; import getType, {isPrimitive} from 'jest-get-type'; import prettyFormat from 'pretty-format'; @@ -262,6 +266,8 @@ const isLineDiffable = (expected: unknown, received: unknown): boolean => { return true; }; +const MAX_DIFF_STRING_LENGTH = 20000; + export const printDiffOrStringify = ( expected: unknown, received: unknown, @@ -269,24 +275,31 @@ export const printDiffOrStringify = ( receivedLabel: string, expand: boolean, // CLI options: true if `--expand` or false if `--no-expand` ): string => { - if (typeof expected === 'string' && typeof received === 'string') { - const result = getStringDiff(expected, received, { - aAnnotation: expectedLabel, - bAnnotation: receivedLabel, - expand, - }); + if ( + typeof expected === 'string' && + typeof received === 'string' && + expected.length !== 0 && + received.length !== 0 && + expected.length <= MAX_DIFF_STRING_LENGTH && + received.length <= MAX_DIFF_STRING_LENGTH && + expected !== received + ) { + if (expected.includes('\n') || received.includes('\n')) { + return diffStringsAligned(expected, received, { + aAnnotation: expectedLabel, + bAnnotation: receivedLabel, + expand, + }); + } - if (result !== null) { - if (result.isMultiline) { - return result.annotatedDiff; - } + // Format the changed substrings using inverse from the chalk package. + const [expected2, received2] = diffStringsUnaligned(expected, received); - const printLabel = getLabelPrinter(expectedLabel, receivedLabel); - const expectedLine = printLabel(expectedLabel) + printExpected(result.a); - const receivedLine = printLabel(receivedLabel) + printReceived(result.b); + const printLabel = getLabelPrinter(expectedLabel, receivedLabel); + const expectedLine = printLabel(expectedLabel) + printExpected(expected2); + const receivedLine = printLabel(receivedLabel) + printReceived(received2); - return expectedLine + '\n' + receivedLine; - } + return expectedLine + '\n' + receivedLine; } if (isLineDiffable(expected, received)) { diff --git a/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap b/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap index 8c7ff72d1c25..87ec74f50b4c 100644 --- a/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap +++ b/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap @@ -105,6 +105,23 @@ exports[`fallback to line diff 1`] = ` + ================================================================================ `; +exports[`has no common after clean up chaff array 1`] = ` +- Snapshot ++ Received + + Array [ +- "delete", +- "two", ++ "insert", ++ "2", + ] +`; + +exports[`has no common after clean up chaff string single line 1`] = ` +Snapshot: "delete" +Received: "insert" +`; + exports[`isLineDiffable false boolean 1`] = ` Snapshot: true Received: false @@ -213,6 +230,21 @@ Snapshot: var foo = \`backtick\`; Received: var foo = \`back\${x}tick\`; `; +exports[`without serialize has no common after clean up chaff multi line 1`] = ` +- Snapshot ++ Received + +- delete +- two ++ insert ++ 2 +`; + +exports[`without serialize has no common after clean up chaff single line 1`] = ` +Snapshot: delete +Received: insert +`; + exports[`without serialize prettier/pull/5590 1`] = ` - Snapshot + Received diff --git a/packages/jest-snapshot/src/__tests__/printDiffOrStringified.test.ts b/packages/jest-snapshot/src/__tests__/printDiffOrStringified.test.ts index 3907dc1b9564..0a863c23e4aa 100644 --- a/packages/jest-snapshot/src/__tests__/printDiffOrStringified.test.ts +++ b/packages/jest-snapshot/src/__tests__/printDiffOrStringified.test.ts @@ -231,6 +231,22 @@ test('fallback to line diff', () => { expect(testWithSerialize(expected, received, false)).toMatchSnapshot(); }); +describe('has no common after clean up chaff', () => { + test('array', () => { + const expected = ['delete', 'two']; + const received = ['insert', '2']; + + expect(testWithSerialize(expected, received, false)).toMatchSnapshot(); + }); + + test('string single line', () => { + const expected = 'delete'; + const received = 'insert'; + + expect(testWithSerialize(expected, received, false)).toMatchSnapshot(); + }); +}); + describe('isLineDiffable', () => { describe('false', () => { test('boolean', () => { @@ -361,6 +377,20 @@ describe('without serialize', () => { expect(testWithoutSerialize(expected, received, false)).toMatchSnapshot(); }); + test('has no common after clean up chaff multi line', () => { + const expected = 'delete\ntwo'; + const received = 'insert\n2'; + + expect(testWithoutSerialize(expected, received, false)).toMatchSnapshot(); + }); + + test('has no common after clean up chaff single line', () => { + const expected = 'delete'; + const received = 'insert'; + + expect(testWithoutSerialize(expected, received, false)).toMatchSnapshot(); + }); + test('prettier/pull/5590', () => { const expected = [ '====================================options=====================================', diff --git a/packages/jest-snapshot/src/print.ts b/packages/jest-snapshot/src/print.ts index bd4aacd7cf38..aff299a27d72 100644 --- a/packages/jest-snapshot/src/print.ts +++ b/packages/jest-snapshot/src/print.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import diff, {getStringDiff} from 'jest-diff'; +import diff, {diffStringsAligned, diffStringsUnaligned} from 'jest-diff'; import getType, {isPrimitive} from 'jest-get-type'; import { EXPECTED_COLOR, @@ -45,6 +45,8 @@ const isLineDiffable = (received: any): boolean => { return true; }; +const MAX_DIFF_STRING_LENGTH = 20000; + export const printDiffOrStringified = ( expectedSerializedTrimmed: string, receivedSerializedTrimmed: string, @@ -77,33 +79,45 @@ export const printDiffOrStringified = ( } // Display substring highlight even when strings have custom serialization. - const result = getStringDiff( - expectedSerializedTrimmed, - receivedSerializedTrimmed, - { - aAnnotation: expectedLabel, - bAnnotation: receivedLabel, - expand, - }, - ); - - if (result !== null) { - if (result.isMultiline) { - return result.annotatedDiff; + if ( + expectedSerializedTrimmed.length !== 0 && + receivedSerializedTrimmed.length !== 0 && + expectedSerializedTrimmed.length <= MAX_DIFF_STRING_LENGTH && + receivedSerializedTrimmed.length <= MAX_DIFF_STRING_LENGTH && + expectedSerializedTrimmed !== receivedSerializedTrimmed + ) { + if ( + expectedSerializedTrimmed.includes('\n') || + receivedSerializedTrimmed.includes('\n') + ) { + return diffStringsAligned( + expectedSerializedTrimmed, + receivedSerializedTrimmed, + { + aAnnotation: expectedLabel, + bAnnotation: receivedLabel, + expand, + }, + ); } + // Format the changed substrings using inverse from the chalk package. + const [expected2, received2] = diffStringsUnaligned( + expectedSerializedTrimmed, + receivedSerializedTrimmed, + ); + // Because not default stringify, call EXPECTED_COLOR and RECEIVED_COLOR - // This is reason to call getStringDiff instead of printDiffOrStringify + // This is reason to call diffStrings2 instead of printDiffOrStringify // Because there is no closing double quote mark at end of single lines, // future improvement is to call replaceSpacesAtEnd if it becomes public. const printLabel = getLabelPrinter(expectedLabel, receivedLabel); - return ( - printLabel(expectedLabel) + - EXPECTED_COLOR(result.a) + - '\n' + - printLabel(receivedLabel) + - RECEIVED_COLOR(result.b) - ); + const expectedLine = + printLabel(expectedLabel) + EXPECTED_COLOR(expected2); + const receivedLine = + printLabel(receivedLabel) + RECEIVED_COLOR(received2); + + return expectedLine + '\n' + receivedLine; } } From 707fa69ea90480ad648de18f665ba0a50e211116 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 16 Aug 2019 15:41:08 -0400 Subject: [PATCH 02/16] Fix prettier lint errors in README --- packages/jest-diff/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index 2b435a01b319..aed1b690e028 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -61,7 +61,7 @@ The returned **string** consists of: Here are edge cases for the return value: -- `' Comparing two different types of values. …'` if the arguments have **different types** according to the `jest-get-type` package (instances of different classes have the same `'object'` type) +- `' Comparing two different types of values. …'` if the arguments have **different types** according to the `jest-get-type` package (instances of different classes have the same `'object'` type) - `'Compared values have no visual difference.'` if the arguments have either **referential identity** according to `Object.is` method or **same serialization** according to the `pretty-format` package - `null` if either argument is a so-called **asymmetric matcher** in Jasmine or Jest @@ -236,4 +236,4 @@ const options = { }; ``` -A patch mark like `@@ -12,7 +12,9 @@` accounts for omitted common lines. +A patch mark like `@@ -12,7 +12,9 @@` accounts for omitted common lines. From 664f191098b861fb30c47cf726e65a76eb9e49bc Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 16 Aug 2019 15:43:22 -0400 Subject: [PATCH 03/16] Delete unneeded prettier-ignore comment from README --- packages/jest-diff/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index aed1b690e028..cf7501e32d52 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -158,7 +158,6 @@ For other applications, you can provide an options object as a third argument: ### Properties of options object - | name | default | | :------------- | :------------ | | `aAnnotation` | `'Expected'` | From c23118b56ca7ae611320cbc48327064883c6abfb Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 16 Aug 2019 15:56:08 -0400 Subject: [PATCH 04/16] Edit name of function in comment --- packages/jest-snapshot/src/print.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-snapshot/src/print.ts b/packages/jest-snapshot/src/print.ts index aff299a27d72..454602d462ac 100644 --- a/packages/jest-snapshot/src/print.ts +++ b/packages/jest-snapshot/src/print.ts @@ -108,7 +108,7 @@ export const printDiffOrStringified = ( ); // Because not default stringify, call EXPECTED_COLOR and RECEIVED_COLOR - // This is reason to call diffStrings2 instead of printDiffOrStringify + // This is reason to call diffStringsUnaligned instead of printDiffOrStringify // Because there is no closing double quote mark at end of single lines, // future improvement is to call replaceSpacesAtEnd if it becomes public. const printLabel = getLabelPrinter(expectedLabel, receivedLabel); From 0246ae0b620fb837c426b6b8380d896f3e67b03c Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 16 Aug 2019 16:11:28 -0400 Subject: [PATCH 05/16] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd152ef8534..4df99bb476ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[jest-diff]` Add options for colors and symbols ([#8841](https://github.com/facebook/jest/pull/8841)) + ### Fixes ### Chore & Maintenance From ddee70f4d4e3430479b4dc0afb076d1a20f7acdc Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 16 Aug 2019 16:19:43 -0400 Subject: [PATCH 06/16] Add copyright and license comment to added source file --- packages/jest-diff/src/normalizeDiffOptions.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/jest-diff/src/normalizeDiffOptions.ts b/packages/jest-diff/src/normalizeDiffOptions.ts index 2c4d4e0ac0c5..b22c85fd1d78 100644 --- a/packages/jest-diff/src/normalizeDiffOptions.ts +++ b/packages/jest-diff/src/normalizeDiffOptions.ts @@ -1,3 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + import chalk from 'chalk'; import {DiffOptions, DiffOptionsNormalized} from './types'; From d03a394fcf4ce3390f3b1a492b548f97c8478c88 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Sun, 18 Aug 2019 14:35:51 -0400 Subject: [PATCH 07/16] Update packages/jest-diff/README.md Co-Authored-By: Simen Bekkhus --- packages/jest-diff/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index cf7501e32d52..3cb01682e9a6 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -24,7 +24,7 @@ Given values and optional options, `diffLines(a, b, options?)` does the followin - **compare** the strings line-by-line using the `diff-sequences` package - **format** the changed or common lines using the `chalk` package -To use `diffLines` as the function name, write either of the following: +To use this function, write either of the following: - `const diffLines = require('jest-diff');` in a CommonJS module - `import diffLines from 'jest-diff';` in an ECMAScript module From bf2338362bf47da632e8df902a4c15fa987ea844 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 19 Aug 2019 10:01:04 -0400 Subject: [PATCH 08/16] Fix lint error sort-imports --- packages/jest-diff/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-diff/src/index.ts b/packages/jest-diff/src/index.ts index 827c878d3e05..3732e8810216 100644 --- a/packages/jest-diff/src/index.ts +++ b/packages/jest-diff/src/index.ts @@ -12,7 +12,7 @@ import diffLines from './diffLines'; import {normalizeDiffOptions} from './normalizeDiffOptions'; import {diffStringsAligned, diffStringsUnaligned} from './printDiffs'; import {NO_DIFF_MESSAGE, SIMILAR_MESSAGE} from './constants'; -import {DiffOptions as JestDiffOptions, DiffOptionsNormalized} from './types'; +import {DiffOptionsNormalized, DiffOptions as JestDiffOptions} from './types'; const { AsymmetricMatcher, From 9a4a7f7e651f531b31f792a92c7225323a517cd1 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 19 Aug 2019 12:18:10 -0400 Subject: [PATCH 09/16] Rename diffStringsAligned as diffStringsUnified --- CHANGELOG.md | 1 + packages/jest-diff/README.md | 34 +++---- .../__tests__/__snapshots__/diff.test.ts.snap | 94 +++++++++---------- packages/jest-diff/src/__tests__/diff.test.ts | 20 ++-- .../src/__tests__/getAlignedDiffs.test.ts | 4 +- packages/jest-diff/src/index.ts | 4 +- packages/jest-diff/src/printDiffs.ts | 2 +- packages/jest-matcher-utils/src/index.ts | 4 +- packages/jest-snapshot/src/print.ts | 4 +- 9 files changed, 84 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e53842a55e..f9da43fbeb1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - `[jest-fake-timers]` `getTimerCount` will not include cancelled immediates ([#8764](https://github.com/facebook/jest/pull/8764)) ### Chore & Maintenance + - `[docs]` Fix broken link pointing to legacy JS file in "Snapshot Testing". ### Performance diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index 3cb01682e9a6..e77578ff13cb 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -6,7 +6,7 @@ The default export serializes JavaScript **values** and compares them line-by-li Two named exports compare **strings** character-by-character: -- `diffStringsAligned` returns a string which includes comparison lines. +- `diffStringsUnified` returns a string which includes comparison lines. - `diffStringsUnaligned` returns an array of 2 strings. ## Installation @@ -18,7 +18,7 @@ To add this package as a dependency of a project, run either of the following co ## Usage of default export -Given values and optional options, `diffLines(a, b, options?)` does the following: +Given values and optional options, `diffLinesUnified(a, b, options?)` does the following: - **serialize** the values as strings using the `pretty-format` package - **compare** the strings line-by-line using the `diff-sequences` package @@ -26,8 +26,8 @@ Given values and optional options, `diffLines(a, b, options?)` does the followin To use this function, write either of the following: -- `const diffLines = require('jest-diff');` in a CommonJS module -- `import diffLines from 'jest-diff';` in an ECMAScript module +- `const diffLinesUnified = require('jest-diff');` in a CommonJS module +- `import diffLinesUnified from 'jest-diff';` in an ECMAScript module ### Example of default export @@ -35,14 +35,14 @@ To use this function, write either of the following: const a = ['delete', 'change from', 'common']; const b = ['change to', 'insert', 'common']; -const difference = diffLines(a, b); +const difference = diffLinesUnified(a, b); ``` The returned **string** consists of: - annotation lines which describe the change symbols with labels - blank line -- comparison lines in which `Expected` lines are green, `Received` lines are red, and common lines are dim +- 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 - Expected @@ -65,9 +65,9 @@ Here are edge cases for the return value: - `'Compared values have no visual difference.'` if the arguments have either **referential identity** according to `Object.is` method or **same serialization** according to the `pretty-format` package - `null` if either argument is a so-called **asymmetric matcher** in Jasmine or Jest -## Usage of diffStringsAligned +## Usage of diffStringsUnified -Given strings and optional options, `diffStringsAligned(a, b, options?)` does the following: +Given strings and optional options, `diffStringsUnified(a, b, options?)` does the following: - **compare** the strings character-by-character using the `diff-sequences` package - **clean up** small (often coincidental) common substrings, known as “chaff” @@ -77,23 +77,23 @@ Although the function is mainly for **multiline** strings, it compares any strin Write either of the following: -- `const {diffStringsAligned} = require('jest-diff');` in a CommonJS module -- `import {diffStringsAligned} from 'jest-diff';` in an ECMAScript module +- `const {diffStringsUnified} = require('jest-diff');` in a CommonJS module +- `import {diffStringsUnified} from 'jest-diff';` in an ECMAScript module -### Example of diffStringsAligned +### Example of diffStringsUnified ```js const a = 'change from\ncommon'; const b = 'change to\ncommon'; -const difference = diffStringsAligned(a, b); +const difference = diffStringsUnified(a, b); ``` The returned **string** consists of: - annotation lines which describe the change symbols with labels - blank line -- comparison lines in which **changed substrings** have **inverted** foreground and background colors (for example, `from` is white-on-green and `to` is white-on-red) +- comparison lines: similar to “unified” view on GitHub, and **changed substrings** have **inverted** foreground and background colors ```diff - Expected @@ -104,7 +104,7 @@ The returned **string** consists of: common ``` -### Edge cases of diffStringsAligned +### Edge cases of diffStringsUnified Here are edge cases for the return value: @@ -113,7 +113,7 @@ Here are edge cases for the return value: - 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) -### Performance of diffStringsAligned +### Performance of diffStringsUnified To get the benefit of **changed substrings** within the comparison lines, a character-by-character comparison has a higher computational cost (in time and space) than a line-by-line comparison. @@ -153,8 +153,8 @@ The default options are for the report when an assertion fails from the `expect` For other applications, you can provide an options object as a third argument: -- `diffLines(a, b, options)` -- `diffStringsAligned(a, b, options)` +- `diffLinesUnified(a, b, options)` +- `diffStringsUnified(a, b, options)` ### Properties of options object 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 96d0ad54d4ad..f59912baeb3b 100644 --- a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap +++ b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap @@ -186,52 +186,6 @@ exports[`context number of lines: undefined (5 default) 1`] = ` }" `; -exports[`diffStringsAligned edge cases empty both a and b 1`] = ` -"- Expected -+ Received - -" -`; - -exports[`diffStringsAligned edge cases empty only a 1`] = ` -"- Expected -+ Received - -+ one-line string" -`; - -exports[`diffStringsAligned edge cases empty only b 1`] = ` -"- Expected -+ Received - -- one-line string" -`; - -exports[`diffStringsAligned edge cases equal both non-empty 1`] = ` -"- Expected -+ Received - - one-line string" -`; - -exports[`diffStringsAligned edge cases multiline has no common after clean up chaff 1`] = ` -"- Expected -+ Received - -- delete -- two -+ insert -+ 2" -`; - -exports[`diffStringsAligned edge cases one-line has no common after clean up chaff 1`] = ` -"- Expected -+ Received - -- delete -+ insert" -`; - exports[`diffStringsUnaligned edge cases empty both a and b 1`] = ` Array [ "", @@ -306,6 +260,52 @@ Array [ ] `; +exports[`diffStringsUnified edge cases empty both a and b 1`] = ` +"- Expected ++ Received + +" +`; + +exports[`diffStringsUnified edge cases empty only a 1`] = ` +"- Expected ++ Received + ++ one-line string" +`; + +exports[`diffStringsUnified edge cases empty only b 1`] = ` +"- Expected ++ Received + +- one-line string" +`; + +exports[`diffStringsUnified edge cases equal both non-empty 1`] = ` +"- Expected ++ Received + + one-line string" +`; + +exports[`diffStringsUnified edge cases multiline has no common after clean up chaff 1`] = ` +"- Expected ++ Received + +- delete +- two ++ insert ++ 2" +`; + +exports[`diffStringsUnified edge cases one-line has no common after clean up chaff 1`] = ` +"- Expected ++ Received + +- delete ++ insert" +`; + exports[`falls back to not call toJSON if it throws and then objects have differences 1`] = ` "- Expected + Received @@ -388,7 +388,7 @@ exports[`options 7980 diff 1`] = ` + \`\${Ti.App.getName()} \${Ti.App.getVersion()} \${Ti.Platform.getName()} \${Ti.Platform.getVersion()}\`" `; -exports[`options 7980 diffStringsAligned 1`] = ` +exports[`options 7980 diffStringsUnified 1`] = ` "- Original + Modified diff --git a/packages/jest-diff/src/__tests__/diff.test.ts b/packages/jest-diff/src/__tests__/diff.test.ts index f5e8ef2105fa..30bbe0ec7db5 100644 --- a/packages/jest-diff/src/__tests__/diff.test.ts +++ b/packages/jest-diff/src/__tests__/diff.test.ts @@ -9,7 +9,7 @@ import chalk from 'chalk'; import stripAnsi from 'strip-ansi'; import diff from '../'; -import {diffStringsAligned, diffStringsUnaligned} from '../printDiffs'; +import {diffStringsUnified, diffStringsUnaligned} from '../printDiffs'; import {DiffOptions} from '../types'; const NO_DIFF_MESSAGE = 'Compared values have no visual difference.'; @@ -863,47 +863,47 @@ describe('context', () => { testDiffContextLines(); // (5 default) }); -describe('diffStringsAligned edge cases', () => { +describe('diffStringsUnified edge cases', () => { test('empty both a and b', () => { const a = ''; const b = ''; - expect(diffStringsAligned(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b)).toMatchSnapshot(); }); test('empty only a', () => { const a = ''; const b = 'one-line string'; - expect(diffStringsAligned(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b)).toMatchSnapshot(); }); test('empty only b', () => { const a = 'one-line string'; const b = ''; - expect(diffStringsAligned(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b)).toMatchSnapshot(); }); test('equal both non-empty', () => { const a = 'one-line string'; const b = 'one-line string'; - expect(diffStringsAligned(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b)).toMatchSnapshot(); }); test('multiline has no common after clean up chaff', () => { const a = 'delete\ntwo'; const b = 'insert\n2'; - expect(diffStringsAligned(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b)).toMatchSnapshot(); }); test('one-line has no common after clean up chaff', () => { const a = 'delete'; const b = 'insert'; - expect(diffStringsAligned(a, b)).toMatchSnapshot(); + expect(diffStringsUnified(a, b)).toMatchSnapshot(); }); }); @@ -998,8 +998,8 @@ describe('options 7980', () => { expect(diff(a, b, options)).toMatchSnapshot(); }); - test('diffStringsAligned', () => { - expect(diffStringsAligned(a, b, options)).toMatchSnapshot(); + test('diffStringsUnified', () => { + expect(diffStringsUnified(a, b, options)).toMatchSnapshot(); }); }); diff --git a/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts b/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts index 60c685525df4..5da95c716b28 100644 --- a/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts +++ b/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts @@ -5,11 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import {diffStringsAligned} from '../printDiffs'; +import {diffStringsUnified} from '../printDiffs'; // Slice annotation lines and blank line until there is an option to omit them. const testAlignedDiffs = (a: string, b: string): string => - diffStringsAligned(a, b) + diffStringsUnified(a, b) .split('\n') .slice(3) .join('\n'); diff --git a/packages/jest-diff/src/index.ts b/packages/jest-diff/src/index.ts index 3732e8810216..b63fd807a4fc 100644 --- a/packages/jest-diff/src/index.ts +++ b/packages/jest-diff/src/index.ts @@ -10,7 +10,7 @@ import chalk from 'chalk'; import getType from 'jest-get-type'; import diffLines from './diffLines'; import {normalizeDiffOptions} from './normalizeDiffOptions'; -import {diffStringsAligned, diffStringsUnaligned} from './printDiffs'; +import {diffStringsUnified, diffStringsUnaligned} from './printDiffs'; import {NO_DIFF_MESSAGE, SIMILAR_MESSAGE} from './constants'; import {DiffOptionsNormalized, DiffOptions as JestDiffOptions} from './types'; @@ -162,7 +162,7 @@ namespace diff { export type DiffOptions = JestDiffOptions; } -diff.diffStringsAligned = diffStringsAligned; +diff.diffStringsUnified = diffStringsUnified; diff.diffStringsUnaligned = diffStringsUnaligned; export = diff; diff --git a/packages/jest-diff/src/printDiffs.ts b/packages/jest-diff/src/printDiffs.ts index 48562d41afec..62846b9b7d97 100644 --- a/packages/jest-diff/src/printDiffs.ts +++ b/packages/jest-diff/src/printDiffs.ts @@ -126,7 +126,7 @@ export const createPatchMark = ( // Given two string arguments, compare them character-by-character. // Format as comparison lines in which changed substrings have inverse colors. -export const diffStringsAligned = ( +export const diffStringsUnified = ( a: string, b: string, options?: DiffOptions, diff --git a/packages/jest-matcher-utils/src/index.ts b/packages/jest-matcher-utils/src/index.ts index d9c38a7ea75a..a889bdcbb7e3 100644 --- a/packages/jest-matcher-utils/src/index.ts +++ b/packages/jest-matcher-utils/src/index.ts @@ -8,7 +8,7 @@ import chalk from 'chalk'; import jestDiff, { DiffOptions, - diffStringsAligned, + diffStringsUnified, diffStringsUnaligned, } from 'jest-diff'; import getType, {isPrimitive} from 'jest-get-type'; @@ -285,7 +285,7 @@ export const printDiffOrStringify = ( expected !== received ) { if (expected.includes('\n') || received.includes('\n')) { - return diffStringsAligned(expected, received, { + return diffStringsUnified(expected, received, { aAnnotation: expectedLabel, bAnnotation: receivedLabel, expand, diff --git a/packages/jest-snapshot/src/print.ts b/packages/jest-snapshot/src/print.ts index 454602d462ac..f8b225aee752 100644 --- a/packages/jest-snapshot/src/print.ts +++ b/packages/jest-snapshot/src/print.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import diff, {diffStringsAligned, diffStringsUnaligned} from 'jest-diff'; +import diff, {diffStringsUnified, diffStringsUnaligned} from 'jest-diff'; import getType, {isPrimitive} from 'jest-get-type'; import { EXPECTED_COLOR, @@ -90,7 +90,7 @@ export const printDiffOrStringified = ( expectedSerializedTrimmed.includes('\n') || receivedSerializedTrimmed.includes('\n') ) { - return diffStringsAligned( + return diffStringsUnified( expectedSerializedTrimmed, receivedSerializedTrimmed, { From 43b2d4cad993fce717fe9ede31bd2ffa0f164751 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 19 Aug 2019 14:20:10 -0400 Subject: [PATCH 10/16] Remove diffStringsUnaligned call from jest-snapshot --- .../printDiffOrStringified.test.ts.snap | 14 +++++-- packages/jest-snapshot/src/print.ts | 37 ++++--------------- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap b/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap index 87ec74f50b4c..7c00b08fe37b 100644 --- a/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap +++ b/packages/jest-snapshot/src/__tests__/__snapshots__/printDiffOrStringified.test.ts.snap @@ -226,8 +226,11 @@ exports[`without serialize backtick single line expected and multi line received `; exports[`without serialize backtick single line expected and received 1`] = ` -Snapshot: var foo = \`backtick\`; -Received: var foo = \`back\${x}tick\`; +- Snapshot ++ Received + +- var foo = \`backtick\`; ++ var foo = \`back\${x}tick\`; `; exports[`without serialize has no common after clean up chaff multi line 1`] = ` @@ -241,8 +244,11 @@ exports[`without serialize has no common after clean up chaff multi line 1`] = ` `; exports[`without serialize has no common after clean up chaff single line 1`] = ` -Snapshot: delete -Received: insert +- Snapshot ++ Received + +- delete ++ insert `; exports[`without serialize prettier/pull/5590 1`] = ` diff --git a/packages/jest-snapshot/src/print.ts b/packages/jest-snapshot/src/print.ts index f8b225aee752..4c8c91221e1f 100644 --- a/packages/jest-snapshot/src/print.ts +++ b/packages/jest-snapshot/src/print.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import diff, {diffStringsUnified, diffStringsUnaligned} from 'jest-diff'; +import diff, {diffStringsUnified} from 'jest-diff'; import getType, {isPrimitive} from 'jest-get-type'; import { EXPECTED_COLOR, @@ -86,38 +86,15 @@ export const printDiffOrStringified = ( receivedSerializedTrimmed.length <= MAX_DIFF_STRING_LENGTH && expectedSerializedTrimmed !== receivedSerializedTrimmed ) { - if ( - expectedSerializedTrimmed.includes('\n') || - receivedSerializedTrimmed.includes('\n') - ) { - return diffStringsUnified( - expectedSerializedTrimmed, - receivedSerializedTrimmed, - { - aAnnotation: expectedLabel, - bAnnotation: receivedLabel, - expand, - }, - ); - } - - // Format the changed substrings using inverse from the chalk package. - const [expected2, received2] = diffStringsUnaligned( + return diffStringsUnified( expectedSerializedTrimmed, receivedSerializedTrimmed, + { + aAnnotation: expectedLabel, + bAnnotation: receivedLabel, + expand, + }, ); - - // Because not default stringify, call EXPECTED_COLOR and RECEIVED_COLOR - // This is reason to call diffStringsUnaligned instead of printDiffOrStringify - // Because there is no closing double quote mark at end of single lines, - // future improvement is to call replaceSpacesAtEnd if it becomes public. - const printLabel = getLabelPrinter(expectedLabel, receivedLabel); - const expectedLine = - printLabel(expectedLabel) + EXPECTED_COLOR(expected2); - const receivedLine = - printLabel(receivedLabel) + RECEIVED_COLOR(received2); - - return expectedLine + '\n' + receivedLine; } } From 7829d513cc8347855f7080ff499be14649e4c24d Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 19 Aug 2019 15:36:15 -0400 Subject: [PATCH 11/16] Replace diffStringsUnaligned with diffStringsRaw --- packages/jest-diff/README.md | 62 +++++++++++---- .../__tests__/__snapshots__/diff.test.ts.snap | 74 ------------------ packages/jest-diff/src/__tests__/diff.test.ts | 76 +------------------ packages/jest-diff/src/index.ts | 14 +++- packages/jest-diff/src/printDiffs.ts | 30 +++----- packages/jest-matcher-utils/src/index.ts | 45 +++++++++-- 6 files changed, 108 insertions(+), 193 deletions(-) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index e77578ff13cb..06dde842d994 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -7,7 +7,7 @@ The default export serializes JavaScript **values** and compares them line-by-li Two named exports compare **strings** character-by-character: - `diffStringsUnified` returns a string which includes comparison lines. -- `diffStringsUnaligned` returns an array of 2 strings. +- `diffStringsRaw` returns an array of `Diff` objects. ## Installation @@ -70,7 +70,7 @@ Here are edge cases for the return value: Given strings and optional options, `diffStringsUnified(a, b, options?)` does the following: - **compare** the strings character-by-character using the `diff-sequences` package -- **clean up** small (often coincidental) common substrings, known as “chaff” +- **clean up** small (often coincidental) common substrings, also known as chaff - **format** the changed or common lines using the `chalk` package Although the function is mainly for **multiline** strings, it compares any strings. @@ -119,33 +119,63 @@ To get the benefit of **changed substrings** within the comparison lines, a char If the input strings can have **arbitrary length**, we recommend that the calling code set a limit, beyond which it calls the default export instead. For example, Jest falls back to line-by-line comparison if either string has length greater than 20K characters. -## Usage of diffStringsUnaligned +## Usage of diffStringsRaw -Given strings, `diffStringsUnaligned(a, b)` does the following: +Given strings, `diffStringsRaw(a, b, cleanup)` does the following: - **compare** the strings character-by-character using the `diff-sequences` package -- **clean up** small (often coincidental) common substrings, known as “chaff” -- **format** the changed substrings using `inverse` from the `chalk` package +- optionally **clean up** small (often coincidental) common substrings, also known as chaff -Although the function is mainly for **one-line** strings, it compares any strings. +Write one of the following: -Write either of the following: +- `const {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diffStringsRaw} = require('jest-diff');` in a CommonJS module +- `import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diffStringsRaw} from 'jest-diff';` in an ECMAScript module +- `import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff, diffStringsRaw} from 'jest-diff';` in an TypeScript module + +The returned **array** describes substrings as instances of the `Diff` class (which calling code can access like array tuples). -- `const {diffStringsUnaligned} = require('jest-diff');` in a CommonJS module -- `import {diffStringsUnaligned} from 'jest-diff';` in an ECMAScript module +| value | named export | description | +| ----: | :------------ | :-------------------- | +| `0` | `DIFF_EQUAL` | in `a` and `b` | +| `-1` | `DIFF_DELETE` | in `a` but not in `b` | +| `1` | `DIFF_INSERT` | in `b` but not in `a` | -### Example of diffStringsUnaligned +### Example of diffStringsRaw with cleanup ```js -const [a, b] = diffStringsUnaligned('change from', 'change to'); +const diffs = diffStringsRaw('change from', 'change to', true); + +// diffs[0][0] === DIFF_EQUAL +// diffs[0][1] === 'change ' -// a === 'change ' + chalk.inverse('from') -// b === 'change ' + chalk.inverse('to') +// diffs[1][0] === DIFF_DELETE +// diffs[1][1] === 'from' + +// diffs[2][0] === DIFF_INSERT +// diffs[2][1] === 'to' ``` -The returned **array** of **two strings** corresponds to the two arguments. +### Example of diffStringsRaw without cleanup + +```js +const diffs = diffStringsRaw('change from', 'change to', false); -Because the caller is responsible to format the returned strings, there are no options. +// diffs[0][0] === DIFF_EQUAL +// diffs[0][1] === 'change ' + +// diffs[1][0] === DIFF_DELETE +// diffs[1][1] === 'fr' + +// diffs[2][0] === DIFF_INSERT +// diffs[2][1] === 't' + +// Here is a small coincidental common substring: +// diffs[3][0] === DIFF_EQUAL +// diffs[3][1] === 'o' + +// diffs[4][0] === DIFF_DELETE +// diffs[4][1] === 'm' +``` ## Options 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 f59912baeb3b..3b5c7ef26206 100644 --- a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap +++ b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap @@ -186,80 +186,6 @@ exports[`context number of lines: undefined (5 default) 1`] = ` }" `; -exports[`diffStringsUnaligned edge cases empty both a and b 1`] = ` -Array [ - "", - "", -] -`; - -exports[`diffStringsUnaligned edge cases empty only a 1`] = ` -Array [ - "", - "one-line string", -] -`; - -exports[`diffStringsUnaligned edge cases empty only b 1`] = ` -Array [ - "one-line string", - "", -] -`; - -exports[`diffStringsUnaligned edge cases equal both non-empty 1`] = ` -Array [ - "one-line string", - "one-line string", -] -`; - -exports[`diffStringsUnaligned edge cases multiline has changes 1`] = ` -Array [ - "change from -common", - "change to -common", -] -`; - -exports[`diffStringsUnaligned edge cases multiline has no common after clean up chaff 1`] = ` -Array [ - "delete -two", - "insert -2", -] -`; - -exports[`diffStringsUnaligned edge cases one-line has no common after clean up chaff 1`] = ` -Array [ - "delete", - "insert", -] -`; - -exports[`diffStringsUnaligned normal cases change 1`] = ` -Array [ - "change from", - "change to", -] -`; - -exports[`diffStringsUnaligned normal cases delete 1`] = ` -Array [ - "delete common", - "common", -] -`; - -exports[`diffStringsUnaligned normal cases insert 1`] = ` -Array [ - "common", - "insert common", -] -`; - exports[`diffStringsUnified edge cases empty both a and b 1`] = ` "- Expected + Received diff --git a/packages/jest-diff/src/__tests__/diff.test.ts b/packages/jest-diff/src/__tests__/diff.test.ts index 30bbe0ec7db5..0ad705569f41 100644 --- a/packages/jest-diff/src/__tests__/diff.test.ts +++ b/packages/jest-diff/src/__tests__/diff.test.ts @@ -9,7 +9,7 @@ import chalk from 'chalk'; import stripAnsi from 'strip-ansi'; import diff from '../'; -import {diffStringsUnified, diffStringsUnaligned} from '../printDiffs'; +import {diffStringsUnified} from '../printDiffs'; import {DiffOptions} from '../types'; const NO_DIFF_MESSAGE = 'Compared values have no visual difference.'; @@ -907,80 +907,6 @@ describe('diffStringsUnified edge cases', () => { }); }); -describe('diffStringsUnaligned edge cases', () => { - test('empty both a and b', () => { - const a = ''; - const b = ''; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('empty only a', () => { - const a = ''; - const b = 'one-line string'; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('empty only b', () => { - const a = 'one-line string'; - const b = ''; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('equal both non-empty', () => { - const a = 'one-line string'; - const b = 'one-line string'; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('multiline has changes', () => { - const a = ['change from', 'common'].join('\n'); - const b = ['change to', 'common'].join('\n'); - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('multiline has no common after clean up chaff', () => { - const a = 'delete\ntwo'; - const b = 'insert\n2'; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('one-line has no common after clean up chaff', () => { - const a = 'delete'; - const b = 'insert'; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); -}); - -describe('diffStringsUnaligned normal cases', () => { - test('change', () => { - const a = 'change from'; - const b = 'change to'; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('delete', () => { - const a = 'delete common'; - const b = 'common'; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); - - test('insert', () => { - const a = 'common'; - const b = 'insert common'; - - expect(diffStringsUnaligned(a, b)).toMatchSnapshot(); - }); -}); - describe('options 7980', () => { const a = '`${Ti.App.name} ${Ti.App.version} ${Ti.Platform.name} ${Ti.Platform.version}`'; diff --git a/packages/jest-diff/src/index.ts b/packages/jest-diff/src/index.ts index b63fd807a4fc..a574c9c61bfa 100644 --- a/packages/jest-diff/src/index.ts +++ b/packages/jest-diff/src/index.ts @@ -8,9 +8,15 @@ import prettyFormat from 'pretty-format'; import chalk from 'chalk'; import getType from 'jest-get-type'; +import { + DIFF_DELETE, + DIFF_EQUAL, + DIFF_INSERT, + Diff as DiffClass, +} from './cleanupSemantic'; import diffLines from './diffLines'; import {normalizeDiffOptions} from './normalizeDiffOptions'; -import {diffStringsUnified, diffStringsUnaligned} from './printDiffs'; +import {diffStringsRaw, diffStringsUnified} from './printDiffs'; import {NO_DIFF_MESSAGE, SIMILAR_MESSAGE} from './constants'; import {DiffOptionsNormalized, DiffOptions as JestDiffOptions} from './types'; @@ -159,10 +165,14 @@ function compareObjects( // eslint-disable-next-line no-redeclare namespace diff { + export type Diff = DiffClass; export type DiffOptions = JestDiffOptions; } diff.diffStringsUnified = diffStringsUnified; -diff.diffStringsUnaligned = diffStringsUnaligned; +diff.diffStringsRaw = diffStringsRaw; +diff.DIFF_DELETE = DIFF_DELETE; +diff.DIFF_EQUAL = DIFF_EQUAL; +diff.DIFF_INSERT = DIFF_INSERT; export = diff; diff --git a/packages/jest-diff/src/printDiffs.ts b/packages/jest-diff/src/printDiffs.ts index 62846b9b7d97..520750619484 100644 --- a/packages/jest-diff/src/printDiffs.ts +++ b/packages/jest-diff/src/printDiffs.ts @@ -7,13 +7,7 @@ import chalk from 'chalk'; -import { - DIFF_DELETE, - DIFF_EQUAL, - DIFF_INSERT, - Diff, - cleanupSemantic, -} from './cleanupSemantic'; +import {DIFF_EQUAL, Diff, cleanupSemantic} from './cleanupSemantic'; import diffLines from './diffLines'; import diffStrings from './diffStrings'; import getAlignedDiffs from './getAlignedDiffs'; @@ -46,12 +40,6 @@ export const invertChangedSubstrings = ( '', ); -const getExpectedString = (diffs: Array): string => - invertChangedSubstrings(DIFF_DELETE, diffs); - -const getReceivedString = (diffs: Array): string => - invertChangedSubstrings(DIFF_INSERT, diffs); - const NEWLINE_SYMBOL = '\u{21B5}'; // downwards arrow with corner leftwards const SPACE_SYMBOL = '\u{00B7}'; // middle dot @@ -194,18 +182,18 @@ export const diffStringsUnified = ( }; // Given two string arguments, compare them character-by-character. -// Format the changed substrings using inverse from the chalk package. -// Return an array of two strings which correspond to the two arguments. -export const diffStringsUnaligned = ( +// Optionally clean up small common substrings, also known as chaff. +// Return an array of diff objects. +export const diffStringsRaw = ( a: string, b: string, -): [string, string] => { + cleanup: boolean, +): Array => { const diffs = diffStrings(a, b); - cleanupSemantic(diffs); // impure function - if (hasCommonDiff(diffs, false)) { - return [getExpectedString(diffs), getReceivedString(diffs)]; + if (cleanup) { + cleanupSemantic(diffs); // impure function } - return [a, b]; + return diffs; }; diff --git a/packages/jest-matcher-utils/src/index.ts b/packages/jest-matcher-utils/src/index.ts index a889bdcbb7e3..75508190b417 100644 --- a/packages/jest-matcher-utils/src/index.ts +++ b/packages/jest-matcher-utils/src/index.ts @@ -7,9 +7,13 @@ import chalk from 'chalk'; import jestDiff, { + DIFF_DELETE, + DIFF_EQUAL, + DIFF_INSERT, + Diff, DiffOptions, + diffStringsRaw, diffStringsUnified, - diffStringsUnaligned, } from 'jest-diff'; import getType, {isPrimitive} from 'jest-get-type'; import prettyFormat from 'pretty-format'; @@ -216,6 +220,29 @@ export const ensureExpectedIsNonNegativeInteger = ( } }; +// Given array of diffs, return concatenated string: +// * include common substrings +// * exclude change substrings which have opposite op +// * include change substrings which have argument op +// with inverse highlight only if there is a common substring +const getCommonAndChangedSubstrings = ( + diffs: Array, + op: number, + hasCommonDiff: boolean, +): string => + diffs.reduce( + (reduced: string, diff: Diff): string => + reduced + + (diff[0] === DIFF_EQUAL + ? diff[1] + : diff[0] !== op + ? '' + : hasCommonDiff + ? INVERTED_COLOR(diff[1]) + : diff[1]), + '', + ); + const isLineDiffable = (expected: unknown, received: unknown): boolean => { const expectedType = getType(expected); const receivedType = getType(received); @@ -292,12 +319,20 @@ export const printDiffOrStringify = ( }); } - // Format the changed substrings using inverse from the chalk package. - const [expected2, received2] = diffStringsUnaligned(expected, received); + const diffs = diffStringsRaw(expected, received, true); + const hasCommonDiff = diffs.some(diff => diff[0] === DIFF_EQUAL); const printLabel = getLabelPrinter(expectedLabel, receivedLabel); - const expectedLine = printLabel(expectedLabel) + printExpected(expected2); - const receivedLine = printLabel(receivedLabel) + printReceived(received2); + const expectedLine = + printLabel(expectedLabel) + + printExpected( + getCommonAndChangedSubstrings(diffs, DIFF_DELETE, hasCommonDiff), + ); + const receivedLine = + printLabel(receivedLabel) + + printReceived( + getCommonAndChangedSubstrings(diffs, DIFF_INSERT, hasCommonDiff), + ); return expectedLine + '\n' + receivedLine; } From 67b212745e3da1f4a0734a71cffd5a77454efff5 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 19 Aug 2019 16:34:09 -0400 Subject: [PATCH 12/16] Add omitAnnotationLines option --- packages/jest-diff/README.md | 29 ++++++++++--------- .../__tests__/__snapshots__/diff.test.ts.snap | 12 ++++++++ packages/jest-diff/src/__tests__/diff.test.ts | 14 +++++++++ .../src/__tests__/getAlignedDiffs.test.ts | 6 +--- .../jest-diff/src/normalizeDiffOptions.ts | 1 + packages/jest-diff/src/printDiffs.ts | 11 ++++--- packages/jest-diff/src/types.ts | 2 ++ 7 files changed, 52 insertions(+), 23 deletions(-) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index 06dde842d994..52778b885cfb 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -130,13 +130,13 @@ Write one of the following: - `const {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diffStringsRaw} = require('jest-diff');` in a CommonJS module - `import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diffStringsRaw} from 'jest-diff';` in an ECMAScript module -- `import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff, diffStringsRaw} from 'jest-diff';` in an TypeScript module +- `import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff, diffStringsRaw} from 'jest-diff';` in a TypeScript module The returned **array** describes substrings as instances of the `Diff` class (which calling code can access like array tuples). | value | named export | description | | ----: | :------------ | :-------------------- | -| `0` | `DIFF_EQUAL` | in `a` and `b` | +| `0` | `DIFF_EQUAL` | in `a` and in `b` | | `-1` | `DIFF_DELETE` | in `a` but not in `b` | | `1` | `DIFF_INSERT` | in `b` but not in `a` | @@ -188,18 +188,19 @@ For other applications, you can provide an options object as a third argument: ### Properties of options object -| name | default | -| :------------- | :------------ | -| `aAnnotation` | `'Expected'` | -| `aColor` | `chalk.green` | -| `aSymbol` | `'-'` | -| `bAnnotation` | `'Received'` | -| `bColor` | `chalk.red` | -| `bSymbol` | `'+'` | -| `commonColor` | `chalk.dim` | -| `commonSymbol` | `' '` | -| `contextLines` | `5` | -| `expand` | `true` | +| name | default | +| :-------------------- | :------------ | +| `aAnnotation` | `'Expected'` | +| `aColor` | `chalk.green` | +| `aSymbol` | `'-'` | +| `bAnnotation` | `'Received'` | +| `bColor` | `chalk.red` | +| `bSymbol` | `'+'` | +| `commonColor` | `chalk.dim` | +| `commonSymbol` | `' '` | +| `contextLines` | `5` | +| `expand` | `true` | +| `omitAnnotationLines` | `false` | ### Example of options for labels 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 3b5c7ef26206..c7e1b19669fc 100644 --- a/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap +++ b/packages/jest-diff/src/__tests__/__snapshots__/diff.test.ts.snap @@ -347,3 +347,15 @@ exports[`options common diff 1`] = ` = \\"common\\", = ]" `; + +exports[`options omitAnnotationLines diff 1`] = ` +" Array [ +- \\"delete\\", +- \\"change from\\", ++ \\"change to\\", ++ \\"insert\\", + \\"common\\", + ]" +`; + +exports[`options omitAnnotationLines 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 0ad705569f41..32d1b4f6d714 100644 --- a/packages/jest-diff/src/__tests__/diff.test.ts +++ b/packages/jest-diff/src/__tests__/diff.test.ts @@ -954,4 +954,18 @@ describe('options', () => { expect(diff(a, b, options)).toMatchSnapshot(); }); }); + + describe('omitAnnotationLines', () => { + const options = { + omitAnnotationLines: true, + }; + + test('diff', () => { + expect(diff(a, b, options)).toMatchSnapshot(); + }); + + test('diffStringsUnified empty strings', () => { + expect(diffStringsUnified('', '', options)).toMatchSnapshot(); + }); + }); }); diff --git a/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts b/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts index 5da95c716b28..df825a822c6a 100644 --- a/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts +++ b/packages/jest-diff/src/__tests__/getAlignedDiffs.test.ts @@ -7,12 +7,8 @@ import {diffStringsUnified} from '../printDiffs'; -// Slice annotation lines and blank line until there is an option to omit them. const testAlignedDiffs = (a: string, b: string): string => - diffStringsUnified(a, b) - .split('\n') - .slice(3) - .join('\n'); + diffStringsUnified(a, b, {omitAnnotationLines: true}); describe('getAlignedDiffs', () => { describe('lines', () => { diff --git a/packages/jest-diff/src/normalizeDiffOptions.ts b/packages/jest-diff/src/normalizeDiffOptions.ts index b22c85fd1d78..1430d7041d73 100644 --- a/packages/jest-diff/src/normalizeDiffOptions.ts +++ b/packages/jest-diff/src/normalizeDiffOptions.ts @@ -22,6 +22,7 @@ const OPTIONS_DEFAULT: DiffOptionsNormalized = { commonSymbol: ' ', contextLines: DIFF_CONTEXT_DEFAULT, expand: true, + omitAnnotationLines: false, }; const getContextLines = (contextLines?: number): number => diff --git a/packages/jest-diff/src/printDiffs.ts b/packages/jest-diff/src/printDiffs.ts index 520750619484..91e25bcca716 100644 --- a/packages/jest-diff/src/printDiffs.ts +++ b/packages/jest-diff/src/printDiffs.ts @@ -95,11 +95,14 @@ export const printAnnotation = ({ bAnnotation, bColor, bSymbol, + omitAnnotationLines, }: DiffOptionsNormalized): string => - aColor(aSymbol + ' ' + aAnnotation) + - '\n' + - bColor(bSymbol + ' ' + bAnnotation) + - '\n\n'; + omitAnnotationLines + ? '' + : aColor(aSymbol + ' ' + aAnnotation) + + '\n' + + bColor(bSymbol + ' ' + bAnnotation) + + '\n\n'; // In GNU diff format, indexes are one-based instead of zero-based. export const createPatchMark = ( diff --git a/packages/jest-diff/src/types.ts b/packages/jest-diff/src/types.ts index 90d4c9f60cd7..2ffd17280fdd 100644 --- a/packages/jest-diff/src/types.ts +++ b/packages/jest-diff/src/types.ts @@ -18,6 +18,7 @@ export type DiffOptions = { commonSymbol?: string; contextLines?: number; expand?: boolean; + omitAnnotationLines?: boolean; }; export type DiffOptionsNormalized = { @@ -31,4 +32,5 @@ export type DiffOptionsNormalized = { commonSymbol: string; contextLines: number; expand: boolean; + omitAnnotationLines: boolean; }; From 97d13bd7f79bc627525c96c116752f05c9d1e391 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Tue, 20 Aug 2019 10:29:25 -0400 Subject: [PATCH 13/16] Example of option to omit annotation lines --- packages/jest-diff/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index 52778b885cfb..8d50767e43bc 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -267,3 +267,23 @@ const options = { ``` A patch mark like `@@ -12,7 +12,9 @@` accounts for omitted common lines. + +### Example of option to omit annotation lines + +To display only the comparison lines: + +```js +const a = 'change from\ncommon'; +const b = 'change to\ncommon'; +const options = { + omitAnnotationLines: true, +}; + +const difference = diffStringsUnified(a, b, options); +``` + +```diff +- change from ++ change to + common +``` From a2ad22620871905de559faa73f0ebdbd82248c55 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 22 Aug 2019 10:43:44 -0400 Subject: [PATCH 14/16] Correct reference to exported Diff type --- packages/jest-matcher-utils/src/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/jest-matcher-utils/src/index.ts b/packages/jest-matcher-utils/src/index.ts index 7deb7bce0544..aba7ff29812e 100644 --- a/packages/jest-matcher-utils/src/index.ts +++ b/packages/jest-matcher-utils/src/index.ts @@ -14,8 +14,6 @@ const { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, - Diff, - DiffOptions, diffStringsRaw, diffStringsUnified, } = jestDiff; @@ -228,12 +226,12 @@ export const ensureExpectedIsNonNegativeInteger = ( // * include change substrings which have argument op // with inverse highlight only if there is a common substring const getCommonAndChangedSubstrings = ( - diffs: Array, + diffs: Array, op: number, hasCommonDiff: boolean, ): string => diffs.reduce( - (reduced: string, diff: Diff): string => + (reduced: string, diff: jestDiff.Diff): string => reduced + (diff[0] === DIFF_EQUAL ? diff[1] From 61b18f7381d16c6cb4cf521cf13e048c58bb74c1 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 22 Aug 2019 10:57:24 -0400 Subject: [PATCH 15/16] Add sentence about diffStringsRaw to jest-diff README --- packages/jest-diff/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index 8d50767e43bc..d60e630be761 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -140,6 +140,8 @@ The returned **array** describes substrings as instances of the `Diff` class (wh | `-1` | `DIFF_DELETE` | in `a` but not in `b` | | `1` | `DIFF_INSERT` | in `b` but not in `a` | +Because `diffStringsRaw` returns the difference as **data** instead of a string, you are free to format it as your application requires (for example, enclosed in HTML markup for browser instead of escape sequences for console). + ### Example of diffStringsRaw with cleanup ```js From 109d70686c21d12ab05b5a0526bf83535fa279d9 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 22 Aug 2019 11:07:56 -0400 Subject: [PATCH 16/16] Refactor to call diffStringsRaw from diffStringsUnified --- packages/jest-diff/src/printDiffs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-diff/src/printDiffs.ts b/packages/jest-diff/src/printDiffs.ts index 91e25bcca716..07d6c4d2ad4f 100644 --- a/packages/jest-diff/src/printDiffs.ts +++ b/packages/jest-diff/src/printDiffs.ts @@ -163,11 +163,11 @@ export const diffStringsUnified = ( const isMultiline = a.includes('\n') || b.includes('\n'); // getAlignedDiffs assumes that a newline was appended to the strings. - const diffs = diffStrings( + const diffs = diffStringsRaw( isMultiline ? a + '\n' : a, isMultiline ? b + '\n' : b, + true, // cleanupSemantic ); - cleanupSemantic(diffs); // impure function if (hasCommonDiff(diffs, isMultiline)) { const lines = getAlignedDiffs(diffs);