From 14f56383f8e5ad26d5f0b0e2b48c59438016fb57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 21 Dec 2016 19:33:56 +0100 Subject: [PATCH 01/14] First attempt to fix this --- packages/jest-diff/src/index.js | 8 ++++++-- packages/jest-matcher-utils/src/index.js | 7 +++++++ packages/jest-matchers/src/jasmine-utils.js | 20 +++++++++++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/jest-diff/src/index.js b/packages/jest-diff/src/index.js index ed70ea90d2be..088a323c1b6a 100644 --- a/packages/jest-diff/src/index.js +++ b/packages/jest-diff/src/index.js @@ -83,7 +83,9 @@ function compareObjects(a: Object, b: Object, options: ?DiffOptions) { try { diffMessage = diffStrings( - prettyFormat(a, FORMAT_OPTIONS), + a.jasmineToPrettyString + ? a.jasmineToPrettyString(FORMAT_OPTIONS) + : prettyFormat(a, FORMAT_OPTIONS), prettyFormat(b, FORMAT_OPTIONS), options, ); @@ -95,7 +97,9 @@ function compareObjects(a: Object, b: Object, options: ?DiffOptions) { // without calling `toJSON`. It's also possible that toJSON might throw. if (!diffMessage || diffMessage === NO_DIFF_MESSAGE) { diffMessage = diffStrings( - prettyFormat(a, FALLBACK_FORMAT_OPTIONS), + a.jasmineToPrettyString + ? a.jasmineToPrettyString(FALLBACK_FORMAT_OPTIONS) + : prettyFormat(a, FALLBACK_FORMAT_OPTIONS), prettyFormat(b, FALLBACK_FORMAT_OPTIONS), options, ); diff --git a/packages/jest-matcher-utils/src/index.js b/packages/jest-matcher-utils/src/index.js index e223d06d6e6b..ee42df217af1 100644 --- a/packages/jest-matcher-utils/src/index.js +++ b/packages/jest-matcher-utils/src/index.js @@ -50,6 +50,7 @@ const NUMBERS = [ // get the type of a value with handling the edge cases like `typeof []` // and `typeof null` const getType = (value: any): ValueType => { + console.log(value, typeof value); if (typeof value === 'undefined') { return 'undefined'; } else if (value === null) { @@ -71,6 +72,8 @@ const getType = (value: any): ValueType => { return 'map'; } else if (value.constructor === Set) { return 'set'; + } else if (value.toString() === 'ArrayContaining') { + return 'array'; } return 'object'; // $FlowFixMe https://github.com/facebook/flow/issues/1015 @@ -98,6 +101,10 @@ const stringify = (object: any, maxDepth?: number = 10): string => { }); } + if (object && object.jasmineToString) { + result = object.jasmineToString(); + } + return result.length >= MAX_LENGTH && maxDepth > 1 ? stringify(object, Math.floor(maxDepth / 2)) : result; diff --git a/packages/jest-matchers/src/jasmine-utils.js b/packages/jest-matchers/src/jasmine-utils.js index 8868f2216e49..1cb4da33c894 100644 --- a/packages/jest-matchers/src/jasmine-utils.js +++ b/packages/jest-matchers/src/jasmine-utils.js @@ -361,10 +361,17 @@ ArrayContaining.prototype.asymmetricMatch = function(other) { return true; }; +ArrayContaining.prototype.toString = function () { + return 'ArrayContaining'; +}; + ArrayContaining.prototype.jasmineToString = function () { - return ''; + return ''; }; +ArrayContaining.prototype.jasmineToPrettyString = function (options) { + return ''; +}; function ObjectContaining(sample) { this.sample = sample; @@ -411,10 +418,17 @@ ObjectContaining.prototype.asymmetricMatch = function(other) { return true; }; +ObjectContaining.prototype.toString = function() { + return 'ObjectContaining'; +}; + ObjectContaining.prototype.jasmineToString = function() { - return ''; + return ''; }; +ObjectContaining.prototype.jasmineToPrettyString = function(options) { + return ''; +}; function StringMatching(expected) { if (!isA('String', expected) && !isA('RegExp', expected)) { @@ -433,7 +447,7 @@ StringMatching.prototype.asymmetricMatch = function(other) { }; StringMatching.prototype.jasmineToString = function() { - return ''; + return ''; }; module.exports = { From 9b538b9ce6678d54f50f0d8303944771bdec4099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 30 Dec 2016 15:27:22 +0100 Subject: [PATCH 02/14] Extract asymmetric-matchers from jasmine-utils --- packages/jest-matcher-utils/src/index.js | 1 - .../jest-matchers/src/asymmetric-matchers.js | 180 ++++++++++++++++++ packages/jest-matchers/src/index.js | 2 +- packages/jest-matchers/src/jasmine-utils.js | 176 +---------------- 4 files changed, 189 insertions(+), 170 deletions(-) create mode 100644 packages/jest-matchers/src/asymmetric-matchers.js diff --git a/packages/jest-matcher-utils/src/index.js b/packages/jest-matcher-utils/src/index.js index ee42df217af1..4d654ef0d9ad 100644 --- a/packages/jest-matcher-utils/src/index.js +++ b/packages/jest-matcher-utils/src/index.js @@ -50,7 +50,6 @@ const NUMBERS = [ // get the type of a value with handling the edge cases like `typeof []` // and `typeof null` const getType = (value: any): ValueType => { - console.log(value, typeof value); if (typeof value === 'undefined') { return 'undefined'; } else if (value === null) { diff --git a/packages/jest-matchers/src/asymmetric-matchers.js b/packages/jest-matchers/src/asymmetric-matchers.js new file mode 100644 index 000000000000..d51acaa05f20 --- /dev/null +++ b/packages/jest-matchers/src/asymmetric-matchers.js @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2014, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow + */ + +'use strict'; + +const prettyFormat = require('pretty-format'); +const { + contains, + equals, + fnNameFor, + hasProperty, + isA, + isUndefined, +} = require('./jasmine-utils'); + +class Any { + expectedObject: any; + + constructor(expectedObject: any) { + if (typeof expectedObject === 'undefined') { + throw new TypeError( + 'jasmine.any() expects to be passed a constructor function. ' + + 'Please pass one or use jasmine.anything() to match any object.' + ); + } + this.expectedObject = expectedObject; + } + + asymmetricMatch(other: any) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + } + + jasmineToString() { + return ''; + } +} + +class Anything { + asymmetricMatch(other: any) { + return !isUndefined(other) && other !== null; + } + + jasmineToString() { + return ''; + } +} + +class ArrayContaining { + sample: Array; + + constructor(sample: Array) { + this.sample = sample; + } + + asymmetricMatch(other: Array) { + const className = Object.prototype.toString.call(this.sample); + if (className !== '[object Array]') { + throw new Error( + 'You must provide an array to arrayContaining, not \'' + + typeof this.sample + '\'.' + ); + } + + for (let i = 0; i < this.sample.length; i++) { + const item = this.sample[i]; + if (!contains(other, item)) { + return false; + } + } + + return true; + } + + toString() { + return 'ArrayContaining'; + } + + jasmineToString() { + return ''; + } + + jasmineToPrettyString(options: Object) { + return ''; + } + } + +class ObjectContaining { + sample: Object; + + constructor(sample: Object) { + this.sample = sample; + } + + asymmetricMatch(other: Object) { + if (typeof this.sample !== 'object') { + throw new Error( + 'You must provide an object to objectContaining, not \'' + + typeof this.sample + '\'.' + ); + } + + for (const property in this.sample) { + if ( + !hasProperty(other, property) || + !equals(this.sample[property], other[property]) + ) { + return false; + } + } + + return true; + } + + toString() { + return 'ObjectContaining'; + } + + jasmineToString() { + return ''; + } + + jasmineToPrettyString(options: Object) { + return ''; + } +} + +class StringMatching { + regexp: RegExp; + + constructor(expected: string | RegExp) { + if (!isA('String', expected) && !isA('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + + this.regexp = new RegExp(expected); + } + + asymmetricMatch(other: string) { + return this.regexp.test(other); + } + + jasmineToString() { + return ''; + } +} + +module.exports = { + any: (expectedObject: any) => new Any(expectedObject), + anything: () => new Anything(), + arrayContaining: (sample: Array) => new ArrayContaining(sample), + objectContaining: (sample: Object) => new ObjectContaining(sample), + stringMatching: (expected: string | RegExp) => new StringMatching(expected), +}; diff --git a/packages/jest-matchers/src/index.js b/packages/jest-matchers/src/index.js index da5da68ee44b..2747edd90d53 100644 --- a/packages/jest-matchers/src/index.js +++ b/packages/jest-matchers/src/index.js @@ -32,7 +32,7 @@ const { arrayContaining, objectContaining, stringMatching, -} = require('./jasmine-utils'); +} = require('./asymmetric-matchers'); const GLOBAL_STATE = Symbol.for('$$jest-matchers-object'); diff --git a/packages/jest-matchers/src/jasmine-utils.js b/packages/jest-matchers/src/jasmine-utils.js index 1cb4da33c894..1273fda9c2af 100644 --- a/packages/jest-matchers/src/jasmine-utils.js +++ b/packages/jest-matchers/src/jasmine-utils.js @@ -25,8 +25,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; -const prettyFormat = require('pretty-format'); - // Extracted out of jasmine 2.5.2 function equals(a, b, customTesters) { customTesters = customTesters || []; @@ -247,22 +245,6 @@ function has(obj, key) { return Object.prototype.hasOwnProperty.call(obj, key) && obj[key] !== undefined; } -function isFunction(obj) { - return typeof obj === 'function'; -} - -function isObjectConstructor(ctor) { - // aCtor instanceof aCtor is true for the Object and Function - // constructors (since a constructor is-a Function and a function is-a - // Object). We don't just compare ctor === Object because the constructor - // might come from a different frame with different globals. - return isFunction(ctor) && ctor instanceof ctor; -} - -function isUndefined(obj) { - return obj === void 0; -} - function isA(typeName, value) { return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; } @@ -276,109 +258,12 @@ function fnNameFor(func) { return func.name; } - var matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/); + const matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/); return matches ? matches[1] : ''; } - -function Any(expectedObject) { - if (typeof expectedObject === 'undefined') { - throw new TypeError( - 'jasmine.any() expects to be passed a constructor function. ' + - 'Please pass one or use jasmine.anything() to match any object.' - ); - } - this.expectedObject = expectedObject; -} - -function any(expectedObject) { - return new Any(expectedObject); -} - -Any.prototype.asymmetricMatch = function(other) { - if (this.expectedObject == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedObject == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedObject == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedObject == Object) { - return typeof other == 'object'; - } - - if (this.expectedObject == Boolean) { - return typeof other == 'boolean'; - } - - return other instanceof this.expectedObject; -}; - -Any.prototype.jasmineToString = function() { - return ''; -}; - - -function Anything() {} - -function anything() { - return new Anything(); -} - -Anything.prototype.asymmetricMatch = function(other) { - return !isUndefined(other) && other !== null; -}; - -Anything.prototype.jasmineToString = function() { - return ''; -}; - - -function ArrayContaining(sample) { - this.sample = sample; -} - -function arrayContaining(sample) { - return new ArrayContaining(sample); -} - -ArrayContaining.prototype.asymmetricMatch = function(other) { - var className = Object.prototype.toString.call(this.sample); - if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } - - for (var i = 0; i < this.sample.length; i++) { - var item = this.sample[i]; - if (!contains(other, item)) { - return false; - } - } - - return true; -}; - -ArrayContaining.prototype.toString = function () { - return 'ArrayContaining'; -}; - -ArrayContaining.prototype.jasmineToString = function () { - return ''; -}; - -ArrayContaining.prototype.jasmineToPrettyString = function (options) { - return ''; -}; - -function ObjectContaining(sample) { - this.sample = sample; -} - -function objectContaining(sample) { - return new ObjectContaining(sample); +function isUndefined(obj) { + return obj === void 0; } function getPrototype(obj) { @@ -405,56 +290,11 @@ function hasProperty(obj, property) { return hasProperty(getPrototype(obj), property); } -ObjectContaining.prototype.asymmetricMatch = function(other) { - if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } - - for (var property in this.sample) { - if (!hasProperty(other, property) || - !equals(this.sample[property], other[property])) { - return false; - } - } - - return true; -}; - -ObjectContaining.prototype.toString = function() { - return 'ObjectContaining'; -}; - -ObjectContaining.prototype.jasmineToString = function() { - return ''; -}; - -ObjectContaining.prototype.jasmineToPrettyString = function(options) { - return ''; -}; - -function StringMatching(expected) { - if (!isA('String', expected) && !isA('RegExp', expected)) { - throw new Error('Expected is not a String or a RegExp'); - } - - this.regexp = new RegExp(expected); -} - -function stringMatching(expected) { - return new StringMatching(expected); -} - -StringMatching.prototype.asymmetricMatch = function(other) { - return this.regexp.test(other); -}; - -StringMatching.prototype.jasmineToString = function() { - return ''; -}; - module.exports = { - any, - anything, - arrayContaining, + contains, equals, - objectContaining, - stringMatching, + fnNameFor, + hasProperty, + isA, + isUndefined, }; From c7a2bff01be12526e76e851db3007ab146393f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 30 Dec 2016 15:43:01 +0100 Subject: [PATCH 03/14] Remove adnotations --- packages/jest-matchers/src/asymmetric-matchers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/jest-matchers/src/asymmetric-matchers.js b/packages/jest-matchers/src/asymmetric-matchers.js index d51acaa05f20..d3f238ee1cf6 100644 --- a/packages/jest-matchers/src/asymmetric-matchers.js +++ b/packages/jest-matchers/src/asymmetric-matchers.js @@ -26,8 +26,8 @@ class Any { constructor(expectedObject: any) { if (typeof expectedObject === 'undefined') { throw new TypeError( - 'jasmine.any() expects to be passed a constructor function. ' + - 'Please pass one or use jasmine.anything() to match any object.' + 'any() expects to be passed a constructor function. ' + + 'Please pass one or use anything() to match any object.' ); } this.expectedObject = expectedObject; @@ -58,7 +58,7 @@ class Any { } jasmineToString() { - return ''; + return ''; } } @@ -68,7 +68,7 @@ class Anything { } jasmineToString() { - return ''; + return ''; } } From 1da977e49a0859fffc07a3553497cff0e0e77cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 30 Dec 2016 15:52:23 +0100 Subject: [PATCH 04/14] Remove obsolete snapshots --- .../__snapshots__/matchers-test.js.snap | 86 +++++++++++-------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap index 7c31e8fade00..55f4d234f5f7 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap @@ -183,7 +183,7 @@ exports[`.toBeCloseTo() accepts an optional precision argument: [0, 0.000004, 5] Expected value not to be close to (with 5-digit precision): 0.000004 -Received: +Received: 0" `; @@ -192,7 +192,7 @@ exports[`.toBeCloseTo() accepts an optional precision argument: [0, 0.0001, 3] 1 Expected value not to be close to (with 3-digit precision): 0.0001 -Received: +Received: 0" `; @@ -201,7 +201,7 @@ exports[`.toBeCloseTo() accepts an optional precision argument: [0, 0.1, 0] 1`] Expected value not to be close to (with 0-digit precision): 0.1 -Received: +Received: 0" `; @@ -210,7 +210,7 @@ exports[`.toBeCloseTo() passes: [0, 0.001] 1`] = ` Expected value not to be close to (with 2-digit precision): 0.001 -Received: +Received: 0" `; @@ -219,7 +219,7 @@ exports[`.toBeCloseTo() passes: [0, 0] 1`] = ` Expected value not to be close to (with 2-digit precision): 0 -Received: +Received: 0" `; @@ -228,7 +228,7 @@ exports[`.toBeCloseTo() passes: [1.23, 1.225] 1`] = ` Expected value not to be close to (with 2-digit precision): 1.225 -Received: +Received: 1.23" `; @@ -237,7 +237,7 @@ exports[`.toBeCloseTo() passes: [1.23, 1.226] 1`] = ` Expected value not to be close to (with 2-digit precision): 1.226 -Received: +Received: 1.23" `; @@ -246,7 +246,7 @@ exports[`.toBeCloseTo() passes: [1.23, 1.229] 1`] = ` Expected value not to be close to (with 2-digit precision): 1.229 -Received: +Received: 1.23" `; @@ -255,7 +255,7 @@ exports[`.toBeCloseTo() passes: [1.23, 1.234] 1`] = ` Expected value not to be close to (with 2-digit precision): 1.234 -Received: +Received: 1.23" `; @@ -264,7 +264,7 @@ exports[`.toBeCloseTo() throws: [0, 0.01] 1`] = ` Expected value to be close to (with 2-digit precision): 0.01 -Received: +Received: 0" `; @@ -273,7 +273,7 @@ exports[`.toBeCloseTo() throws: [1, 1.23] 1`] = ` Expected value to be close to (with 2-digit precision): 1.23 -Received: +Received: 1" `; @@ -282,7 +282,7 @@ exports[`.toBeCloseTo() throws: [1.23, 1.2249999] 1`] = ` Expected value to be close to (with 2-digit precision): 1.2249999 -Received: +Received: 1.23" `; @@ -1742,20 +1742,20 @@ Received: \"abc\"" `; -exports[`.toEqual() expect("abcd").not.toEqual({"regexp": /bc/}) 1`] = ` +exports[`.toEqual() expect("abcd").not.toEqual() 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: - {\"regexp\": /bc/} +  Received: \"abcd\"" `; -exports[`.toEqual() expect("abd").toEqual({"regexp": /bc/i}) 1`] = ` +exports[`.toEqual() expect("abd").toEqual() 1`] = ` "expect(received).toEqual(expected) Expected value to equal: - {\"regexp\": /bc/i} +  Received: \"abd\" @@ -1773,33 +1773,43 @@ Received: \"banana\"" `; -exports[`.toEqual() expect([1, 2, 3]).not.toEqual({"sample": [2, 3]}) 1`] = ` +exports[`.toEqual() expect([1, 2, 3]).not.toEqual() 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: - {\"sample\": [2, 3]} +  Received: [1, 2, 3]" `; -exports[`.toEqual() expect([1, 3]).toEqual({"sample": [1, 2]}) 1`] = ` +exports[`.toEqual() expect([1, 3]).toEqual() 1`] = ` "expect(received).toEqual(expected) Expected value to equal: - {\"sample\": [1, 2]} +  Received: [1, 3] Difference: - Comparing two different types of values. Expected object but received array." +- Expected ++ Received + +@@ -1,4 +1,4 @@ +- ++ 3, ++]" `; -exports[`.toEqual() expect([Function anonymous]).not.toEqual({"expectedObject": [Function Function]}) 1`] = ` +exports[`.toEqual() expect([Function anonymous]).not.toEqual() 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: - {\"expectedObject\": [Function Function]} +  Received: [Function anonymous]" `; @@ -1813,20 +1823,20 @@ Received: {\"a\": 1, \"b\": [Function b], \"c\": true}" `; -exports[`.toEqual() expect({"a": 1, "b": 2}).not.toEqual({"sample": {"a": 1}}) 1`] = ` +exports[`.toEqual() expect({"a": 1, "b": 2}).not.toEqual() 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: - {\"sample\": {\"a\": 1}} +  Received: {\"a\": 1, \"b\": 2}" `; -exports[`.toEqual() expect({"a": 1, "b": 2}).toEqual({"sample": {"a": 2}}) 1`] = ` +exports[`.toEqual() expect({"a": 1, "b": 2}).toEqual() 1`] = ` "expect(received).toEqual(expected) Expected value to equal: - {\"sample\": {\"a\": 2}} +  Received: {\"a\": 1, \"b\": 2} @@ -1835,14 +1845,14 @@ Difference: - Expected + Received --ObjectContaining { -- \"sample\": Object { -- \"a\": 2, -- }, +@@ -1,3 +1,4 @@ +- +Object { + \"a\": 1, + \"b\": 2, - }" ++}" `; exports[`.toEqual() expect({"a": 5}).toEqual({"b": 6}) 1`] = ` @@ -1904,11 +1914,11 @@ Difference: Comparing two different types of values. Expected undefined but received null." `; -exports[`.toEqual() expect(true).not.toEqual({}) 1`] = ` +exports[`.toEqual() expect(true).not.toEqual() 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: - {} +  Received: true" `; @@ -1931,11 +1941,11 @@ Received: true" `; -exports[`.toEqual() expect(undefined).toEqual({"expectedObject": [Function Function]}) 1`] = ` +exports[`.toEqual() expect(undefined).toEqual() 1`] = ` "expect(received).toEqual(expected) Expected value to equal: - {\"expectedObject\": [Function Function]} +  Received: undefined @@ -1944,11 +1954,11 @@ Difference: Comparing two different types of values. Expected object but received undefined." `; -exports[`.toEqual() expect(undefined).toEqual({}) 1`] = ` +exports[`.toEqual() expect(undefined).toEqual() 1`] = ` "expect(received).toEqual(expected) Expected value to equal: - {} +  Received: undefined From f4d5d9c6c88bc362d086da5f8bc13578d8de5e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 2 Jan 2017 18:19:58 +0100 Subject: [PATCH 05/14] Refactor asymmetric-matchers --- .../jest-matchers/src/asymmetric-matchers.js | 90 +++++++++++-------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/packages/jest-matchers/src/asymmetric-matchers.js b/packages/jest-matchers/src/asymmetric-matchers.js index d3f238ee1cf6..4f182f18ae82 100644 --- a/packages/jest-matchers/src/asymmetric-matchers.js +++ b/packages/jest-matchers/src/asymmetric-matchers.js @@ -20,62 +20,80 @@ const { isUndefined, } = require('./jasmine-utils'); -class Any { - expectedObject: any; +class AsymmetricMatcher { + $$typeof: Symbol; - constructor(expectedObject: any) { - if (typeof expectedObject === 'undefined') { + constructor() { + this.$$typeof = Symbol.for('jest.asymmetricMatcher'); + } +} + +class Any extends AsymmetricMatcher { + sample: any; + + constructor(sample: any) { + super(); + if (typeof sample === 'undefined') { throw new TypeError( 'any() expects to be passed a constructor function. ' + 'Please pass one or use anything() to match any object.' ); } - this.expectedObject = expectedObject; + this.sample = sample; } asymmetricMatch(other: any) { - if (this.expectedObject == String) { + if (this.sample == String) { return typeof other == 'string' || other instanceof String; } - if (this.expectedObject == Number) { + if (this.sample == Number) { return typeof other == 'number' || other instanceof Number; } - if (this.expectedObject == Function) { + if (this.sample == Function) { return typeof other == 'function' || other instanceof Function; } - if (this.expectedObject == Object) { + if (this.sample == Object) { return typeof other == 'object'; } - if (this.expectedObject == Boolean) { + if (this.sample == Boolean) { return typeof other == 'boolean'; } - return other instanceof this.expectedObject; + return other instanceof this.sample; } - jasmineToString() { - return ''; + toString() { + return 'Any'; + } + + toAsymmetricMatcher() { + return ''; } } -class Anything { +class Anything extends AsymmetricMatcher { asymmetricMatch(other: any) { return !isUndefined(other) && other !== null; } - jasmineToString() { + toString() { + return 'Anything'; + } + + toAsymmetricMatcher() { return ''; } } -class ArrayContaining { +class ArrayContaining extends AsymmetricMatcher { sample: Array; constructor(sample: Array) { + super(); this.sample = sample; } @@ -102,19 +120,16 @@ class ArrayContaining { return 'ArrayContaining'; } - jasmineToString() { - return ''; - } - - jasmineToPrettyString(options: Object) { - return ''; + toAsymmetricMatcher(print: Function) { + return ''; } } -class ObjectContaining { +class ObjectContaining extends AsymmetricMatcher { sample: Object; constructor(sample: Object) { + super(); this.sample = sample; } @@ -142,32 +157,33 @@ class ObjectContaining { return 'ObjectContaining'; } - jasmineToString() { - return ''; - } - - jasmineToPrettyString(options: Object) { - return ''; + toAsymmetricMatcher(print: Function) { + return ''; } } -class StringMatching { - regexp: RegExp; +class StringMatching extends AsymmetricMatcher { + sample: RegExp; - constructor(expected: string | RegExp) { - if (!isA('String', expected) && !isA('RegExp', expected)) { + constructor(sample: string | RegExp) { + super(); + if (!isA('String', sample) && !isA('RegExp', sample)) { throw new Error('Expected is not a String or a RegExp'); } - this.regexp = new RegExp(expected); + this.sample = new RegExp(sample); } asymmetricMatch(other: string) { - return this.regexp.test(other); + return this.sample.test(other); + } + + toString() { + return 'StringMatching'; } - jasmineToString() { - return ''; + toAsymmetricMatcher(print: Function) { + return ''; } } From 52787d866796c98d117356275cac2f927c5eeed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 2 Jan 2017 18:18:14 +0100 Subject: [PATCH 06/14] Add AsymmetricMatcher plugin for pretty-format --- .../src/__tests__/AsymmetricMatcher-test.js | 138 ++++++++++++++++++ .../src/plugins/AsymmetricMatcher.js | 23 +++ 2 files changed, 161 insertions(+) create mode 100644 packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js create mode 100644 packages/pretty-format/src/plugins/AsymmetricMatcher.js diff --git a/packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js b/packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js new file mode 100644 index 000000000000..1d3f0b532327 --- /dev/null +++ b/packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* eslint-disable max-len */ + +'use strict'; + +const prettyFormat = require('../'); +const AsymmetricMatcher = require('../plugins/AsymmetricMatcher'); +const jestExpect = require('jest-matchers'); +let options; + +function fnNameFor(func) { + if (func.name) { + return func.name; + } + + const matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/); + return matches ? matches[1] : ''; +} + +beforeEach(() => { + options = {plugins: [AsymmetricMatcher]}; +}); + +[ + String, + Function, + Array, + Object, + RegExp, + Symbol, + Function, + () => {}, + function namedFuntction() {}, +].forEach(type => { + test(`supports any(${fnNameFor(type)})`, () => { + const result = prettyFormat(jestExpect.any(type), options); + expect(result).toEqual(``); + }); + + test(`supports nested any(${fnNameFor(type)})`, () => { + const result = prettyFormat({ + test: { + nested: jestExpect.any(type), + }, + }, options); + expect(result).toEqual(`Object {\n "test": Object {\n "nested": ,\n },\n}`); + }); +}); + +test(`anything()`, () => { + const result = prettyFormat(jestExpect.anything(), options); + expect(result).toEqual(''); +}); + +test(`arrayContaining()`, () => { + const result = prettyFormat(jestExpect.arrayContaining([1, 2]), options); + expect(result).toEqual( +`` + ); +}); + +test(`objectContaining()`, () => { + const result = prettyFormat(jestExpect.objectContaining({a: 'test'}), options); + expect(result).toEqual( +`` + ); +}); + +test(`stringMatching(string)`, () => { + const result = prettyFormat(jestExpect.stringMatching('jest'), options); + expect(result).toEqual(''); +}); + +test(`stringMatching(regexp)`, () => { + const result = prettyFormat(jestExpect.stringMatching(/(jest|niema).*/), options); + expect(result).toEqual(''); +}); + +test(`supports multiple nested asymmetric matchers`, () => { + const result = prettyFormat({ + test: { + nested: jestExpect.objectContaining({ + a: jestExpect.arrayContaining([1]), + b: jestExpect.anything(), + c: jestExpect.any(String), + d: jestExpect.stringMatching('jest'), + e: jestExpect.objectContaining({test: 'case'}), + }), + }, + }, options); + expect(result).toEqual( +`Object { + "test": Object { + "nested": , + "b": , + "c": , + "d": , + "e": , + })>, + }, +}` + ); +}); + +test(`supports minified output`, () => { + options.min = true; + const result = prettyFormat({ + test: { + nested: jestExpect.objectContaining({ + a: jestExpect.arrayContaining([1]), + b: jestExpect.anything(), + c: jestExpect.any(String), + d: jestExpect.stringMatching('jest'), + e: jestExpect.objectContaining({test: 'case'}), + }), + }, + }, options); + expect(result).toEqual( +`{"test": {"nested": , "b": , "c": , "d": , "e": })>}}` + ); +}); diff --git a/packages/pretty-format/src/plugins/AsymmetricMatcher.js b/packages/pretty-format/src/plugins/AsymmetricMatcher.js new file mode 100644 index 000000000000..43c2f1744a59 --- /dev/null +++ b/packages/pretty-format/src/plugins/AsymmetricMatcher.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * @flow + */ + +'use strict'; + +const asymmetricMatcher = Symbol.for('jest.asymmetricMatcher'); + +module.exports = { + print: ( + val: any, + print: Function, + indent: Function, + opts: Object, + colors: Object + ) => val.toAsymmetricMatcher(print), + test: (object: any) => object.$$typeof === asymmetricMatcher, +}; From 85559597fa2060f9a3d7f2f71501bf6f1ef176b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 2 Jan 2017 18:22:30 +0100 Subject: [PATCH 07/14] Use AsymmetricMatcher plugin in diffs --- packages/jest-diff/src/index.js | 15 ++++++++------- packages/jest-matcher-utils/src/index.js | 9 +++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/jest-diff/src/index.js b/packages/jest-diff/src/index.js index 088a323c1b6a..e8d93049e71c 100644 --- a/packages/jest-diff/src/index.js +++ b/packages/jest-diff/src/index.js @@ -14,6 +14,7 @@ import type {DiffOptions} from './diffStrings'; const ReactElementPlugin = require('pretty-format/build/plugins/ReactElement'); const ReactTestComponentPlugin = require('pretty-format/build/plugins/ReactTestComponent'); +const AsymmetricMatcherPlugin = require('pretty-format/build/plugins/AsymmetricMatcher'); const chalk = require('chalk'); const diffStrings = require('./diffStrings'); @@ -25,7 +26,11 @@ const { SIMILAR_MESSAGE, } = require('./constants'); -const PLUGINS = [ReactTestComponentPlugin, ReactElementPlugin]; +const PLUGINS = [ + ReactTestComponentPlugin, + ReactElementPlugin, + AsymmetricMatcherPlugin, +]; const FORMAT_OPTIONS = { plugins: PLUGINS, }; @@ -83,9 +88,7 @@ function compareObjects(a: Object, b: Object, options: ?DiffOptions) { try { diffMessage = diffStrings( - a.jasmineToPrettyString - ? a.jasmineToPrettyString(FORMAT_OPTIONS) - : prettyFormat(a, FORMAT_OPTIONS), + prettyFormat(a, FORMAT_OPTIONS), prettyFormat(b, FORMAT_OPTIONS), options, ); @@ -97,9 +100,7 @@ function compareObjects(a: Object, b: Object, options: ?DiffOptions) { // without calling `toJSON`. It's also possible that toJSON might throw. if (!diffMessage || diffMessage === NO_DIFF_MESSAGE) { diffMessage = diffStrings( - a.jasmineToPrettyString - ? a.jasmineToPrettyString(FALLBACK_FORMAT_OPTIONS) - : prettyFormat(a, FALLBACK_FORMAT_OPTIONS), + prettyFormat(a, FALLBACK_FORMAT_OPTIONS), prettyFormat(b, FALLBACK_FORMAT_OPTIONS), options, ); diff --git a/packages/jest-matcher-utils/src/index.js b/packages/jest-matcher-utils/src/index.js index 4d654ef0d9ad..d5d272d830ab 100644 --- a/packages/jest-matcher-utils/src/index.js +++ b/packages/jest-matcher-utils/src/index.js @@ -12,6 +12,9 @@ const chalk = require('chalk'); const prettyFormat = require('pretty-format'); +const AsymmetricMatcherPlugin = require('pretty-format/build/plugins/AsymmetricMatcher'); + +const PLUGINS = [AsymmetricMatcherPlugin]; export type ValueType = | 'array' @@ -91,19 +94,17 @@ const stringify = (object: any, maxDepth?: number = 10): string => { result = prettyFormat(object, { maxDepth, min: true, + plugins: PLUGINS, }); } catch (e) { result = prettyFormat(object, { callToJSON: false, maxDepth, min: true, + plugins: PLUGINS, }); } - if (object && object.jasmineToString) { - result = object.jasmineToString(); - } - return result.length >= MAX_LENGTH && maxDepth > 1 ? stringify(object, Math.floor(maxDepth / 2)) : result; From 0eefe659a21c96773a4e114cd69b61bc20530639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 2 Jan 2017 18:29:12 +0100 Subject: [PATCH 08/14] Remove unused prettyFormat from asymmetric-matcher --- packages/jest-matchers/src/asymmetric-matchers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jest-matchers/src/asymmetric-matchers.js b/packages/jest-matchers/src/asymmetric-matchers.js index 4f182f18ae82..d8087dba7f2f 100644 --- a/packages/jest-matchers/src/asymmetric-matchers.js +++ b/packages/jest-matchers/src/asymmetric-matchers.js @@ -10,7 +10,6 @@ 'use strict'; -const prettyFormat = require('pretty-format'); const { contains, equals, From 5d6f6b430194cb9ae3ce65a01e207b8d4a9bc369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 2 Jan 2017 18:37:36 +0100 Subject: [PATCH 09/14] Check for object presence in AsymmetricMatcher --- packages/pretty-format/src/plugins/AsymmetricMatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pretty-format/src/plugins/AsymmetricMatcher.js b/packages/pretty-format/src/plugins/AsymmetricMatcher.js index 43c2f1744a59..fb9a330af69d 100644 --- a/packages/pretty-format/src/plugins/AsymmetricMatcher.js +++ b/packages/pretty-format/src/plugins/AsymmetricMatcher.js @@ -19,5 +19,5 @@ module.exports = { opts: Object, colors: Object ) => val.toAsymmetricMatcher(print), - test: (object: any) => object.$$typeof === asymmetricMatcher, + test: (object: any) => object && object.$$typeof === asymmetricMatcher, }; From 560d15ce8ee3326f3b72a2cb2a70abb3858e4c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 2 Jan 2017 18:37:48 +0100 Subject: [PATCH 10/14] Update snapshots --- .../__tests__/__snapshots__/index-test.js.snap | 17 +++++++++-------- .../__snapshots__/matchers-test.js.snap | 4 ++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap index 22aabaa1c4ab..fd90b142d2b9 100644 --- a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap +++ b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap @@ -1,23 +1,24 @@ -exports[`.stringify() reduces maxDepth if stringifying very large objects 1`] = `"{\"a\": 1, \"b\": [Object]}"`; +exports[`.stringify() reduces maxDepth if stringifying very large objects 1`] = `"{"a": 1, "b": [Object]}"`; -exports[`.stringify() reduces maxDepth if stringifying very large objects 2`] = `"{\"a\": 1, \"b\": {\"0\": \"test\", \"1\": \"test\", \"2\": \"test\", \"3\": \"test\", \"4\": \"test\", \"5\": \"test\", \"6\": \"test\", \"7\": \"test\", \"8\": \"test\", \"9\": \"test\"}}"`; +exports[`.stringify() reduces maxDepth if stringifying very large objects 2`] = `"{"a": 1, "b": {"0": "test", "1": "test", "2": "test", "3": "test", "4": "test", "5": "test", "6": "test", "7": "test", "8": "test", "9": "test"}}"`; exports[`.stringify() toJSON errors when comparing two objects 1`] = ` "expect(received).toEqual(expected) Expected value to equal: - {\"b\": 1, \"toJSON\": [Function toJSON]} + {"b": 1, "toJSON": [Function toJSON]} Received: - {\"a\": 1, \"toJSON\": [Function toJSON]} + {"a": 1, "toJSON": [Function toJSON]} Difference: - Expected + Received - Object { -- \"b\": 1, -+ \"a\": 1, - \"toJSON\": [Function toJSON], +@@ -1,4 +1,4 @@ + Object { +- "b": 1, ++ "a": 1, + "toJSON": [Function toJSON],  }" `; diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap index 55f4d234f5f7..ecb25c28da0b 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap @@ -1814,11 +1814,11 @@ Received: [Function anonymous]" `; -exports[`.toEqual() expect({"a": 1, "b": [Function b], "c": true}).not.toEqual({"a": 1, "b": {"expectedObject": [Function Function]}, "c": {}}) 1`] = ` +exports[`.toEqual() expect({"a": 1, "b": [Function b], "c": true}).not.toEqual({"a": 1, "b": , "c": }) 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: - {\"a\": 1, \"b\": {\"expectedObject\": [Function Function]}, \"c\": {}} + {\"a\": 1, \"b\": , \"c\": } Received: {\"a\": 1, \"b\": [Function b], \"c\": true}" `; From d741f29ff15eaa49f57eabaec0fe04f3799688f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 2 Jan 2017 22:12:41 +0100 Subject: [PATCH 11/14] Tests for asymmetric-matchers --- .../src/__tests__/asymmetric-matchers-test.js | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js diff --git a/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js b/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js new file mode 100644 index 000000000000..c606bfb5e76c --- /dev/null +++ b/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails oncall+jsinfra + */ + /* eslint-disable max-len */ + +'use strict'; + +const jestExpect = require('../'); +const { + any, + anything, + arrayContaining, + objectContaining, + stringMatching, +} = require('../asymmetric-matchers'); +const prettyFormat = require('pretty-format'); + +const print = string => prettyFormat(string, {min: true}); + +test('Any.asymmetricMatch()', () => { + const Thing = function() {}; + + [ + any(String).asymmetricMatch('jest'), + any(Number).asymmetricMatch(1), + any(Function).asymmetricMatch(() => {}), + any(Boolean).asymmetricMatch(true), + any(Object).asymmetricMatch({}), + any(Array).asymmetricMatch([]), + any(Thing).asymmetricMatch(new Thing()), + ].forEach(test => { + jestExpect(test).toBe(true); + }); +}); + +test('Any.toAsymmetricMatcher()', () => { + jestExpect(any(Number).toAsymmetricMatcher()).toBe(''); +}); + +test('Any throws when called with empty constructor', () => { + expect(() => any()).toThrow(); +}); + +test('Anything matches any type', () => { + [ + anything().asymmetricMatch('jest'), + anything().asymmetricMatch(1), + anything().asymmetricMatch(() => {}), + anything().asymmetricMatch(true), + anything().asymmetricMatch({}), + anything().asymmetricMatch([]), + ].forEach(test => { + jestExpect(test).toBe(true); + }); +}); + +test('Anything does not match null and undefined', () => { + [ + anything().asymmetricMatch(null), + anything().asymmetricMatch(undefined), + ].forEach(test => { + jestExpect(test).toBe(false); + }); +}); + +test('Anything.toAsymmetricMatcher()', () => { + jestExpect(anything().toAsymmetricMatcher()).toBe(''); +}); + +test('ArrayContaining matches', () => { + [ + arrayContaining([]).asymmetricMatch('jest'), + arrayContaining(['foo']).asymmetricMatch(['foo']), + arrayContaining(['foo']).asymmetricMatch(['foo', 'bar']), + arrayContaining([]).asymmetricMatch({}), + ].forEach(test => { + jestExpect(test).toEqual(true); + }); +}); + +test('ArrayContaining does not match', () => { + jestExpect(arrayContaining(['foo']).asymmetricMatch(['bar'])).toBe(false); +}); + +test('ArrayContaining throws for non-arrays', () => { + jestExpect(() => { + arrayContaining('foo').asymmetricMatch([]); + }).toThrow(); +}); + +test('ArrayContaining.toAsymmetricMatcher()', () => { + jestExpect(arrayContaining(['foo', 'bar']).toAsymmetricMatcher(print)) + .toBe(''); +}); + +test('ObjectContaining matches', () => { + [ + objectContaining({}).asymmetricMatch('jest'), + objectContaining({foo: 'foo'}).asymmetricMatch({foo: 'foo', jest: 'jest'}), + objectContaining({foo: undefined}).asymmetricMatch({foo: undefined}), + objectContaining({first: objectContaining({second: {}})}).asymmetricMatch({first: {second: {}}}), + ].forEach(test => { + jestExpect(test).toEqual(true); + }); +}); + +test('ObjectContaining does not match', () => { + [ + objectContaining({foo: 'foo'}).asymmetricMatch({bar: 'bar'}), + objectContaining({foo: 'foo'}).asymmetricMatch({foo: 'foox'}), + objectContaining({foo: undefined}).asymmetricMatch({}), + ].forEach(test => { + jestExpect(test).toEqual(false); + }); +}); + +test('ObjectContaining matches defined properties', () => { + const definedPropertyObject = {}; + Object.defineProperty(definedPropertyObject, 'foo', {get: () => 'bar'}); + expect(objectContaining({foo: 'bar'}).asymmetricMatch(definedPropertyObject)).toBe(true); +}); + +test('ObjectContaining matches prototype properties', () => { + const prototypeObject = {foo: 'bar'}; + let obj; + + if (Object.create) { + obj = Object.create(prototypeObject); + } else { + function Foo() {} + Foo.prototype = prototypeObject; + Foo.prototype.constructor = Foo; + obj = new Foo(); + } + expect(objectContaining({foo: 'bar'}).asymmetricMatch(obj)).toBe(true); +}); + +test('ObjectContaining throws for non-objects', () => { + jestExpect(() => objectContaining(1337).asymmetricMatch()).toThrow(); +}); + +test('ObjectContaining.toAsymmetricMatcher()', () => { + jestExpect(objectContaining({super: 'trooper'}).toAsymmetricMatcher(print)) + .toBe(''); +}); + +test('StringMatching matches string against regexp', () => { + jestExpect(stringMatching(/en/).asymmetricMatch('queen')).toBe(true); + jestExpect(stringMatching(/en/).asymmetricMatch('queue')).toBe(false); +}); + +test('StringMatching matches string against string', () => { + jestExpect(stringMatching('en').asymmetricMatch('queen')).toBe(true); + jestExpect(stringMatching('en').asymmetricMatch('queue')).toBe(false); +}); + +test('StringMatching throws for non-strings and non-regexps', () => { + jestExpect(() => { + stringMatching([1]).asymmetricMatch('queen'); + }).toThrow(); +}); + +test('StringMatching.toAsymmetricMatcher()', () => { + jestExpect(stringMatching(/(foo|bar)/).toAsymmetricMatcher(print)) + .toBe(''); +}); From 538fb1806f6e77170b04240a0c00a7461836f71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 9 Jan 2017 23:18:54 +0100 Subject: [PATCH 12/14] Update snapshots with patch marks --- .../__tests__/__snapshots__/index-test.js.snap | 17 ++++++++--------- .../__snapshots__/matchers-test.js.snap | 6 ++---- packages/jest-matchers/src/matchers.js | 4 ++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap index fd90b142d2b9..22aabaa1c4ab 100644 --- a/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap +++ b/packages/jest-matcher-utils/src/__tests__/__snapshots__/index-test.js.snap @@ -1,24 +1,23 @@ -exports[`.stringify() reduces maxDepth if stringifying very large objects 1`] = `"{"a": 1, "b": [Object]}"`; +exports[`.stringify() reduces maxDepth if stringifying very large objects 1`] = `"{\"a\": 1, \"b\": [Object]}"`; -exports[`.stringify() reduces maxDepth if stringifying very large objects 2`] = `"{"a": 1, "b": {"0": "test", "1": "test", "2": "test", "3": "test", "4": "test", "5": "test", "6": "test", "7": "test", "8": "test", "9": "test"}}"`; +exports[`.stringify() reduces maxDepth if stringifying very large objects 2`] = `"{\"a\": 1, \"b\": {\"0\": \"test\", \"1\": \"test\", \"2\": \"test\", \"3\": \"test\", \"4\": \"test\", \"5\": \"test\", \"6\": \"test\", \"7\": \"test\", \"8\": \"test\", \"9\": \"test\"}}"`; exports[`.stringify() toJSON errors when comparing two objects 1`] = ` "expect(received).toEqual(expected) Expected value to equal: - {"b": 1, "toJSON": [Function toJSON]} + {\"b\": 1, \"toJSON\": [Function toJSON]} Received: - {"a": 1, "toJSON": [Function toJSON]} + {\"a\": 1, \"toJSON\": [Function toJSON]} Difference: - Expected + Received -@@ -1,4 +1,4 @@ - Object { -- "b": 1, -+ "a": 1, - "toJSON": [Function toJSON], + Object { +- \"b\": 1, ++ \"a\": 1, + \"toJSON\": [Function toJSON],  }" `; diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap index ecb25c28da0b..bf4c045ff877 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap @@ -1795,8 +1795,7 @@ Difference: - Expected + Received -@@ -1,4 +1,4 @@ -- +Object { diff --git a/packages/jest-matchers/src/matchers.js b/packages/jest-matchers/src/matchers.js index 3885b41a7218..253d96dd1fb2 100644 --- a/packages/jest-matchers/src/matchers.js +++ b/packages/jest-matchers/src/matchers.js @@ -118,12 +118,12 @@ const matchers: MatchersObject = { ? () => matcherHint('.not.toBeCloseTo', 'received', 'expected, precision') + '\n\n' + `Expected value not to be close to (with ${printExpected(precision)}-digit precision):\n` + ` ${printExpected(expected)}\n` + - `Received: \n` + + `Received:\n` + ` ${printReceived(actual)}` : () => matcherHint('.toBeCloseTo', 'received', 'expected, precision') + '\n\n' + `Expected value to be close to (with ${printExpected(precision)}-digit precision):\n` + ` ${printExpected(expected)}\n` + - `Received: \n` + + `Received:\n` + ` ${printReceived(actual)}`; return {message, pass}; From 9692e181227726e56dfc1262bbbf4c56373d9845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Thu, 12 Jan 2017 20:55:37 +0100 Subject: [PATCH 13/14] Adjust printing of asymmetric matchers --- .../__snapshots__/matchers-test.js.snap | 54 +++++++------ .../src/__tests__/asymmetric-matchers-test.js | 16 ++-- .../jest-matchers/src/asymmetric-matchers.js | 19 +++-- .../src/__tests__/AsymmetricMatcher-test.js | 77 +++++++++---------- 4 files changed, 81 insertions(+), 85 deletions(-) diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap index bf4c045ff877..832733ed22cb 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap @@ -1742,20 +1742,20 @@ Received: \"abc\"" `; -exports[`.toEqual() expect("abcd").not.toEqual() 1`] = ` +exports[`.toEqual() expect("abcd").not.toEqual(StringMatching /bc/) 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: -  + StringMatching /bc/ Received: \"abcd\"" `; -exports[`.toEqual() expect("abd").toEqual() 1`] = ` +exports[`.toEqual() expect("abd").toEqual(StringMatching /bc/i) 1`] = ` "expect(received).toEqual(expected) Expected value to equal: -  + StringMatching /bc/i Received: \"abd\" @@ -1773,20 +1773,20 @@ Received: \"banana\"" `; -exports[`.toEqual() expect([1, 2, 3]).not.toEqual() 1`] = ` +exports[`.toEqual() expect([1, 2, 3]).not.toEqual(ArrayContaining [2, 3]) 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: -  + ArrayContaining [2, 3] Received: [1, 2, 3]" `; -exports[`.toEqual() expect([1, 3]).toEqual() 1`] = ` +exports[`.toEqual() expect([1, 3]).toEqual(ArrayContaining [1, 2]) 1`] = ` "expect(received).toEqual(expected) Expected value to equal: -  + ArrayContaining [1, 2] Received: [1, 3] @@ -1795,47 +1795,46 @@ Difference: - Expected + Received -- + 3, -+]" + ]" `; -exports[`.toEqual() expect([Function anonymous]).not.toEqual() 1`] = ` +exports[`.toEqual() expect([Function anonymous]).not.toEqual(Any) 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: -  + Any Received: [Function anonymous]" `; -exports[`.toEqual() expect({"a": 1, "b": [Function b], "c": true}).not.toEqual({"a": 1, "b": , "c": }) 1`] = ` +exports[`.toEqual() expect({"a": 1, "b": [Function b], "c": true}).not.toEqual({"a": 1, "b": Any, "c": Anything}) 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: - {\"a\": 1, \"b\": , \"c\": } + {\"a\": 1, \"b\": Any, \"c\": Anything} Received: {\"a\": 1, \"b\": [Function b], \"c\": true}" `; -exports[`.toEqual() expect({"a": 1, "b": 2}).not.toEqual() 1`] = ` +exports[`.toEqual() expect({"a": 1, "b": 2}).not.toEqual(ObjectContaining {"a": 1}) 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: -  + ObjectContaining {\"a\": 1} Received: {\"a\": 1, \"b\": 2}" `; -exports[`.toEqual() expect({"a": 1, "b": 2}).toEqual() 1`] = ` +exports[`.toEqual() expect({"a": 1, "b": 2}).toEqual(ObjectContaining {"a": 2}) 1`] = ` "expect(received).toEqual(expected) Expected value to equal: -  + ObjectContaining {\"a\": 2} Received: {\"a\": 1, \"b\": 2} @@ -1844,13 +1843,12 @@ Difference: - Expected + Received -- +Object { + \"a\": 1, + \"b\": 2, -+}" + }" `; exports[`.toEqual() expect({"a": 5}).toEqual({"b": 6}) 1`] = ` @@ -1912,11 +1910,11 @@ Difference: Comparing two different types of values. Expected undefined but received null." `; -exports[`.toEqual() expect(true).not.toEqual() 1`] = ` +exports[`.toEqual() expect(true).not.toEqual(Anything) 1`] = ` "expect(received).not.toEqual(expected) Expected value to not equal: -  + Anything Received: true" `; @@ -1939,11 +1937,11 @@ Received: true" `; -exports[`.toEqual() expect(undefined).toEqual() 1`] = ` +exports[`.toEqual() expect(undefined).toEqual(Any) 1`] = ` "expect(received).toEqual(expected) Expected value to equal: -  + Any Received: undefined @@ -1952,11 +1950,11 @@ Difference: Comparing two different types of values. Expected object but received undefined." `; -exports[`.toEqual() expect(undefined).toEqual() 1`] = ` +exports[`.toEqual() expect(undefined).toEqual(Anything) 1`] = ` "expect(received).toEqual(expected) Expected value to equal: -  + Anything Received: undefined diff --git a/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js b/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js index c606bfb5e76c..48df9329b682 100644 --- a/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js +++ b/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js @@ -40,11 +40,11 @@ test('Any.asymmetricMatch()', () => { }); test('Any.toAsymmetricMatcher()', () => { - jestExpect(any(Number).toAsymmetricMatcher()).toBe(''); + jestExpect(any(Number).toAsymmetricMatcher()).toBe('Any'); }); test('Any throws when called with empty constructor', () => { - expect(() => any()).toThrow(); + jestExpect(() => any()).toThrow(); }); test('Anything matches any type', () => { @@ -70,7 +70,7 @@ test('Anything does not match null and undefined', () => { }); test('Anything.toAsymmetricMatcher()', () => { - jestExpect(anything().toAsymmetricMatcher()).toBe(''); + jestExpect(anything().toAsymmetricMatcher()).toBe('Anything'); }); test('ArrayContaining matches', () => { @@ -96,7 +96,7 @@ test('ArrayContaining throws for non-arrays', () => { test('ArrayContaining.toAsymmetricMatcher()', () => { jestExpect(arrayContaining(['foo', 'bar']).toAsymmetricMatcher(print)) - .toBe(''); + .toBe('ArrayContaining ["foo", "bar"]'); }); test('ObjectContaining matches', () => { @@ -123,7 +123,7 @@ test('ObjectContaining does not match', () => { test('ObjectContaining matches defined properties', () => { const definedPropertyObject = {}; Object.defineProperty(definedPropertyObject, 'foo', {get: () => 'bar'}); - expect(objectContaining({foo: 'bar'}).asymmetricMatch(definedPropertyObject)).toBe(true); + jestExpect(objectContaining({foo: 'bar'}).asymmetricMatch(definedPropertyObject)).toBe(true); }); test('ObjectContaining matches prototype properties', () => { @@ -138,7 +138,7 @@ test('ObjectContaining matches prototype properties', () => { Foo.prototype.constructor = Foo; obj = new Foo(); } - expect(objectContaining({foo: 'bar'}).asymmetricMatch(obj)).toBe(true); + jestExpect(objectContaining({foo: 'bar'}).asymmetricMatch(obj)).toBe(true); }); test('ObjectContaining throws for non-objects', () => { @@ -147,7 +147,7 @@ test('ObjectContaining throws for non-objects', () => { test('ObjectContaining.toAsymmetricMatcher()', () => { jestExpect(objectContaining({super: 'trooper'}).toAsymmetricMatcher(print)) - .toBe(''); + .toBe('ObjectContaining {"super": "trooper"}'); }); test('StringMatching matches string against regexp', () => { @@ -168,5 +168,5 @@ test('StringMatching throws for non-strings and non-regexps', () => { test('StringMatching.toAsymmetricMatcher()', () => { jestExpect(stringMatching(/(foo|bar)/).toAsymmetricMatcher(print)) - .toBe(''); + .toBe('StringMatching /(foo|bar)/'); }); diff --git a/packages/jest-matchers/src/asymmetric-matchers.js b/packages/jest-matchers/src/asymmetric-matchers.js index d8087dba7f2f..84d3e25ecd51 100644 --- a/packages/jest-matchers/src/asymmetric-matchers.js +++ b/packages/jest-matchers/src/asymmetric-matchers.js @@ -70,7 +70,7 @@ class Any extends AsymmetricMatcher { } toAsymmetricMatcher() { - return ''; + return 'Any<' + fnNameFor(this.sample) + '>'; } } @@ -84,7 +84,7 @@ class Anything extends AsymmetricMatcher { } toAsymmetricMatcher() { - return ''; + return 'Anything'; } } @@ -97,10 +97,9 @@ class ArrayContaining extends AsymmetricMatcher { } asymmetricMatch(other: Array) { - const className = Object.prototype.toString.call(this.sample); - if (className !== '[object Array]') { + if (!Array.isArray(this.sample)) { throw new Error( - 'You must provide an array to arrayContaining, not \'' + + 'You must provide an array to ArrayContaining, not \'' + typeof this.sample + '\'.' ); } @@ -120,9 +119,9 @@ class ArrayContaining extends AsymmetricMatcher { } toAsymmetricMatcher(print: Function) { - return ''; + return 'ArrayContaining ' + print(this.sample).replace(/Array\s+/, ''); } - } +} class ObjectContaining extends AsymmetricMatcher { sample: Object; @@ -135,7 +134,7 @@ class ObjectContaining extends AsymmetricMatcher { asymmetricMatch(other: Object) { if (typeof this.sample !== 'object') { throw new Error( - 'You must provide an object to objectContaining, not \'' + + 'You must provide an object to ObjectContaining, not \'' + typeof this.sample + '\'.' ); } @@ -157,7 +156,7 @@ class ObjectContaining extends AsymmetricMatcher { } toAsymmetricMatcher(print: Function) { - return ''; + return 'ObjectContaining ' + print(this.sample).replace(/Object\s+/, ''); } } @@ -182,7 +181,7 @@ class StringMatching extends AsymmetricMatcher { } toAsymmetricMatcher(print: Function) { - return ''; + return 'StringMatching ' + print(this.sample).replace(/Object/, ''); } } diff --git a/packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js b/packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js index 1d3f0b532327..41a0700c2ac3 100644 --- a/packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js +++ b/packages/pretty-format/src/__tests__/AsymmetricMatcher-test.js @@ -12,7 +12,6 @@ const prettyFormat = require('../'); const AsymmetricMatcher = require('../plugins/AsymmetricMatcher'); -const jestExpect = require('jest-matchers'); let options; function fnNameFor(func) { @@ -40,80 +39,80 @@ beforeEach(() => { function namedFuntction() {}, ].forEach(type => { test(`supports any(${fnNameFor(type)})`, () => { - const result = prettyFormat(jestExpect.any(type), options); - expect(result).toEqual(``); + const result = prettyFormat(expect.any(type), options); + expect(result).toEqual(`Any<${fnNameFor(type)}>`); }); test(`supports nested any(${fnNameFor(type)})`, () => { const result = prettyFormat({ test: { - nested: jestExpect.any(type), + nested: expect.any(type), }, }, options); - expect(result).toEqual(`Object {\n "test": Object {\n "nested": ,\n },\n}`); + expect(result).toEqual(`Object {\n "test": Object {\n "nested": Any<${fnNameFor(type)}>,\n },\n}`); }); }); test(`anything()`, () => { - const result = prettyFormat(jestExpect.anything(), options); - expect(result).toEqual(''); + const result = prettyFormat(expect.anything(), options); + expect(result).toEqual('Anything'); }); test(`arrayContaining()`, () => { - const result = prettyFormat(jestExpect.arrayContaining([1, 2]), options); + const result = prettyFormat(expect.arrayContaining([1, 2]), options); expect(result).toEqual( -`` +]` ); }); test(`objectContaining()`, () => { - const result = prettyFormat(jestExpect.objectContaining({a: 'test'}), options); + const result = prettyFormat(expect.objectContaining({a: 'test'}), options); expect(result).toEqual( -`` +}` ); }); test(`stringMatching(string)`, () => { - const result = prettyFormat(jestExpect.stringMatching('jest'), options); - expect(result).toEqual(''); + const result = prettyFormat(expect.stringMatching('jest'), options); + expect(result).toEqual('StringMatching /jest/'); }); test(`stringMatching(regexp)`, () => { - const result = prettyFormat(jestExpect.stringMatching(/(jest|niema).*/), options); - expect(result).toEqual(''); + const result = prettyFormat(expect.stringMatching(/(jest|niema).*/), options); + expect(result).toEqual('StringMatching /(jest|niema).*/'); }); test(`supports multiple nested asymmetric matchers`, () => { const result = prettyFormat({ test: { - nested: jestExpect.objectContaining({ - a: jestExpect.arrayContaining([1]), - b: jestExpect.anything(), - c: jestExpect.any(String), - d: jestExpect.stringMatching('jest'), - e: jestExpect.objectContaining({test: 'case'}), + nested: expect.objectContaining({ + a: expect.arrayContaining([1]), + b: expect.anything(), + c: expect.any(String), + d: expect.stringMatching('jest'), + e: expect.objectContaining({test: 'case'}), }), }, }, options); expect(result).toEqual( `Object { "test": Object { - "nested": , - "b": , - "c": , - "d": , - "e": , + "d": StringMatching /jest/, + "e": ObjectContaining { "test": "case", - })>, - })>, + }, + }, }, }` ); @@ -123,16 +122,16 @@ test(`supports minified output`, () => { options.min = true; const result = prettyFormat({ test: { - nested: jestExpect.objectContaining({ - a: jestExpect.arrayContaining([1]), - b: jestExpect.anything(), - c: jestExpect.any(String), - d: jestExpect.stringMatching('jest'), - e: jestExpect.objectContaining({test: 'case'}), + nested: expect.objectContaining({ + a: expect.arrayContaining([1]), + b: expect.anything(), + c: expect.any(String), + d: expect.stringMatching('jest'), + e: expect.objectContaining({test: 'case'}), }), }, }, options); expect(result).toEqual( -`{"test": {"nested": , "b": , "c": , "d": , "e": })>}}` +`{"test": {"nested": ObjectContaining {"a": ArrayContaining [1], "b": Anything, "c": Any, "d": StringMatching /jest/, "e": ObjectContaining {"test": "case"}}}}` ); }); From fd7a7ecb73359b119b9b5c55d4868cc4ed914934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 15 Jan 2017 18:23:37 +0100 Subject: [PATCH 14/14] Use class instances instead of regex to render values --- .../src/__tests__/asymmetric-matchers-test.js | 15 ------- .../jest-matchers/src/asymmetric-matchers.js | 12 ------ .../src/plugins/AsymmetricMatcher.js | 42 +++++++++++++++---- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js b/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js index 48df9329b682..e41c259c8447 100644 --- a/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js +++ b/packages/jest-matchers/src/__tests__/asymmetric-matchers-test.js @@ -94,11 +94,6 @@ test('ArrayContaining throws for non-arrays', () => { }).toThrow(); }); -test('ArrayContaining.toAsymmetricMatcher()', () => { - jestExpect(arrayContaining(['foo', 'bar']).toAsymmetricMatcher(print)) - .toBe('ArrayContaining ["foo", "bar"]'); -}); - test('ObjectContaining matches', () => { [ objectContaining({}).asymmetricMatch('jest'), @@ -145,11 +140,6 @@ test('ObjectContaining throws for non-objects', () => { jestExpect(() => objectContaining(1337).asymmetricMatch()).toThrow(); }); -test('ObjectContaining.toAsymmetricMatcher()', () => { - jestExpect(objectContaining({super: 'trooper'}).toAsymmetricMatcher(print)) - .toBe('ObjectContaining {"super": "trooper"}'); -}); - test('StringMatching matches string against regexp', () => { jestExpect(stringMatching(/en/).asymmetricMatch('queen')).toBe(true); jestExpect(stringMatching(/en/).asymmetricMatch('queue')).toBe(false); @@ -165,8 +155,3 @@ test('StringMatching throws for non-strings and non-regexps', () => { stringMatching([1]).asymmetricMatch('queen'); }).toThrow(); }); - -test('StringMatching.toAsymmetricMatcher()', () => { - jestExpect(stringMatching(/(foo|bar)/).toAsymmetricMatcher(print)) - .toBe('StringMatching /(foo|bar)/'); -}); diff --git a/packages/jest-matchers/src/asymmetric-matchers.js b/packages/jest-matchers/src/asymmetric-matchers.js index 84d3e25ecd51..29ef881f8864 100644 --- a/packages/jest-matchers/src/asymmetric-matchers.js +++ b/packages/jest-matchers/src/asymmetric-matchers.js @@ -117,10 +117,6 @@ class ArrayContaining extends AsymmetricMatcher { toString() { return 'ArrayContaining'; } - - toAsymmetricMatcher(print: Function) { - return 'ArrayContaining ' + print(this.sample).replace(/Array\s+/, ''); - } } class ObjectContaining extends AsymmetricMatcher { @@ -154,10 +150,6 @@ class ObjectContaining extends AsymmetricMatcher { toString() { return 'ObjectContaining'; } - - toAsymmetricMatcher(print: Function) { - return 'ObjectContaining ' + print(this.sample).replace(/Object\s+/, ''); - } } class StringMatching extends AsymmetricMatcher { @@ -179,10 +171,6 @@ class StringMatching extends AsymmetricMatcher { toString() { return 'StringMatching'; } - - toAsymmetricMatcher(print: Function) { - return 'StringMatching ' + print(this.sample).replace(/Object/, ''); - } } module.exports = { diff --git a/packages/pretty-format/src/plugins/AsymmetricMatcher.js b/packages/pretty-format/src/plugins/AsymmetricMatcher.js index fb9a330af69d..0434db196ccf 100644 --- a/packages/pretty-format/src/plugins/AsymmetricMatcher.js +++ b/packages/pretty-format/src/plugins/AsymmetricMatcher.js @@ -10,14 +10,42 @@ 'use strict'; const asymmetricMatcher = Symbol.for('jest.asymmetricMatcher'); +const SPACE = ' '; + +class ArrayContaining extends Array {} +class ObjectContaining extends Object {} + +const printAsymmetricMatcher = ( + val: any, + print: Function, + indent: Function, + opts: Object, + colors: Object +) => { + const stringedValue = val.toString(); + + if (stringedValue === 'ArrayContaining') { + const array = ArrayContaining.from(val.sample); + return opts.spacing === SPACE + ? stringedValue + SPACE + print(array) + : print(array); + } + + if (stringedValue === 'ObjectContaining') { + const object = Object.assign(new ObjectContaining(), val.sample); + return opts.spacing === SPACE + ? stringedValue + SPACE + print(object) + : print(object); + } + + if (stringedValue === 'StringMatching') { + return stringedValue + SPACE + print(val.sample); + } + + return val.toAsymmetricMatcher(); +}; module.exports = { - print: ( - val: any, - print: Function, - indent: Function, - opts: Object, - colors: Object - ) => val.toAsymmetricMatcher(print), + print: printAsymmetricMatcher, test: (object: any) => object && object.$$typeof === asymmetricMatcher, };