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

Do not highlight matched asymmetricMatcher in diffs #9257

Merged
merged 47 commits into from
Jan 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
586b17a
Do not diff attribute which match asymmetricMatcher.
WeiAnAn Dec 2, 2019
ec68d66
Use 'new' to copy RegExp. Fix calling method error after copied by Ob…
WeiAnAn Dec 2, 2019
4f7c31a
Use jest-util deepCopy to copy record.
WeiAnAn Dec 2, 2019
e69b816
update snapshot
WeiAnAn Dec 2, 2019
727d5e5
Merge branch 'master' into asymmetric_matcher_diff
WeiAnAn Dec 2, 2019
2c88e99
update changelog
WeiAnAn Dec 2, 2019
3885f56
remove line
WeiAnAn Dec 2, 2019
7a04cb0
sort changelog
WeiAnAn Dec 3, 2019
48ab232
sort path
WeiAnAn Dec 3, 2019
69fbc5d
move replaceMatchedToAsymmetricMatcher to jest-matcher-utils
WeiAnAn Dec 7, 2019
149783b
fix object attribute order
WeiAnAn Dec 7, 2019
883f4cd
add `ignoreAsymmetricMatches` option
WeiAnAn Dec 7, 2019
a6632ed
update docs
WeiAnAn Dec 7, 2019
4f5c1a9
update snapshot
WeiAnAn Dec 7, 2019
29154cd
add e2e test
WeiAnAn Dec 7, 2019
e99e985
merge master
WeiAnAn Dec 7, 2019
7aa6f27
update changelog
WeiAnAn Dec 7, 2019
b148c0f
ignore e2e test file
WeiAnAn Dec 7, 2019
fd6443e
lint
WeiAnAn Dec 7, 2019
ba5d7ec
update snapshot
WeiAnAn Dec 7, 2019
34c18dc
fix jest-circus doesn't get ignoreAsymmetricMatches value
WeiAnAn Dec 7, 2019
e0a6288
only import deepCyclicCopy to avoid loading incompatible packages in …
WeiAnAn Dec 7, 2019
c5361b4
lint
WeiAnAn Dec 7, 2019
c90a4ed
prettier Configuration.md
WeiAnAn Dec 7, 2019
d7c9a55
add copyright headers
WeiAnAn Dec 7, 2019
b438d6d
update snapshot
WeiAnAn Dec 7, 2019
65bd0cc
remove ignoreAsymmetricMatches option
WeiAnAn Jan 4, 2020
61f2c9f
support copy built-in objects
WeiAnAn Jan 5, 2020
4c0f959
add Class Replaceable to provide same api for object operation
WeiAnAn Jan 5, 2020
03e00c3
use Replaceable for replacing to asymmetrict matcher
WeiAnAn Jan 5, 2020
9a81ffc
lint
WeiAnAn Jan 5, 2020
67c443e
update changelog
WeiAnAn Jan 5, 2020
263f9a7
add braces
WeiAnAn Jan 5, 2020
d5863fa
change any type to unknow
WeiAnAn Jan 5, 2020
8f5321a
import correctly
WeiAnAn Jan 5, 2020
83ddb57
avoid browser read property of undefined
WeiAnAn Jan 5, 2020
4d4b7ce
update changelog
WeiAnAn Jan 5, 2020
d9dd2c4
Check value before checking constructor.
WeiAnAn Jan 14, 2020
d4c2f5e
restore deepCyclicCopy
WeiAnAn Jan 18, 2020
1db1b73
create deepCyclicCopyReplaceable
WeiAnAn Jan 18, 2020
739d835
change to use deepCyclicCopyReplaceable for copy
WeiAnAn Jan 18, 2020
3e200ce
undo no use change
WeiAnAn Jan 18, 2020
829ef89
drop dependency
WeiAnAn Jan 18, 2020
684b8ff
remove outdated changelog
WeiAnAn Jan 18, 2020
0a26b71
add braces
WeiAnAn Jan 19, 2020
0f441f4
undo outdated change
WeiAnAn Jan 19, 2020
c63cc72
Merge branch 'master' into asymmetric_matcher_diff
SimenB Jan 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- `[@jest/fake-timers]` Add Lolex as implementation of fake timers ([#8897](https://github.com/facebook/jest/pull/8897))
- `[jest-get-type]` Add `BigInt` support. ([#8382](https://github.com/facebook/jest/pull/8382))
- `[jest-matcher-utils]` Add `BigInt` support to `ensureNumbers` `ensureActualIsNumber`, `ensureExpectedIsNumber` ([#8382](https://github.com/facebook/jest/pull/8382))
- `[jest-matcher-utils]` Ignore highlighting matched asymmetricMatcher in diffs ([#9257](https://github.com/facebook/jest/pull/9257))
- `[jest-reporters]` Export utils for path formatting ([#9162](https://github.com/facebook/jest/pull/9162))
- `[jest-reporters]` Provides global coverage thresholds as watermarks for istanbul ([#9416](https://github.com/facebook/jest/pull/9416))
- `[jest-runner]` Warn if a worker had to be force exited ([#8206](https://github.com/facebook/jest/pull/8206))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,8 @@ exports[`.toBe() fails for: {"a": [Function a], "b": 2} and {"a": Any<Function>,

<d>If it should pass with deep equality, replace "toBe" with "toStrictEqual"</>

<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<g>- "a": Any<Function>,</>
<r>+ "a": [Function a],</>
<d> "b": 2,</>
<d> }</>
Expected: <g>{"a": Any<Function>, "b": 2}</>
Received: <r>{"a": [Function a], "b": 2}</>
`;

exports[`.toBe() fails for: {"a": 1} and {"a": 1} 1`] = `
Expand Down
56 changes: 56 additions & 0 deletions packages/jest-matcher-utils/src/Replaceable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* 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 getType = require('jest-get-type');

const supportTypes = ['map', 'array', 'object'];

type ReplaceableForEachCallBack = (value: any, key: any, object: any) => void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can all these anys be unknowns?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If change any to unknown, it will do a lot task for type converting.
For example:

get(key: unknown): unknown {
  if (isMap(this.object)) {
    return this.object.get(key);
  } else if (Array.isArray(this.object)) {
    return this.object[key as number];
  } else if (typeof this.object === 'object' && this.object !== null) {
    return this.object[key as keyof object];
  }
}

I think it make code a little bit ugly, although is suitable for typescript.


export default class Replaceable {
object: any;
type: string;

constructor(object: any) {
this.object = object;
this.type = getType(object);
if (!supportTypes.includes(this.type)) {
throw new Error(`Type ${this.type} is not support in Replaceable!`);
}
}

static isReplaceable(obj1: any, obj2: any): boolean {
const obj1Type = getType(obj1);
const obj2Type = getType(obj2);
return obj1Type === obj2Type && supportTypes.includes(obj1Type);
}

forEach(cb: ReplaceableForEachCallBack): void {
if (this.type === 'object') {
Object.entries(this.object).forEach(([key, value]) => {
cb(value, key, this.object);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs

See Object.entries on Mozilla Developer Network

Therefore, you might need to add the following iteration for enumerable symbol-keyed properties:

  Object.getOwnPropertySymbols(this.object).forEach(key => {
    const descriptor = Object.getOwnPropertyDescriptor(this.object, key);
    if ((descriptor as PropertyDescriptor).enumerable) {
      cb(this.object[key], key, this.object);
    }
  });

The forEach method for Map and Set do not have this problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the diff snapshots in matchers.test.ts.snap cover the case of symbol keys :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add some symbol keys test cases, and make them work.
Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WeiAnAn For your information, the current target for Jest 25 is Tuesday January 21, so if this rare edge case will slow you down too much, please leave it for a follow-up pull request. The copy function is the important thing.

} else {
this.object.forEach(cb);
}
}

get(key: any): any {
if (this.type === 'map') {
return this.object.get(key);
}
return this.object[key];
}

set(key: any, value: any): void {
if (this.type === 'map') {
this.object.set(key, value);
} else {
this.object[key] = value;
}
}
}
154 changes: 154 additions & 0 deletions packages/jest-matcher-utils/src/__tests__/Replaceable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* 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 Replaceable from '../Replaceable';

describe('Replaceable', () => {
describe('constructor', () => {
test('init with object', () => {
const replaceable = new Replaceable({a: 1, b: 2});
expect(replaceable.object).toEqual({a: 1, b: 2});
expect(replaceable.type).toBe('object');
});

test('init with array', () => {
const replaceable = new Replaceable([1, 2, 3]);
expect(replaceable.object).toEqual([1, 2, 3]);
expect(replaceable.type).toBe('array');
});

test('init with Map', () => {
const replaceable = new Replaceable(
new Map([
['a', 1],
['b', 2],
]),
);
expect(replaceable.object).toEqual(
new Map([
['a', 1],
['b', 2],
]),
);
expect(replaceable.type).toBe('map');
});

test('init with other type should throw error', () => {
expect(() => {
//eslint-disable-next-line @typescript-eslint/no-unused-vars
const replaceable = new Replaceable(new Date());
}).toThrow('Type date is not support in Replaceable!');
});
});

describe('get', () => {
test('get object item', () => {
const replaceable = new Replaceable({a: 1, b: 2});
expect(replaceable.get('b')).toBe(2);
});

test('get array item', () => {
const replaceable = new Replaceable([1, 2, 3]);
expect(replaceable.get(1)).toBe(2);
});

test('get Map item', () => {
const replaceable = new Replaceable(
new Map([
['a', 1],
['b', 2],
]),
);
expect(replaceable.get('b')).toBe(2);
});
});

describe('set', () => {
test('set object item', () => {
const replaceable = new Replaceable({a: 1, b: 2});
replaceable.set('b', 3);
expect(replaceable.object).toEqual({a: 1, b: 3});
});

test('set array item', () => {
const replaceable = new Replaceable([1, 2, 3]);
replaceable.set(1, 3);
expect(replaceable.object).toEqual([1, 3, 3]);
});

test('set Map item', () => {
const replaceable = new Replaceable(
new Map([
['a', 1],
['b', 2],
]),
);
replaceable.set('b', 3);
expect(replaceable.object).toEqual(
new Map([
['a', 1],
['b', 3],
]),
);
});
});

describe('forEach', () => {
test('object forEach', () => {
const replaceable = new Replaceable({a: 1, b: 2});
const cb = jest.fn();
replaceable.forEach(cb);
expect(cb.mock.calls[0]).toEqual([1, 'a', {a: 1, b: 2}]);
expect(cb.mock.calls[1]).toEqual([2, 'b', {a: 1, b: 2}]);
});

test('array forEach', () => {
const replaceable = new Replaceable([1, 2, 3]);
const cb = jest.fn();
replaceable.forEach(cb);
expect(cb.mock.calls[0]).toEqual([1, 0, [1, 2, 3]]);
expect(cb.mock.calls[1]).toEqual([2, 1, [1, 2, 3]]);
expect(cb.mock.calls[2]).toEqual([3, 2, [1, 2, 3]]);
});

test('map forEach', () => {
const map = new Map([
['a', 1],
['b', 2],
]);
const replaceable = new Replaceable(map);
const cb = jest.fn();
replaceable.forEach(cb);
expect(cb.mock.calls[0]).toEqual([1, 'a', map]);
expect(cb.mock.calls[1]).toEqual([2, 'b', map]);
});
});

describe('isReplaceable', () => {
test('should return true if two object types equal and support', () => {
expect(Replaceable.isReplaceable({a: 1}, {b: 2})).toBe(true);
expect(Replaceable.isReplaceable([], [1, 2, 3])).toBe(true);
expect(
Replaceable.isReplaceable(
new Map(),
new Map([
['a', 1],
['b', 2],
]),
),
).toBe(true);
});

test('should return false if two object types not equal', () => {
expect(Replaceable.isReplaceable({a: 1}, [1, 2, 3])).toBe(false);
});

test('should return false if object types not support', () => {
expect(Replaceable.isReplaceable('foo', 'bar')).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,160 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`printDiffOrStringify asymmetricMatcher array 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Array [</>
<d> 1,</>
<d> Any<Number>,</>
<g>- 3,</>
<r>+ 2,</>
<d> ]</>
`;

exports[`printDiffOrStringify asymmetricMatcher circular array 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Array [</>
<d> 1,</>
<d> Any<Number>,</>
<g>- 3,</>
<r>+ 2,</>
<d> [Circular],</>
<d> ]</>
`;

exports[`printDiffOrStringify asymmetricMatcher circular map 1`] = `
<g>- Expected - 2</>
<r>+ Received + 2</>

<d> Map {</>
<d> "a" => 1,</>
<d> "b" => Any<Number>,</>
<g>- "c" => 3,</>
<r>+ "c" => 2,</>
<d> "circular" => Map {</>
<d> "a" => 1,</>
<d> "b" => Any<Number>,</>
<g>- "c" => 3,</>
<r>+ "c" => 2,</>
<d> "circular" => [Circular],</>
<d> },</>
<d> }</>
`;

exports[`printDiffOrStringify asymmetricMatcher circular object 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<d> "a": [Circular],</>
<d> "b": Any<Number>,</>
<g>- "c": 3,</>
<r>+ "c": 2,</>
<d> }</>
`;

exports[`printDiffOrStringify asymmetricMatcher custom asymmetricMatcher 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<d> "a": equal5<>,</>
<g>- "b": false,</>
<r>+ "b": true,</>
<d> }</>
`;

exports[`printDiffOrStringify asymmetricMatcher jest asymmetricMatcher 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<d> "a": Any<Number>,</>
<d> "b": Anything,</>
<d> "c": ArrayContaining [</>
<d> 1,</>
<d> 3,</>
<d> ],</>
<d> "d": StringContaining "jest",</>
<d> "e": StringMatching /^jest/,</>
<d> "f": ObjectContaining {</>
<d> "a": Any<Date>,</>
<d> },</>
<g>- "g": true,</>
<r>+ "g": false,</>
<d> }</>
`;

exports[`printDiffOrStringify asymmetricMatcher map 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Map {</>
<d> "a" => 1,</>
<d> "b" => Any<Number>,</>
<g>- "c" => 3,</>
<r>+ "c" => 2,</>
<d> }</>
`;

exports[`printDiffOrStringify asymmetricMatcher minimal test 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<d> "a": Any<Number>,</>
<g>- "b": 2,</>
<r>+ "b": 1,</>
<d> }</>
`;

exports[`printDiffOrStringify asymmetricMatcher nested object 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<d> "a": Any<Number>,</>
<d> "b": Object {</>
<d> "a": 1,</>
<d> "b": Any<Number>,</>
<d> },</>
<g>- "c": 2,</>
<r>+ "c": 1,</>
<d> }</>
`;

exports[`printDiffOrStringify asymmetricMatcher object in array 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Array [</>
<d> 1,</>
<d> Object {</>
<d> "a": 1,</>
<d> "b": Any<Number>,</>
<d> },</>
<g>- 3,</>
<r>+ 2,</>
<d> ]</>
`;

exports[`printDiffOrStringify asymmetricMatcher transitive circular 1`] = `
<g>- Expected - 1</>
<r>+ Received + 1</>

<d> Object {</>
<g>- "a": 3,</>
<r>+ "a": 2,</>
<d> "nested": Object {</>
<d> "b": Any<Number>,</>
<d> "parent": [Circular],</>
<d> },</>
<d> }</>
`;

exports[`printDiffOrStringify expected and received are multi line with trailing spaces 1`] = `
<g>- Expected - 3</>
<r>+ Received + 3</>
Expand Down
Loading