diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ffc3b5e246e..a3ad74742717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ - `[jest]` [**BREAKING**] Use ESM exports ([#8874](https://github.com/facebook/jest/pull/8874)) - `[jest-cli]` [**BREAKING**] Use ESM exports ([#8874](https://github.com/facebook/jest/pull/8874)) - `[jest-cli]` [**BREAKING**] Remove re-exports from `@jest/core` ([#8874](https://github.com/facebook/jest/pull/8874)) +- `[jest-diff]` Remove the need to export `splitLines0` function ([#9151](https://github.com/facebook/jest/pull/9151)) - `[jest-environment-jsdom]` [**BREAKING**] Upgrade JSDOM from v11 to v15 ([#8851](https://github.com/facebook/jest/pull/8851)) - `[jest-util]` [**BREAKING**] Remove deprecated exports ([#8863](https://github.com/facebook/jest/pull/8863)) - `[jest-validate]` [**BREAKING**] Use ESM exports ([#8874](https://github.com/facebook/jest/pull/8874)) diff --git a/packages/jest-diff/README.md b/packages/jest-diff/README.md index c4244c40a2a5..f774c66665ab 100644 --- a/packages/jest-diff/README.md +++ b/packages/jest-diff/README.md @@ -151,58 +151,6 @@ Here are edge cases for arguments and return values: - only `b` is empty string: all comparison lines have `aColor` and `aIndicator` (see Options) - `a` and `b` are equal non-empty strings: all comparison lines have `commonColor` and `commonIndicator` (see Options) -To get the comparison lines described above from the `diffLineUnified` function, call `splitLines0(string)` instead of `string.split('\n')` - -```js -export const splitLines0 = string => - string.length === 0 ? [] : string.split('\n'); -``` - -### Example of splitLines0 function - -```js -import {diffLinesUnified, splitLines0} from 'jest-diff'; - -const a = 'multi\nline\nstring'; -const b = ''; -const options = {includeChangeCounts: true}; // see Options - -const difference = diffLinesUnified(splitLines0(a), splitLines0(b), options); -``` - -Given an empty string, `splitLines0(b)` returns `[]` an empty array, formatted as no `Received` lines: - -```diff -- Expected - 3 -+ Received + 0 - -- multi -- line -- string -``` - -### Example of split method - -```js -const a = 'multi\nline\nstring'; -const b = ''; -const options = {includeChangeCounts: true}; // see Options - -const difference = diffLinesUnified(a.split('\n'), b.split('\n'), options); -``` - -Given an empty string, `b.split('\n')` returns `['']` an array that contains an empty string, formatted as one empty `Received` line, which is **ambiguous** with an empty line: - -```diff -- Expected - 3 -+ Received + 1 - -- multi -- line -- string -+ -``` - ## Usage of diffLinesUnified2 Given two **pairs** of arrays of strings, `diffLinesUnified2(aLinesDisplay, bLinesDisplay, aLinesCompare, bLinesCompare, options?)` does the following: @@ -348,6 +296,78 @@ const diffs = diffLinesRaw(aLines, bLines); | `3` | `1` | `'changed to'` | | `4` | `1` | `'insert'` | +### Edge case of diffLinesRaw + +If you call `string.split('\n')` for an empty string: + +- the result is `['']` an array which contains an empty string +- instead of `[]` an empty array + +Depending of your application, you might call `diffLinesRaw` with either array. + +### Example of split method + +```js +import {diffLinesRaw} from 'jest-diff'; + +const a = 'non-empty string'; +const b = ''; + +const diffs = diffLinesRaw(a.split('\n'), b.split('\n')); +``` + +| `i` | `diffs[i][0]` | `diffs[i][1]` | +| --: | ------------: | :------------------- | +| `0` | `-1` | `'non-empty string'` | +| `1` | `1` | `''` | + +Which you might format as follows: + +```diff +- Expected - 1 ++ Received + 1 + +- non-empty string ++ +``` + +### Example of splitLines0 function + +For edge case behavior like the `diffLinesUnified` function, you might define a `splitLines0` function, which given an empty string, returns `[]` an empty array: + +```js +export const splitLines0 = string => + string.length === 0 ? [] : string.split('\n'); +``` + +```js +import {diffLinesRaw} from 'jest-diff'; + +const a = ''; +const b = 'line 1\nline 2\nline 3'; + +const diffs = diffLinesRaw(a.split('\n'), b.split('\n')); +``` + +| `i` | `diffs[i][0]` | `diffs[i][1]` | +| --: | ------------: | :------------ | +| `0` | `1` | `'line 1'` | +| `1` | `1` | `'line 2'` | +| `2` | `1` | `'line 3'` | + +Which you might format as follows: + +```diff +- Expected - 0 ++ Received + 3 + ++ line 1 ++ line 2 ++ line 3 +``` + +In contrast to the `diffLinesRaw` function, the `diffLinesUnified` and `diffLinesUnified2` functions **automatically** convert array arguments computed by string `split` method, so callers do **not** need a `splitLine0` function. + ## Options The default options are for the report when an assertion fails from the `expect` package used by Jest. diff --git a/packages/jest-diff/src/__tests__/diff.test.ts b/packages/jest-diff/src/__tests__/diff.test.ts index 0f0d8e9a6240..100712eeceb1 100644 --- a/packages/jest-diff/src/__tests__/diff.test.ts +++ b/packages/jest-diff/src/__tests__/diff.test.ts @@ -10,6 +10,7 @@ import stripAnsi from 'strip-ansi'; import {alignedAnsiStyleSerializer} from '@jest/test-utils'; import diff from '../'; +import {diffLinesUnified, diffLinesUnified2} from '../diffLines'; import {noColor} from '../normalizeDiffOptions'; import {diffStringsUnified} from '../printDiffs'; import {DiffOptions} from '../types'; @@ -728,6 +729,156 @@ describe('context', () => { testDiffContextLines(); // (5 default) }); +describe('diffLinesUnified edge cases', () => { + test('a empty string b empty string', () => { + const a = ''; + const b = ''; + + const received = diffLinesUnified(a.split('\n'), b.split('\n'), optionsBe); + const expected = ''; + + expect(received).toBe(expected); + }); + + test('a empty string b one line', () => { + const a = ''; + const b = 'line 1'; + + const received = diffLinesUnified(a.split('\n'), b.split('\n'), optionsBe); + const expected = '+ line 1'; + + expect(received).toBe(expected); + }); + + test('a multiple lines b empty string', () => { + const a = 'line 1\n\nline 3'; + const b = ''; + + const received = diffLinesUnified(a.split('\n'), b.split('\n'), optionsBe); + const expected = '- line 1\n-\n- line 3'; + + expect(received).toBe(expected); + }); + + test('a one line b multiple lines', () => { + const a = 'line 2'; + const b = 'line 1\nline 2\nline 3'; + + const received = diffLinesUnified(a.split('\n'), b.split('\n'), optionsBe); + const expected = '+ line 1\n line 2\n+ line 3'; + + expect(received).toBe(expected); + }); +}); + +describe('diffLinesUnified2 edge cases', () => { + test('a empty string b empty string', () => { + const a = ''; + const b = ''; + + const received = diffLinesUnified2( + a.split('\n'), + b.split('\n'), + a.split('\n'), + b.split('\n'), + optionsBe, + ); + const expected = ''; + + expect(received).toBe(expected); + }); + + test('a empty string b one line', () => { + const a = ''; + const b = 'line 1'; + + const received = diffLinesUnified2( + a.split('\n'), + b.split('\n'), + a.split('\n'), + b.split('\n'), + optionsBe, + ); + const expected = '+ line 1'; + + expect(received).toBe(expected); + }); + + test('a multiple lines b empty string', () => { + const a = 'line 1\n\nline 3'; + const b = ''; + + const received = diffLinesUnified2( + a.split('\n'), + b.split('\n'), + a.split('\n'), + b.split('\n'), + optionsBe, + ); + const expected = '- line 1\n-\n- line 3'; + + expect(received).toBe(expected); + }); + + test('a one line b multiple lines', () => { + const aDisplay = 'LINE 2'; + const bDisplay = 'Line 1\nLine 2\nLine 3'; + const aCompare = aDisplay.toLowerCase(); + const bCompare = bDisplay.toLowerCase(); + + const received = diffLinesUnified2( + aDisplay.split('\n'), + bDisplay.split('\n'), + aCompare.split('\n'), + bCompare.split('\n'), + optionsBe, + ); + const expected = '+ Line 1\n Line 2\n+ Line 3'; + + expect(received).toBe(expected); + }); + + describe('lengths not equal', () => { + // Fall back to diff of display lines. + + test('a', () => { + const aDisplay = 'MiXeD cAsE'; + const bDisplay = 'Mixed case\nUPPER CASE'; + const aCompare = aDisplay.toLowerCase() + '\nlower case'; + const bCompare = bDisplay.toLowerCase(); + + const received = diffLinesUnified2( + aDisplay.split('\n'), + bDisplay.split('\n'), + aCompare.split('\n'), + bCompare.split('\n'), + optionsBe, + ); + const expected = '- MiXeD cAsE\n+ Mixed case\n+ UPPER CASE'; + + expect(received).toBe(expected); + }); + + test('b', () => { + const aDisplay = '{\n "key": "value",\n}'; + const bDisplay = '{\n}'; + const aCompare = '{\n"key": "value",\n}'; + const bCompare = '{}'; + + const expected = ' {\n- "key": "value",\n }'; + const received = diffLinesUnified2( + aDisplay.split('\n'), + bDisplay.split('\n'), + aCompare.split('\n'), + bCompare.split('\n'), + optionsBe, + ); + + expect(received).toBe(expected); + }); + }); +}); + describe('diffStringsUnified edge cases', () => { test('empty both a and b', () => { const a = ''; @@ -928,15 +1079,10 @@ describe('options', () => { }); }); - describe('firstOrLastEmptyLineReplacement', () => { - const noColor = (string: string) => string; + describe('emptyFirstOrLastLinePlaceholder default empty string', () => { const options = { - aColor: noColor, - bColor: noColor, + ...optionsBe, changeColor: noColor, - commonColor: noColor, - firstOrLastEmptyLineReplacement: '', - omitAnnotationLines: true, }; const aEmpty = '\ncommon\nchanged from\n'; diff --git a/packages/jest-diff/src/diffLines.ts b/packages/jest-diff/src/diffLines.ts index 6e5f5b467563..3fefd37a3f45 100644 --- a/packages/jest-diff/src/diffLines.ts +++ b/packages/jest-diff/src/diffLines.ts @@ -11,13 +11,22 @@ import {normalizeDiffOptions} from './normalizeDiffOptions'; import {printDiffLines} from './printDiffs'; import {DiffOptions} from './types'; +const isEmptyString = (lines: Array) => + lines.length === 1 && lines[0].length === 0; + // Compare two arrays of strings line-by-line. Format as comparison lines. export const diffLinesUnified = ( aLines: Array, bLines: Array, options?: DiffOptions, ): string => - printDiffLines(diffLinesRaw(aLines, bLines), normalizeDiffOptions(options)); + printDiffLines( + diffLinesRaw( + isEmptyString(aLines) ? [] : aLines, + isEmptyString(bLines) ? [] : bLines, + ), + normalizeDiffOptions(options), + ); // Given two pairs of arrays of strings: // Compare the pair of comparison arrays line-by-line. @@ -29,6 +38,15 @@ export const diffLinesUnified2 = ( bLinesCompare: Array, options?: DiffOptions, ): string => { + if (isEmptyString(aLinesDisplay) && isEmptyString(aLinesCompare)) { + aLinesDisplay = []; + aLinesCompare = []; + } + if (isEmptyString(bLinesDisplay) && isEmptyString(bLinesCompare)) { + bLinesDisplay = []; + bLinesCompare = []; + } + if ( aLinesDisplay.length !== aLinesCompare.length || bLinesDisplay.length !== bLinesCompare.length diff --git a/packages/jest-diff/src/index.ts b/packages/jest-diff/src/index.ts index e37b122c5577..49d5ef1a3672 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 = require('jest-get-type'); import {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff} from './cleanupSemantic'; import {diffLinesRaw, diffLinesUnified, diffLinesUnified2} from './diffLines'; -import {diffStringsRaw, diffStringsUnified, splitLines0} from './printDiffs'; +import {diffStringsRaw, diffStringsUnified} from './printDiffs'; import {NO_DIFF_MESSAGE, SIMILAR_MESSAGE} from './constants'; import { DiffOptions as ImportDiffOptions, @@ -21,7 +21,7 @@ export type DiffOptions = ImportDiffOptions; export type DiffOptionsColor = ImportDiffOptionsColor; export {diffLinesRaw, diffLinesUnified, diffLinesUnified2}; -export {diffStringsRaw, diffStringsUnified, splitLines0}; +export {diffStringsRaw, diffStringsUnified}; export {DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff}; const { @@ -91,7 +91,7 @@ function diff(a: any, b: any, options?: DiffOptions): string | null { switch (aType) { case 'string': - return diffLinesUnified(splitLines0(a), splitLines0(b), options); + return diffLinesUnified(a.split('\n'), b.split('\n'), options); case 'boolean': case 'number': return comparePrimitive(a, b, options); @@ -113,7 +113,7 @@ function comparePrimitive( const bFormat = prettyFormat(b, FORMAT_OPTIONS); return aFormat === bFormat ? NO_DIFF_MESSAGE - : diffLinesUnified(splitLines0(aFormat), splitLines0(bFormat), options); + : diffLinesUnified(aFormat.split('\n'), bFormat.split('\n'), options); } function sortMap(map: Map) { diff --git a/packages/jest-diff/src/printDiffs.ts b/packages/jest-diff/src/printDiffs.ts index 888dd6e718dd..984beb23f5c9 100644 --- a/packages/jest-diff/src/printDiffs.ts +++ b/packages/jest-diff/src/printDiffs.ts @@ -207,9 +207,6 @@ export const createPatchMark = ( `@@ -${aStart + 1},${aEnd - aStart} +${bStart + 1},${bEnd - bStart} @@`, ); -export const splitLines0 = (string: string) => - string.length === 0 ? [] : string.split('\n'); - // Compare two strings character-by-character. // Format as comparison lines in which changed substrings have inverse colors. export const diffStringsUnified = ( @@ -235,7 +232,7 @@ export const diffStringsUnified = ( } // Fall back to line-by-line diff. - return diffLinesUnified(splitLines0(a), splitLines0(b), options); + return diffLinesUnified(a.split('\n'), b.split('\n'), options); }; // Compare two strings character-by-character. diff --git a/packages/jest-snapshot/src/printSnapshot.ts b/packages/jest-snapshot/src/printSnapshot.ts index 40de416e60a8..538efcbbd466 100644 --- a/packages/jest-snapshot/src/printSnapshot.ts +++ b/packages/jest-snapshot/src/printSnapshot.ts @@ -14,7 +14,6 @@ import { diffLinesUnified, diffStringsRaw, diffStringsUnified, - splitLines0, } from 'jest-diff'; import getType = require('jest-get-type'); import { @@ -145,8 +144,8 @@ export const printPropertiesAndReceived = ( if (isLineDiffable(properties) && isLineDiffable(received)) { return diffLinesUnified( - splitLines0(serialize(properties)), - splitLines0(serialize(received)), + serialize(properties).split('\n'), + serialize(received).split('\n'), { aAnnotation, aColor: EXPECTED_COLOR, @@ -241,12 +240,12 @@ export const printSnapshotAndReceived = ( return a.length <= MAX_DIFF_STRING_LENGTH && b.length <= MAX_DIFF_STRING_LENGTH ? diffStringsUnified(a, b, options) - : diffLinesUnified(splitLines0(a), splitLines0(b), options); + : diffLinesUnified(a.split('\n'), b.split('\n'), options); } if (isLineDiffable(received)) { // TODO future PR will replace with diffLinesUnified2 to ignore indentation - return diffLinesUnified(splitLines0(a), splitLines0(b), options); + return diffLinesUnified(a.split('\n'), b.split('\n'), options); } const printLabel = getLabelPrinter(aAnnotation, bAnnotation);