diff --git a/CHANGELOG.md b/CHANGELOG.md index 874df7600df7..eac3031f0ae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - `[expect]` Improve report when matcher fails, part 16 ([#8306](https://github.com/facebook/jest/pull/8306)) - `[jest-runner]` Pass docblock pragmas to TestEnvironment constructor ([#8320](https://github.com/facebook/jest/pull/8320)) - `[docs]` Add DynamoDB guide ([#8319](https://github.com/facebook/jest/pull/8319)) +- `[expect]` Improve report when matcher fails, part 17 ([#8349](https://github.com/facebook/jest/pull/8349)) ### Fixes diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap index e74042085cc7..0db6e14b79d4 100644 --- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap @@ -2841,7 +2841,7 @@ Expected has value: -3" `; exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('1') 1`] = ` -"expect(received).toHaveProperty(path) +"expect(received).toHaveProperty(path) Matcher error: expected path must be a string or array @@ -2850,7 +2850,7 @@ Expected has value: 1" `; exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('null') 1`] = ` -"expect(received).toHaveProperty(path) +"expect(received).toHaveProperty(path) Matcher error: expected path must be a string or array @@ -2858,17 +2858,24 @@ Expected has value: null" `; exports[`.toHaveProperty() {error} expect({"a": {"b": {}}}).toHaveProperty('undefined') 1`] = ` -"expect(received).toHaveProperty(path) +"expect(received).toHaveProperty(path) Matcher error: expected path must be a string or array Expected has value: undefined" `; -exports[`.toHaveProperty() {error} expect({}).toHaveProperty('') 1`] = `"pass must be initialized"`; +exports[`.toHaveProperty() {error} expect({}).toHaveProperty('') 1`] = ` +"expect(received).toHaveProperty(path) + +Matcher error: expected path must not be an empty array + +Expected has type: array +Expected has value: []" +`; exports[`.toHaveProperty() {error} expect(null).toHaveProperty('a.b') 1`] = ` -"expect(received).toHaveProperty(path) +"expect(received).toHaveProperty(path) Matcher error: received value must not be null nor undefined @@ -2876,7 +2883,7 @@ Received has value: null" `; exports[`.toHaveProperty() {error} expect(undefined).toHaveProperty('a') 1`] = ` -"expect(received).toHaveProperty(path) +"expect(received).toHaveProperty(path) Matcher error: received value must not be null nor undefined @@ -2884,116 +2891,87 @@ Received has value: undefined" `; exports[`.toHaveProperty() {pass: false} expect("").toHaveProperty('key') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - \\"\\" -To have a nested property: - \\"key\\" -" +Expected path: \\"key\\" +Received path: [] + +Received value: \\"\\"" `; exports[`.toHaveProperty() {pass: false} expect("abc").toHaveProperty('a.b.c') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - \\"abc\\" -To have a nested property: - \\"a.b.c\\" -" +Expected path: \\"a.b.c\\" +Received path: [] + +Received value: \\"abc\\"" `; exports[`.toHaveProperty() {pass: false} expect("abc").toHaveProperty('a.b.c', {"a": 5}) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - \\"abc\\" -To have a nested property: - \\"a.b.c\\" -With a value of: - {\\"a\\": 5} -" +"expect(received).toHaveProperty(path, value) + +Expected path: \\"a.b.c\\" +Received path: [] + +Expected value: {\\"a\\": 5} +Received value: \\"abc\\"" `; exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a,b,c,d', 2) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}} -To have a nested property: - [\\"a\\", \\"b\\", \\"c\\", \\"d\\"] -With a value of: - 2 -Received: - 1" +"expect(received).toHaveProperty(path, value) + +Expected path: [\\"a\\", \\"b\\", \\"c\\", \\"d\\"] + +Expected value: 2 +Received value: 1" `; exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a.b.c.d', 2) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}} -To have a nested property: - \\"a.b.c.d\\" -With a value of: - 2 -Received: - 1" +"expect(received).toHaveProperty(path, value) + +Expected path: \\"a.b.c.d\\" + +Expected value: 2 +Received value: 1" `; exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a.b.ttt.d', 1) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}} -To have a nested property: - \\"a.b.ttt.d\\" -With a value of: - 1 -Received: - object.a.b: {\\"c\\": {\\"d\\": 1}}" +"expect(received).toHaveProperty(path, value) + +Expected path: \\"a.b.ttt.d\\" +Received path: \\"a.b\\" + +Expected value: 1 +Received value: {\\"c\\": {\\"d\\": 1}}" `; exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {}}}}).toHaveProperty('a.b.c.d') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {}}}} -To have a nested property: - \\"a.b.c.d\\" -Received: - object.a.b.c: {}" +Expected path: \\"a.b.c.d\\" +Received path: \\"a.b.c\\" + +Received value: {}" `; exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": {}}}}).toHaveProperty('a.b.c.d', 1) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {}}}} -To have a nested property: - \\"a.b.c.d\\" -With a value of: - 1 -Received: - object.a.b.c: {}" +"expect(received).toHaveProperty(path, value) + +Expected path: \\"a.b.c.d\\" +Received path: \\"a.b.c\\" + +Expected value: 1 +Received value: {}" `; exports[`.toHaveProperty() {pass: false} expect({"a": {"b": {"c": 5}}}).toHaveProperty('a.b', {"c": 4}) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": 5}}} -To have a nested property: - \\"a.b\\" -With a value of: - {\\"c\\": 4} -Received: - {\\"c\\": 5} +"expect(received).toHaveProperty(path, value) -Difference: +Expected path: \\"a.b\\" -- Expected -+ Received +- Expected value ++ Received value Object { - \\"c\\": 4, @@ -3002,451 +2980,347 @@ Difference: `; exports[`.toHaveProperty() {pass: false} expect({"a": {"b": 3}}).toHaveProperty('a.b', undefined) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": 3}} -To have a nested property: - \\"a.b\\" -With a value of: - undefined -Received: - 3 +"expect(received).toHaveProperty(path, value) -Difference: +Expected path: \\"a.b\\" - Comparing two different types of values. Expected undefined but received number." +Expected value: undefined +Received value: 3" `; exports[`.toHaveProperty() {pass: false} expect({"a": 1}).toHaveProperty('a.b.c.d') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - {\\"a\\": 1} -To have a nested property: - \\"a.b.c.d\\" -Received: - object.a: 1" +Expected path: \\"a.b.c.d\\" +Received path: \\"a\\" + +Received value: 1" `; exports[`.toHaveProperty() {pass: false} expect({"a": 1}).toHaveProperty('a.b.c.d', 5) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a\\": 1} -To have a nested property: - \\"a.b.c.d\\" -With a value of: - 5 -Received: - object.a: 1" +"expect(received).toHaveProperty(path, value) + +Expected path: \\"a.b.c.d\\" +Received path: \\"a\\" + +Expected value: 5 +Received value: 1" `; exports[`.toHaveProperty() {pass: false} expect({"a.b.c.d": 1}).toHaveProperty('a.b.c.d', 2) 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a.b.c.d\\": 1} -To have a nested property: - \\"a.b.c.d\\" -With a value of: - 2 -" +"expect(received).toHaveProperty(path, value) + +Expected path: \\"a.b.c.d\\" +Received path: [] + +Expected value: 2 +Received value: {\\"a.b.c.d\\": 1}" `; exports[`.toHaveProperty() {pass: false} expect({"a.b.c.d": 1}).toHaveProperty('a.b.c.d', 2) 2`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {\\"a.b.c.d\\": 1} -To have a nested property: - [\\"a.b.c.d\\"] -With a value of: - 2 -Received: - 1" +"expect(received).toHaveProperty(path, value) + +Expected path: [\\"a.b.c.d\\"] + +Expected value: 2 +Received value: 1" `; exports[`.toHaveProperty() {pass: false} expect({"key": 1}).toHaveProperty('not') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - {\\"key\\": 1} -To have a nested property: - \\"not\\" -" +Expected path: \\"not\\" +Received path: [] + +Received value: {\\"key\\": 1}" `; exports[`.toHaveProperty() {pass: false} expect({}).toHaveProperty('a') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - {} -To have a nested property: - \\"a\\" -" +Expected path: \\"a\\" +Received path: [] + +Received value: {}" `; exports[`.toHaveProperty() {pass: false} expect({}).toHaveProperty('a', "a") 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - {} -To have a nested property: - \\"a\\" -With a value of: - \\"a\\" -Received: - undefined +"expect(received).toHaveProperty(path, value) -Difference: +Expected path: \\"a\\" - Comparing two different types of values. Expected string but received undefined." +Expected value: \\"a\\" +Received value: undefined" `; exports[`.toHaveProperty() {pass: false} expect({}).toHaveProperty('a', "test") 1`] = ` -"expect(object).toHaveProperty(path, value) +"expect(received).toHaveProperty(path, value) -Expected the object: - {} -To have a nested property: - \\"a\\" -With a value of: - \\"test\\" -" +Expected path: \\"a\\" +Received path: [] + +Expected value: \\"test\\" +Received value: {}" `; exports[`.toHaveProperty() {pass: false} expect({}).toHaveProperty('b', undefined) 1`] = ` -"expect(object).toHaveProperty(path, value) +"expect(received).toHaveProperty(path, value) -Expected the object: - {} -To have a nested property: - \\"b\\" -With a value of: - undefined -Received: - \\"b\\" - -Difference: +Expected path: \\"b\\" - Comparing two different types of values. Expected undefined but received string." +Expected value: undefined +Received value: \\"b\\"" `; exports[`.toHaveProperty() {pass: false} expect(0).toHaveProperty('key') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - 0 -To have a nested property: - \\"key\\" -" +Expected path: \\"key\\" +Received path: [] + +Received value: 0" `; exports[`.toHaveProperty() {pass: false} expect(1).toHaveProperty('a.b.c') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - 1 -To have a nested property: - \\"a.b.c\\" -" +Expected path: \\"a.b.c\\" +Received path: [] + +Received value: 1" `; exports[`.toHaveProperty() {pass: false} expect(1).toHaveProperty('a.b.c', "test") 1`] = ` -"expect(object).toHaveProperty(path, value) - -Expected the object: - 1 -To have a nested property: - \\"a.b.c\\" -With a value of: - \\"test\\" -" +"expect(received).toHaveProperty(path, value) + +Expected path: \\"a.b.c\\" +Received path: [] + +Expected value: \\"test\\" +Received value: 1" `; exports[`.toHaveProperty() {pass: false} expect(Symbol()).toHaveProperty('key') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - Symbol() -To have a nested property: - \\"key\\" -" +Expected path: \\"key\\" +Received path: [] + +Received value: Symbol()" `; exports[`.toHaveProperty() {pass: false} expect(false).toHaveProperty('key') 1`] = ` -"expect(object).toHaveProperty(path) +"expect(received).toHaveProperty(path) -Expected the object: - false -To have a nested property: - \\"key\\" -" +Expected path: \\"key\\" +Received path: [] + +Received value: false" `; exports[`.toHaveProperty() {pass: true} expect("").toHaveProperty('length', 0) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - \\"\\" -Not to have a nested property: - \\"length\\" -With a value of: - 0 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"length\\" + +Expected value: not 0" `; exports[`.toHaveProperty() {pass: true} expect([Function memoized]).toHaveProperty('memo', []) 1`] = ` -"expect(object).not.toHaveProperty(path, value) +"expect(received).not.toHaveProperty(path, value) -Expected the object: - [Function memoized] -Not to have a nested property: - \\"memo\\" -With a value of: - [] -" +Expected path: \\"memo\\" + +Expected value: not []" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [1, 2, 3]}}).toHaveProperty('a,b,1') 1`] = ` -"expect(object).not.toHaveProperty(path) +"expect(received).not.toHaveProperty(path) -Expected the object: - {\\"a\\": {\\"b\\": [1, 2, 3]}} -Not to have a nested property: - [\\"a\\", \\"b\\", 1] -" +Expected path: not [\\"a\\", \\"b\\", 1] + +Received value: 2" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [1, 2, 3]}}).toHaveProperty('a,b,1', 2) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": [1, 2, 3]}} -Not to have a nested property: - [\\"a\\", \\"b\\", 1] -With a value of: - 2 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: [\\"a\\", \\"b\\", 1] + +Expected value: not 2" +`; + +exports[`.toHaveProperty() {pass: true} expect({"a": {"b": [1, 2, 3]}}).toHaveProperty('a,b,1', Any) 1`] = ` +"expect(received).not.toHaveProperty(path, value) + +Expected path: [\\"a\\", \\"b\\", 1] + +Expected value: not Any +Received value: 2" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a,b,c,d') 1`] = ` -"expect(object).not.toHaveProperty(path) +"expect(received).not.toHaveProperty(path) -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}} -Not to have a nested property: - [\\"a\\", \\"b\\", \\"c\\", \\"d\\"] -" +Expected path: not [\\"a\\", \\"b\\", \\"c\\", \\"d\\"] + +Received value: 1" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a,b,c,d', 1) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}} -Not to have a nested property: - [\\"a\\", \\"b\\", \\"c\\", \\"d\\"] -With a value of: - 1 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: [\\"a\\", \\"b\\", \\"c\\", \\"d\\"] + +Expected value: not 1" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a.b.c.d') 1`] = ` -"expect(object).not.toHaveProperty(path) +"expect(received).not.toHaveProperty(path) -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}} -Not to have a nested property: - \\"a.b.c.d\\" -" +Expected path: not \\"a.b.c.d\\" + +Received value: 1" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": {"c": {"d": 1}}}}).toHaveProperty('a.b.c.d', 1) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": {\\"d\\": 1}}}} -Not to have a nested property: - \\"a.b.c.d\\" -With a value of: - 1 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"a.b.c.d\\" + +Expected value: not 1" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": {"c": 5}}}).toHaveProperty('a.b', {"c": 5}) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": {\\"c\\": 5}}} -Not to have a nested property: - \\"a.b\\" -With a value of: - {\\"c\\": 5} -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"a.b\\" + +Expected value: not {\\"c\\": 5}" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": undefined}}).toHaveProperty('a.b') 1`] = ` -"expect(object).not.toHaveProperty(path) +"expect(received).not.toHaveProperty(path) -Expected the object: - {\\"a\\": {\\"b\\": undefined}} -Not to have a nested property: - \\"a.b\\" -" +Expected path: not \\"a.b\\" + +Received value: undefined" `; exports[`.toHaveProperty() {pass: true} expect({"a": {"b": undefined}}).toHaveProperty('a.b', undefined) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"a\\": {\\"b\\": undefined}} -Not to have a nested property: - \\"a.b\\" -With a value of: - undefined -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"a.b\\" + +Expected value: not undefined" +`; + +exports[`.toHaveProperty() {pass: true} expect({"a": {}}).toHaveProperty('a.b', undefined) 1`] = ` +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"a.b\\" +Received path: \\"a\\" + +Expected value: not undefined +Received value: {} + +Because a positive assertion passes for expected value undefined if the property does not exist, this negative assertion fails unless the property does exist and has a defined value" `; exports[`.toHaveProperty() {pass: true} expect({"a": 0}).toHaveProperty('a') 1`] = ` -"expect(object).not.toHaveProperty(path) +"expect(received).not.toHaveProperty(path) -Expected the object: - {\\"a\\": 0} -Not to have a nested property: - \\"a\\" -" +Expected path: not \\"a\\" + +Received value: 0" `; exports[`.toHaveProperty() {pass: true} expect({"a": 0}).toHaveProperty('a', 0) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"a\\": 0} -Not to have a nested property: - \\"a\\" -With a value of: - 0 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"a\\" + +Expected value: not 0" `; exports[`.toHaveProperty() {pass: true} expect({"a.b.c.d": 1}).toHaveProperty('a.b.c.d') 1`] = ` -"expect(object).not.toHaveProperty(path) +"expect(received).not.toHaveProperty(path) -Expected the object: - {\\"a.b.c.d\\": 1} -Not to have a nested property: - [\\"a.b.c.d\\"] -" +Expected path: not [\\"a.b.c.d\\"] + +Received value: 1" `; exports[`.toHaveProperty() {pass: true} expect({"a.b.c.d": 1}).toHaveProperty('a.b.c.d', 1) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"a.b.c.d\\": 1} -Not to have a nested property: - [\\"a.b.c.d\\"] -With a value of: - 1 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: [\\"a.b.c.d\\"] + +Expected value: not 1" `; exports[`.toHaveProperty() {pass: true} expect({"nodeName": "DIV"}).toHaveProperty('nodeType', 1) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"nodeName\\": \\"DIV\\"} -Not to have a nested property: - \\"nodeType\\" -With a value of: - 1 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"nodeType\\" + +Expected value: not 1" `; exports[`.toHaveProperty() {pass: true} expect({"property": 1}).toHaveProperty('property', 1) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"property\\": 1} -Not to have a nested property: - \\"property\\" -With a value of: - 1 -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"property\\" + +Expected value: not 1" `; exports[`.toHaveProperty() {pass: true} expect({"val": true}).toHaveProperty('a', undefined) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"val\\": true} -Not to have a nested property: - \\"a\\" -With a value of: - undefined -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"a\\" + +Expected value: not undefined" `; exports[`.toHaveProperty() {pass: true} expect({"val": true}).toHaveProperty('c', "c") 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"val\\": true} -Not to have a nested property: - \\"c\\" -With a value of: - \\"c\\" -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"c\\" + +Expected value: not \\"c\\"" `; exports[`.toHaveProperty() {pass: true} expect({"val": true}).toHaveProperty('val', true) 1`] = ` -"expect(object).not.toHaveProperty(path, value) - -Expected the object: - {\\"val\\": true} -Not to have a nested property: - \\"val\\" -With a value of: - true -" +"expect(received).not.toHaveProperty(path, value) + +Expected path: \\"val\\" + +Expected value: not true" `; exports[`.toHaveProperty() {pass: true} expect({}).toHaveProperty('a', undefined) 1`] = ` -"expect(object).not.toHaveProperty(path, value) +"expect(received).not.toHaveProperty(path, value) -Expected the object: - {} -Not to have a nested property: - \\"a\\" -With a value of: - undefined -" +Expected path: \\"a\\" + +Expected value: not undefined" `; exports[`.toHaveProperty() {pass: true} expect({}).toHaveProperty('b', "b") 1`] = ` -"expect(object).not.toHaveProperty(path, value) +"expect(received).not.toHaveProperty(path, value) -Expected the object: - {} -Not to have a nested property: - \\"b\\" -With a value of: - \\"b\\" -" +Expected path: \\"b\\" + +Expected value: not \\"b\\"" `; exports[`.toHaveProperty() {pass: true} expect({}).toHaveProperty('setter', undefined) 1`] = ` -"expect(object).not.toHaveProperty(path, value) +"expect(received).not.toHaveProperty(path, value) -Expected the object: - {} -Not to have a nested property: - \\"setter\\" -With a value of: - undefined -" +Expected path: \\"setter\\" + +Expected value: not undefined" `; exports[`.toMatch() {pass: true} expect(Foo bar).toMatch(/^foo/i) 1`] = ` diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index 8c82661a6987..ad143ffa0437 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -1340,8 +1340,10 @@ describe('.toHaveProperty()', () => { [{a: {b: {c: {d: 1}}}}, ['a', 'b', 'c', 'd'], 1], [{'a.b.c.d': 1}, ['a.b.c.d'], 1], [{a: {b: [1, 2, 3]}}, ['a', 'b', 1], 2], + [{a: {b: [1, 2, 3]}}, ['a', 'b', 1], expect.any(Number)], [{a: 0}, 'a', 0], [{a: {b: undefined}}, 'a.b', undefined], + [{a: {}}, 'a.b', undefined], // delete for breaking change in future major [{a: {b: {c: 5}}}, 'a.b', {c: 5}], [Object.assign(Object.create(null), {property: 1}), 'property', 1], [new Foo(), 'a', undefined], @@ -1379,7 +1381,7 @@ describe('.toHaveProperty()', () => { [{a: {b: {c: 5}}}, 'a.b', {c: 4}], [new Foo(), 'a', 'a'], [new Foo(), 'b', undefined], - // [{a: {}}, 'a.b', undefined], // wait until Jest 25 + // [{a: {}}, 'a.b', undefined], // add for breaking change in future major ].forEach(([obj, keyPath, value]) => { test(`{pass: false} expect(${stringify( obj, diff --git a/packages/expect/src/matchers.ts b/packages/expect/src/matchers.ts index a26fe2a7ebbe..6f31c11f8528 100644 --- a/packages/expect/src/matchers.ts +++ b/packages/expect/src/matchers.ts @@ -42,9 +42,11 @@ import { } from './utils'; import {equals} from './jasmineUtils'; -// Include colon and one or more spaces, same as returned by getLabelPrinter. -const EXPECTED_LABEL = 'Expected: '; -const RECEIVED_LABEL = 'Received: '; +// Omit colon and one or more spaces, so can call getLabelPrinter. +const EXPECTED_LABEL = 'Expected'; +const RECEIVED_LABEL = 'Received'; +const EXPECTED_VALUE_LABEL = 'Expected value'; +const RECEIVED_VALUE_LABEL = 'Received value'; const toStrictEqualTesters = [ iterableEquality, @@ -652,87 +654,123 @@ const matchers: MatchersObject = { toHaveProperty( this: MatcherState, - object: object, - keyPath: string | Array, - value?: unknown, + received: object, + expectedPath: string | Array, + expectedValue?: unknown, ) { - const matcherName = '.toHaveProperty'; - const valuePassed = arguments.length === 3; - const secondArgument = valuePassed ? 'value' : ''; + const matcherName = 'toHaveProperty'; + const expectedArgument = 'path'; + const hasValue = arguments.length === 3; const options: MatcherHintOptions = { isNot: this.isNot, - secondArgument, + promise: this.promise, + secondArgument: hasValue ? 'value' : '', }; - if (object === null || object === undefined) { + if (received === null || received === undefined) { throw new Error( matcherErrorMessage( - matcherHint(matcherName, undefined, 'path', options), + matcherHint(matcherName, undefined, expectedArgument, options), `${RECEIVED_COLOR('received')} value must not be null nor undefined`, - printWithType('Received', object, printReceived), + printWithType('Received', received, printReceived), ), ); } - const keyPathType = getType(keyPath); + const expectedPathType = getType(expectedPath); - if (keyPathType !== 'string' && keyPathType !== 'array') { + if (expectedPathType !== 'string' && expectedPathType !== 'array') { throw new Error( matcherErrorMessage( - matcherHint(matcherName, undefined, 'path', options), + matcherHint(matcherName, undefined, expectedArgument, options), `${EXPECTED_COLOR('expected')} path must be a string or array`, - printWithType('Expected', keyPath, printExpected), + printWithType('Expected', expectedPath, printExpected), ), ); } - const result = getPath(object, keyPath); - const {lastTraversedObject, hasEndProp} = result; + const expectedPathLength = + typeof expectedPath === 'string' + ? expectedPath.split('.').length + : expectedPath.length; + + if (expectedPathType === 'array' && expectedPathLength === 0) { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, expectedArgument, options), + `${EXPECTED_COLOR('expected')} path must not be an empty array`, + printWithType('Expected', expectedPath, printExpected), + ), + ); + } - const pass = valuePassed - ? equals(result.value, value, [iterableEquality]) - : hasEndProp; + const result = getPath(received, expectedPath); + const {lastTraversedObject, hasEndProp} = result; + const receivedPath = result.traversedPath; + const hasCompletePath = receivedPath.length === expectedPathLength; + const receivedValue = hasCompletePath ? result.value : lastTraversedObject; + + const pass = hasValue + ? equals(result.value, expectedValue, [iterableEquality]) + : Boolean(hasEndProp); // theoretically undefined if empty path + // Remove type cast if we rewrite getPath as iterative algorithm. + + // Delete this unique report if future breaking change + // removes the edge case that expected value undefined + // also matches absence of a property with the key path. + if (pass && !hasCompletePath) { + const message = () => + matcherHint(matcherName, undefined, expectedArgument, options) + + '\n\n' + + `Expected path: ${printExpected(expectedPath)}\n` + + `Received path: ${printReceived( + expectedPathType === 'array' || receivedPath.length === 0 + ? receivedPath + : receivedPath.join('.'), + )}\n\n` + + `Expected value: not ${printExpected(expectedValue)}\n` + + `Received value: ${printReceived(receivedValue)}\n\n` + + DIM_COLOR( + 'Because a positive assertion passes for expected value undefined if the property does not exist, this negative assertion fails unless the property does exist and has a defined value', + ); - const traversedPath = result.traversedPath.join('.'); + return {message, pass}; + } const message = pass ? () => - matcherHint(matcherName, 'object', 'path', options) + + matcherHint(matcherName, undefined, expectedArgument, options) + '\n\n' + - `Expected the object:\n` + - ` ${printReceived(object)}\n` + - `Not to have a nested property:\n` + - ` ${printExpected(keyPath)}\n` + - (valuePassed ? `With a value of:\n ${printExpected(value)}\n` : '') - : () => { - const difference = - valuePassed && hasEndProp - ? diff(value, result.value, {expand: this.expand}) - : ''; - return ( - matcherHint(matcherName, 'object', 'path', options) + - '\n\n' + - `Expected the object:\n` + - ` ${printReceived(object)}\n` + - `To have a nested property:\n` + - ` ${printExpected(keyPath)}\n` + - (valuePassed - ? `With a value of:\n ${printExpected(value)}\n` - : '') + - (hasEndProp - ? `Received:\n` + - ` ${printReceived(result.value)}` + - (difference ? `\n\nDifference:\n\n${difference}` : '') - : traversedPath - ? `Received:\n ${RECEIVED_COLOR( - 'object', - )}.${traversedPath}: ${printReceived(lastTraversedObject)}` - : '') - ); - }; - if (pass === undefined) { - throw new Error('pass must be initialized'); - } + (hasValue + ? `Expected path: ${printExpected(expectedPath)}\n\n` + + `Expected value: not ${printExpected(expectedValue)}` + + (stringify(expectedValue) !== stringify(receivedValue) + ? `\nReceived value: ${printReceived(receivedValue)}` + : '') + : `Expected path: not ${printExpected(expectedPath)}\n\n` + + `Received value: ${printReceived(receivedValue)}`) + : () => + matcherHint(matcherName, undefined, expectedArgument, options) + + '\n\n' + + `Expected path: ${printExpected(expectedPath)}\n` + + (hasCompletePath + ? '\n' + + printDiffOrStringify( + expectedValue, + receivedValue, + EXPECTED_VALUE_LABEL, + RECEIVED_VALUE_LABEL, + this.expand, + ) + : `Received path: ${printReceived( + expectedPathType === 'array' || receivedPath.length === 0 + ? receivedPath + : receivedPath.join('.'), + )}\n\n` + + (hasValue + ? `Expected value: ${printExpected(expectedValue)}\n` + : '') + + `Received value: ${printReceived(receivedValue)}`); return {message, pass}; }, diff --git a/packages/expect/src/print.ts b/packages/expect/src/print.ts index 725a16ddbfac..e5687941b481 100644 --- a/packages/expect/src/print.ts +++ b/packages/expect/src/print.ts @@ -11,6 +11,7 @@ import { INVERTED_COLOR, RECEIVED_COLOR, diff, + getLabelPrinter, printExpected, printReceived, stringify, @@ -103,18 +104,31 @@ export const printDiffOrStringify = ( // because stringify (that is, pretty-format with min option) // omits constructor name for array or object, too bad so sad :( const difference = shouldPrintDiff(expected, received) - ? diff(expected, received, {expand}) // string | null + ? diff(expected, received, { + aAnnotation: expectedLabel, + bAnnotation: receivedLabel, + expand, + }) // string | null : null; // Cannot reuse value of stringify(received) in report string, // because printReceived does inverse highlight space at end of line, // but RECEIVED_COLOR does not (it refers to a plain chalk method). - return typeof difference === 'string' && difference.includes('- Expected') - ? difference - : `${expectedLabel}${printExpected(expected)}\n` + - `${receivedLabel}${ - stringify(expected) === stringify(received) - ? 'serializes to the same string' - : printReceived(received) - }`; + if ( + typeof difference === 'string' && + difference.includes('- ' + expectedLabel) && + difference.includes('+ ' + receivedLabel) + ) { + return difference; + } + + const printLabel = getLabelPrinter(expectedLabel, receivedLabel); + return ( + `${printLabel(expectedLabel)}${printExpected(expected)}\n` + + `${printLabel(receivedLabel)}${ + stringify(expected) === stringify(received) + ? 'serializes to the same string' + : printReceived(received) + }` + ); };