Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

jest-diff: Add options for colors and symbols #8841

Merged
merged 19 commits into from Aug 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@

- `[babel-plugin-jest-hoist]` Show codeframe on static hoisting issues ([#8865](https://github.com/facebook/jest/pull/8865))
- `[jest-config]` [**BREAKING**] Set default display name color based on runner ([#8689](https://github.com/facebook/jest/pull/8689))
- `[jest-diff]` Add options for colors and symbols ([#8841](https://github.com/facebook/jest/pull/8841))
- `[@jest/test-result]` Create method to create empty `TestResult` ([#8867](https://github.com/facebook/jest/pull/8867))

### Fixes
Expand Down
291 changes: 291 additions & 0 deletions packages/jest-diff/README.md
@@ -0,0 +1,291 @@
# 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:

- `diffStringsUnified` returns a string which includes comparison lines.
- `diffStringsRaw` returns an array of `Diff` objects.

## 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, `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
- **format** the changed or common lines using the `chalk` package

To use this function, write either of the following:

- `const diffLinesUnified = require('jest-diff');` in a CommonJS module
- `import diffLinesUnified 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 = diffLinesUnified(a, b);
```

The returned **string** consists of:

- annotation lines which describe the change symbols with labels
- blank line
- comparison lines: similar to “unified” view on GitHub, but `Expected` lines are green, `Received` lines are red, and common lines are dim (by default, see Options)

```diff
- Expected
SimenB marked this conversation as resolved.
Show resolved Hide resolved
+ 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 diffStringsUnified

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, 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.

Write either of the following:

- `const {diffStringsUnified} = require('jest-diff');` in a CommonJS module
- `import {diffStringsUnified} from 'jest-diff';` in an ECMAScript module

### Example of diffStringsUnified

```js
const a = 'change from\ncommon';
const b = 'change to\ncommon';

const difference = diffStringsUnified(a, b);
```

The returned **string** consists of:

- annotation lines which describe the change symbols with labels
- blank line
- comparison lines: similar to “unified” view on GitHub, and **changed substrings** have **inverted** foreground and background colors

```diff
- Expected
+ Received

- change from
+ change to
common
```

### Edge cases of diffStringsUnified

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 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.

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 diffStringsRaw

Given strings, `diffStringsRaw(a, b, cleanup)` does the following:

- **compare** the strings character-by-character using the `diff-sequences` package
- optionally **clean up** small (often coincidental) common substrings, also known as chaff

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 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 in `b` |
| `-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
const diffs = diffStringsRaw('change from', 'change to', true);

// diffs[0][0] === DIFF_EQUAL
// diffs[0][1] === 'change '

// diffs[1][0] === DIFF_DELETE
// diffs[1][1] === 'from'

// diffs[2][0] === DIFF_INSERT
// diffs[2][1] === 'to'
```

### Example of diffStringsRaw without cleanup

```js
const diffs = diffStringsRaw('change from', 'change to', false);

// 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

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:

- `diffLinesUnified(a, b, options)`
- `diffStringsUnified(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` |
| `omitAnnotationLines` | `false` |
SimenB marked this conversation as resolved.
Show resolved Hide resolved

### 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.

### 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
```