Skip to content

Commit

Permalink
property-based test for Node deepStrictEqual equivalence (#9167)
Browse files Browse the repository at this point in the history
* property test toStrictEqual vs deepStrictEqual

also, import expect so that property tests do not need rebuilds

* fix primitives being considered equal to new Primitive()s

* update changelog
  • Loading branch information
jeysal committed Nov 13, 2019
1 parent 0996912 commit 1dc8fcb
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 45 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -35,6 +35,8 @@

- `[expect]` Display `expectedDiff` more carefully in `toBeCloseTo` ([#8389](https://github.com/facebook/jest/pull/8389))
- `[expect]` Avoid incorrect difference for subset when `toMatchObject` fails ([#9005](https://github.com/facebook/jest/pull/9005))
- `[expect]` Consider all RegExp flags for equality ([#9167](https://github.com/facebook/jest/pull/9167))
- `[expect]` [**BREAKING**] Consider primitives different from wrappers instantiated with `new` ([#9167](https://github.com/facebook/jest/pull/9167))
- `[jest-config]` Use half of the available cores when `watchAll` mode is enabled ([#9117](https://github.com/facebook/jest/pull/9117))
- `[jest-console]` Add missing `console.group` calls to `NullConsole` ([#9024](https://github.com/facebook/jest/pull/9024))
- `[jest-core]` Don't include unref'd timers in --detectOpenHandles results ([#8941](https://github.com/facebook/jest/pull/8941))
Expand Down Expand Up @@ -63,6 +65,7 @@
- `[docs]` Add alias and optional boolean value to `coverage` CLI Reference ([#8996](https://github.com/facebook/jest/pull/8996))
- `[docs]` Fix broken link pointing to legacy JS file in "Snapshot Testing".
- `[docs]` Add `setupFilesAfterEnv` and `jest.setTimeout` example ([#8971](https://github.com/facebook/jest/pull/8971))
- `[expect]` Test that `toStrictEqual` is equivalent to Node's `assert.deepStrictEqual` ([#9167](https://github.com/facebook/jest/pull/9167))
- `[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))
Expand Down
79 changes: 64 additions & 15 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Expand Up @@ -1986,6 +1986,13 @@ Expected: <g>{"asymmetricMatch": [Function asymmetricMatch]}</>
Received: <r>"Eve"</>
`;

exports[`.toEqual() {pass: false} expect("abc").toEqual({"0": "a", "1": "b", "2": "c"}) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: <g>{"0": "a", "1": "b", "2": "c"}</>
Received: <r>"abc"</>
`;

exports[`.toEqual() {pass: false} expect("abd").toEqual(StringContaining "bc") 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expand Down Expand Up @@ -2021,6 +2028,13 @@ exports[`.toEqual() {pass: false} expect("type TypeName<T> = T extends Function
<r>+ type TypeName<T> = T extends Function<i> </i>? "function"<i> </i>: "object";</>
`;

exports[`.toEqual() {pass: false} expect(/abc/gy).toEqual(/abc/g) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: <g>/abc/g</>
Received: <r>/abc/gy</>
`;

exports[`.toEqual() {pass: false} expect([1, 2]).toEqual([2, 1]) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expand Down Expand Up @@ -2053,6 +2067,13 @@ exports[`.toEqual() {pass: false} expect([1]).toEqual([2]) 1`] = `
<d> ]</>
`;

exports[`.toEqual() {pass: false} expect({"0": "a", "1": "b", "2": "c"}).toEqual("abc") 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: <g>"abc"</>
Received: <r>{"0": "a", "1": "b", "2": "c"}</>
`;

exports[`.toEqual() {pass: false} expect({"a": 1, "b": 2}).toEqual(ObjectContaining {"a": 2}) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expand Down Expand Up @@ -2112,6 +2133,27 @@ exports[`.toEqual() {pass: false} expect({"target": {"nodeType": 1, "value": "a"
<d> }</>
`;

exports[`.toEqual() {pass: false} expect({}).toEqual({}) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: <g>{}</>
Received: serializes to the same string
`;

exports[`.toEqual() {pass: false} expect({}).toEqual(0) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: <g>0</>
Received: <r>{}</>
`;

exports[`.toEqual() {pass: false} expect(0).toEqual({}) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: <g>{}</>
Received: <r>0</>
`;

exports[`.toEqual() {pass: false} expect(0).toEqual(-0) 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expand Down Expand Up @@ -2467,11 +2509,18 @@ Expected: not <g>"abc"</>

`;

exports[`.toEqual() {pass: true} expect("abc").not.toEqual({"0": "a", "1": "b", "2": "c"}) 1`] = `
exports[`.toEqual() {pass: true} expect("abc").not.toEqual("abc") 2`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: not <g>"abc"</>

`;

exports[`.toEqual() {pass: true} expect("abc").not.toEqual("abc") 3`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: not <g>{"0": "a", "1": "b", "2": "c"}</>
Received: <r>"abc"</>
Expected: not <g>"abc"</>

`;

exports[`.toEqual() {pass: true} expect("abcd").not.toEqual(StringContaining "bc") 1`] = `
Expand Down Expand Up @@ -2516,13 +2565,6 @@ Expected: not <g>Any<Function></>
Received: <r>[Function anonymous]</>
`;

exports[`.toEqual() {pass: true} expect({"0": "a", "1": "b", "2": "c"}).not.toEqual("abc") 1`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: not <g>"abc"</>
Received: <r>{"0": "a", "1": "b", "2": "c"}</>
`;

exports[`.toEqual() {pass: true} expect({"a": 1, "b": [Function b], "c": true}).not.toEqual({"a": 1, "b": Any<Function>, "c": Anything}) 1`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expand Down Expand Up @@ -2558,18 +2600,25 @@ Expected: not <g>{}</>

`;

exports[`.toEqual() {pass: true} expect({}).not.toEqual(0) 1`] = `
exports[`.toEqual() {pass: true} expect({}).not.toEqual({}) 2`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: not <g>{}</>

`;

exports[`.toEqual() {pass: true} expect(0).not.toEqual(0) 1`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: not <g>0</>
Received: <r>{}</>

`;

exports[`.toEqual() {pass: true} expect(0).not.toEqual({}) 1`] = `
exports[`.toEqual() {pass: true} expect(0).not.toEqual(0) 2`] = `
<d>expect(</><r>received</><d>).</>not<d>.</>toEqual<d>(</><g>expected</><d>) // deep equality</>

Expected: not <g>{}</>
Received: <r>0</>
Expected: not <g>0</>

`;

exports[`.toEqual() {pass: true} expect(1).not.toEqual(1) 1`] = `
Expand Down
Expand Up @@ -11,6 +11,7 @@ import {
anythingSettings,
assertSettings,
} from './__arbitraries__/sharedSettings';
import expect from '..';

describe('toContain', () => {
it('should always find the value when inside the array', () => {
Expand Down
Expand Up @@ -11,6 +11,7 @@ import {
anythingSettings,
assertSettings,
} from './__arbitraries__/sharedSettings';
import expect from '..';

describe('toContainEqual', () => {
it('should always find the value when inside the array', () => {
Expand Down
Expand Up @@ -11,6 +11,7 @@ import {
anythingSettings,
assertSettings,
} from './__arbitraries__/sharedSettings';
import expect from '..';

describe('toEqual', () => {
it('should be reflexive', () => {
Expand Down
Expand Up @@ -6,13 +6,32 @@
*
*/

import assert from 'assert';
import {onNodeVersions} from '@jest/test-utils';
import fc from 'fast-check';
import {
anythingSettings,
assertSettings,
} from './__arbitraries__/sharedSettings';
import expect from '..';

describe('toStrictEqual', () => {
const safeExpectStrictEqual = (a, b) => {
try {
expect(a).toStrictEqual(b);
return true;
} catch (err) {
return false;
}
};
const safeAssertDeepStrictEqual = (a, b) => {
try {
assert.deepStrictEqual(a, b);
return true;
} catch (err) {
return false;
}
};
it('should be reflexive', () => {
fc.assert(
fc.property(fc.dedup(fc.anything(anythingSettings), 2), ([a, b]) => {
Expand All @@ -24,14 +43,6 @@ describe('toStrictEqual', () => {
});

it('should be symmetric', () => {
const safeExpectStrictEqual = (a, b) => {
try {
expect(a).toStrictEqual(b);
return true;
} catch (err) {
return false;
}
};
fc.assert(
fc.property(
fc.anything(anythingSettings),
Expand All @@ -46,4 +57,21 @@ describe('toStrictEqual', () => {
assertSettings,
);
});

onNodeVersions('>=9', () => {
it('should be equivalent to Node deepStrictEqual', () => {
fc.assert(
fc.property(
fc.anything(anythingSettings),
fc.anything(anythingSettings),
(a, b) => {
expect(safeExpectStrictEqual(a, b)).toBe(
safeAssertDeepStrictEqual(a, b),
);
},
),
assertSettings,
);
});
});
});
21 changes: 13 additions & 8 deletions packages/expect/src/__tests__/matchers.test.js
Expand Up @@ -419,12 +419,19 @@ describe('.toStrictEqual()', () => {
});

describe('.toEqual()', () => {
/* eslint-disable no-new-wrappers */
[
[true, false],
[1, 2],
[0, -0],
[0, Number.MIN_VALUE], // issues/7941
[Number.MIN_VALUE, 0],
[0, new Number(0)],
[new Number(0), 0],
[new Number(0), new Number(1)],
['abc', new String('abc')],
[new String('abc'), 'abc'],
[/abc/gsy, /abc/g],
[{a: 1}, {a: 2}],
[{a: 5}, {b: 6}],
['banana', 'apple'],
Expand Down Expand Up @@ -548,15 +555,12 @@ describe('.toEqual()', () => {
[true, true],
[1, 1],
[NaN, NaN],
// eslint-disable-next-line no-new-wrappers
[0, new Number(0)],
// eslint-disable-next-line no-new-wrappers
[new Number(0), 0],
[0, Number(0)],
[Number(0), 0],
[new Number(0), new Number(0)],
['abc', 'abc'],
// eslint-disable-next-line no-new-wrappers
[new String('abc'), 'abc'],
// eslint-disable-next-line no-new-wrappers
['abc', new String('abc')],
[String('abc'), 'abc'],
['abc', String('abc')],
[[1], [1]],
[
[1, 2],
Expand Down Expand Up @@ -856,6 +860,7 @@ describe('.toEqual()', () => {
expect(d).not.toEqual(c);
});
});
/* eslint-enable */
});

describe('.toBeInstanceOf()', () => {
Expand Down
28 changes: 14 additions & 14 deletions packages/expect/src/jasmineUtils.ts
Expand Up @@ -100,29 +100,29 @@ function eq(
return false;
}
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object Boolean]':
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
return Object.is(Number(a), Number(b));
if (typeof a !== typeof b) {
// One is a primitive, one a `new Primitive()`
return false;
} else if (typeof a !== 'object' && typeof b !== 'object') {
// both are proper primitives
return Object.is(a, b);
} else {
// both are `new Primitive()`s
return Object.is(a.valueOf(), b.valueOf());
}
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// Coerce dates to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return (
a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase
);
return a.source === b.source && a.flags === b.flags;
}
if (typeof a != 'object' || typeof b != 'object') {
if (typeof a !== 'object' || typeof b !== 'object') {
return false;
}

Expand Down

0 comments on commit 1dc8fcb

Please sign in to comment.