From 5a5a0e38d213f93d55cc6cee5a4b53044df27607 Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 19 Apr 2018 10:56:14 +0100 Subject: [PATCH 1/6] Introduce toStrictEqual --- .../expect/src/__tests__/matchers.test.js | 26 +++++++++++++++ packages/expect/src/matchers.js | 33 +++++++++++++++++++ packages/expect/src/utils.js | 8 +++++ 3 files changed, 67 insertions(+) diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index bcd836e78097..c9997bb29e10 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -204,6 +204,32 @@ describe('.toBe()', () => { }); }); +describe('.toStrictEqual()', () => { + class TestClass { + constructor(a, b) { + this.a = a; + this.b = b; + } + } + + expect({ + test: new TestClass(1, 2), + }).toStrictEqual({test: new TestClass(1, 2)}); +}); + +describe('.not.toStrictEqual()', () => { + class TestClass { + constructor(a, b) { + this.a = a; + this.b = b; + } + } + + expect({ + test: new TestClass(1, 2), + }).not.toStrictEqual({test: {a: 1, b: 2}}); +}); + describe('.toEqual()', () => { [ [true, false], diff --git a/packages/expect/src/matchers.js b/packages/expect/src/matchers.js index 773502f7407b..6b4ad5dc7201 100644 --- a/packages/expect/src/matchers.js +++ b/packages/expect/src/matchers.js @@ -29,6 +29,7 @@ import { getPath, iterableEquality, subsetEquality, + typeEquality, } from './utils'; import {equals} from './jasmine_utils'; @@ -615,6 +616,38 @@ const matchers: MatchersObject = { return {message, pass}; }, + + toStrictEqual(received: any, expected: any) { + const pass = equals(received, expected, [iterableEquality, typeEquality]); + + const message = pass + ? () => + matcherHint('.not.toStrictEqual') + + '\n\n' + + `Expected value to not equal:\n` + + ` ${printExpected(expected)}\n` + + `Received:\n` + + ` ${printReceived(received)}` + : () => { + const diffString = diff(expected, received, { + expand: this.expand, + }); + return ( + matcherHint('.toStrictEqual') + + '\n\n' + + `Expected value to equal:\n` + + ` ${printExpected(expected)}\n` + + `Received:\n` + + ` ${printReceived(received)}` + + (diffString ? `\n\nDifference:\n\n${diffString}` : '') + ); + }; + + // Passing the the actual and expected objects so that a custom reporter + // could access them, for example in order to display a custom visual diff, + // or create a different error message + return {actual: received, expected, message, name: 'toStrictEqual', pass}; + }, }; export default matchers; diff --git a/packages/expect/src/utils.js b/packages/expect/src/utils.js index 12eceffd953f..a6cc277952e6 100644 --- a/packages/expect/src/utils.js +++ b/packages/expect/src/utils.js @@ -185,6 +185,14 @@ export const subsetEquality = (object: Object, subset: Object) => { ); }; +export const typeEquality = (a: any, b: any) => { + if (a == null || b == null || a.constructor.name === b.constructor.name) { + return undefined; + } + + return false; +}; + export const partition = ( items: Array, predicate: T => boolean, From 1fa154ef524f233d53507b9bee10acd31d2c2db7 Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 19 Apr 2018 15:23:46 +0100 Subject: [PATCH 2/6] address commments --- CHANGELOG.md | 1 + .../snapshot_escape_regex.js.snap | 9 ++++++ .../expect/src/__tests__/matchers.test.js | 30 ++++++++++--------- packages/expect/src/jasmine_utils.js | 28 ++++++++++------- packages/expect/src/matchers.js | 7 ++++- 5 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 integration-tests/snapshot-escape/__tests__/__snapshots__/snapshot_escape_regex.js.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b117fe5e892..bd332467bec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ ([#5980](https://github.com/facebook/jest/pull/5980)) * `[jest-message-util]` Include column in stack frames ([#5889](https://github.com/facebook/jest/pull/5889)) +* `[expect]` Introduce toStrictEqual ([#6032](https://github.com/facebook/jest/pull/6032)) ### Fixes diff --git a/integration-tests/snapshot-escape/__tests__/__snapshots__/snapshot_escape_regex.js.snap b/integration-tests/snapshot-escape/__tests__/__snapshots__/snapshot_escape_regex.js.snap new file mode 100644 index 000000000000..c1e3ebd4c7f6 --- /dev/null +++ b/integration-tests/snapshot-escape/__tests__/__snapshots__/snapshot_escape_regex.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`escape regex 1`] = `/\\\\dd\\\\ \\\\s\\+ \\\\w \\\\\\\\\\\\\\[ \\\\\\. blahzz\\.\\* \\[xyz\\]\\+/`; + +exports[`escape regex nested in object 1`] = ` +Object { + "regex": /\\\\dd\\\\ \\\\s\\+ \\\\w \\\\\\\\\\\\\\[ \\\\\\. blahzz\\.\\* \\[xyz\\]\\+/, +} +`; diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index c9997bb29e10..2e9c8a300e2b 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -212,22 +212,24 @@ describe('.toStrictEqual()', () => { } } - expect({ - test: new TestClass(1, 2), - }).toStrictEqual({test: new TestClass(1, 2)}); -}); + it('does not ignore keys with undefined values', () => { + expect({ + a: undefined, + b: 2, + }).not.toStrictEqual({b: 2}); + }); -describe('.not.toStrictEqual()', () => { - class TestClass { - constructor(a, b) { - this.a = a; - this.b = b; - } - } + it('passes when comparing same type', () => { + expect({ + test: new TestClass(1, 2), + }).toStrictEqual({test: new TestClass(1, 2)}); + }); - expect({ - test: new TestClass(1, 2), - }).not.toStrictEqual({test: {a: 1, b: 2}}); + it('does not pass for different types', () => { + expect({ + test: new TestClass(1, 2), + }).not.toStrictEqual({test: {a: 1, b: 2}}); + }); }); describe('.toEqual()', () => { diff --git a/packages/expect/src/jasmine_utils.js b/packages/expect/src/jasmine_utils.js index 5a79261c873f..404a45f64528 100644 --- a/packages/expect/src/jasmine_utils.js +++ b/packages/expect/src/jasmine_utils.js @@ -28,9 +28,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. type Tester = (a: any, b: any) => boolean | typeof undefined; // Extracted out of jasmine 2.5.2 -export function equals(a: any, b: any, customTesters?: Array): boolean { +export function equals(a: any, b: any, customTesters?: Array, strictCheck?: boolean): boolean { customTesters = customTesters || []; - return eq(a, b, [], [], customTesters); + return eq(a, b, [], [], customTesters, strictCheck ? hasKey : hasDefinedKey); } function isAsymmetric(obj) { @@ -56,7 +56,7 @@ function asymmetricMatch(a, b) { // Equality function lovingly adapted from isEqual in // [Underscore](http://underscorejs.org) -function eq(a, b, aStack, bStack, customTesters): boolean { +function eq(a, b, aStack, bStack, customTesters, hasKey): boolean { var result = true; var asymmetricResult = asymmetricMatch(a, b); @@ -160,7 +160,7 @@ function eq(a, b, aStack, bStack, customTesters): boolean { } while (size--) { - result = eq(a[size], b[size], aStack, bStack, customTesters); + result = eq(a[size], b[size], aStack, bStack, customTesters, hasKey); if (!result) { return false; } @@ -168,12 +168,12 @@ function eq(a, b, aStack, bStack, customTesters): boolean { } // Deep compare objects. - var aKeys = keys(a, className == '[object Array]'), + var aKeys = keys(a, className == '[object Array]', hasKey), key; size = aKeys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. - if (keys(b, className == '[object Array]').length !== size) { + if (keys(b, className == '[object Array]', hasKey).length !== size) { return false; } @@ -181,7 +181,7 @@ function eq(a, b, aStack, bStack, customTesters): boolean { key = aKeys[size]; // Deep compare each member - result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters); + result = hasKey(b, key) && eq(a[key], b[key], aStack, bStack, customTesters, hasKey); if (!result) { return false; @@ -194,11 +194,11 @@ function eq(a, b, aStack, bStack, customTesters): boolean { return result; } -function keys(obj, isArray) { +function keys(obj, isArray, hasKey) { var allKeys = (function(o) { var keys = []; for (var key in o) { - if (has(o, key)) { + if (hasKey(o, key)) { keys.push(key); } } @@ -223,9 +223,15 @@ function keys(obj, isArray) { return extraKeys; } -function has(obj, key) { +function hasDefinedKey(obj, key) { return ( - Object.prototype.hasOwnProperty.call(obj, key) && obj[key] !== undefined + hasKey(obj, key) && obj[key] !== undefined + ); +} + +function hasKey(obj, key) { + return ( + Object.prototype.hasOwnProperty.call(obj, key) ); } diff --git a/packages/expect/src/matchers.js b/packages/expect/src/matchers.js index 6b4ad5dc7201..a9046862e119 100644 --- a/packages/expect/src/matchers.js +++ b/packages/expect/src/matchers.js @@ -618,7 +618,12 @@ const matchers: MatchersObject = { }, toStrictEqual(received: any, expected: any) { - const pass = equals(received, expected, [iterableEquality, typeEquality]); + const pass = equals( + received, + expected, + [iterableEquality, typeEquality], + true, + ); const message = pass ? () => From 75118e5819461c061d2ea89b3e9ecdc7d448f834 Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Thu, 19 Apr 2018 16:41:14 +0100 Subject: [PATCH 3/6] docs + prettier --- CHANGELOG.md | 3 ++- docs/ExpectAPI.md | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd332467bec8..ee609f944a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,7 +71,8 @@ ([#5980](https://github.com/facebook/jest/pull/5980)) * `[jest-message-util]` Include column in stack frames ([#5889](https://github.com/facebook/jest/pull/5889)) -* `[expect]` Introduce toStrictEqual ([#6032](https://github.com/facebook/jest/pull/6032)) +* `[expect]` Introduce toStrictEqual + ([#6032](https://github.com/facebook/jest/pull/6032)) ### Fixes diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 9d0b47f25c53..8ddfac33816e 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -1096,6 +1096,33 @@ from the test. _Note: While snapshot testing is most commonly used with React components, any serializable value can be used as a snapshot._ +### `.toStrictEqual(value)` + +Much like `.toEqual` but fixes some strictness issues which for legacy reasons +cannot be applied to `.toEqual`. Use `.toStrictEqual` when you came about object +being semantically the same by having the same types as well as structure. + +Differences from `.toEqual`: + +* Keys with `undefined` properties are checked. e.g. `{a: undefined, b: 2}` does + not match `{b: 2}` when using `.toStrictEqual`. +* Object types are checked to be equal. e.g. A class instance with fields `a` + and `b` will not equal a literal object with fields `a` and `b`. + +```js +class LaCroix { + constructor(flavor) { + this.flavor = flavor; + } +} + +describe('the La Croix cans on my desk', () => { + test('are not semantically the same', () => { + expect(new LaCroix('lemon')).not.toBe({flavor: 'lemon'}); + }); +}); +``` + ### `.toThrow(error)` Also under the alias: `.toThrowError(error)` From 9ef1d5fe2c5f20ef8271a259f46840a33e93820e Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Thu, 19 Apr 2018 12:01:57 -0400 Subject: [PATCH 4/6] Update docs --- docs/ExpectAPI.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 8ddfac33816e..63d3ca4dfbea 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -1098,9 +1098,8 @@ serializable value can be used as a snapshot._ ### `.toStrictEqual(value)` -Much like `.toEqual` but fixes some strictness issues which for legacy reasons -cannot be applied to `.toEqual`. Use `.toStrictEqual` when you came about object -being semantically the same by having the same types as well as structure. +Use `.toStrictEqual` to test that objects have the same types as well as +structure. Differences from `.toEqual`: From db86b2890d7ceed137a31c4323a5ca79ee0d1136 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Thu, 19 Apr 2018 12:06:27 -0400 Subject: [PATCH 5/6] Also fix the test example --- docs/ExpectAPI.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 63d3ca4dfbea..2556cc11e9c2 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -1115,9 +1115,10 @@ class LaCroix { } } -describe('the La Croix cans on my desk', () => { - test('are not semantically the same', () => { - expect(new LaCroix('lemon')).not.toBe({flavor: 'lemon'}); +describe("the La Croix cans on my desk", () => { + test("are not semantically the same", () => { + expect(new LaCroix("lemon")).toEqual({ flavor: "lemon" }); + expect(new LaCroix("lemon")).not.toStrictEqual({ flavor: "lemon" }); }); }); ``` From 87a7b534906ccc24f9c7526d70eaa3fa4fce71f1 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Thu, 19 Apr 2018 12:12:00 -0400 Subject: [PATCH 6/6] lint md --- docs/ExpectAPI.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 2556cc11e9c2..50b87353188e 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -1115,10 +1115,10 @@ class LaCroix { } } -describe("the La Croix cans on my desk", () => { - test("are not semantically the same", () => { - expect(new LaCroix("lemon")).toEqual({ flavor: "lemon" }); - expect(new LaCroix("lemon")).not.toStrictEqual({ flavor: "lemon" }); +describe('the La Croix cans on my desk', () => { + test('are not semantically the same', () => { + expect(new LaCroix('lemon')).toEqual({flavor: 'lemon'}); + expect(new LaCroix('lemon')).not.toStrictEqual({flavor: 'lemon'}); }); }); ```