Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expect: Fix non-object received value in toHaveProperty #7986

Merged
merged 6 commits into from
Feb 28, 2019

Conversation

pedrottimark
Copy link
Contributor

@pedrottimark pedrottimark commented Feb 25, 2019

Summary

Fixes #7942

  1. In getPath function, EDIT moveguard in operator into try blockwith conditional and for primitive types boolean-number-string-symbol and use it only if value is undefined
  2. Although not a bug in the issue, also fix false negative from incorrect logic in hasOwnProperty method that called equals for value undefined even when property does not exist in received object

Residue for future pull requests:

  • Replace obscure 'pass must be initialized' error with meaningful matcher error if expected array path is empty
  • Rewrite getPath from recursive to iterative
  • Improve report when test fails

Test plan

Added 14 test cases. Here is the key to notes in cells:

  1. in was broken in Jest 24: TypeError: Cannot use 'in' operator to search for 'key' in …
  2. false negative has been passing when it should fail: Expected the function to throw an error. But it didn't throw anything.
  3. prototype was fixed in Jest 24: TypeError: Cannot read property 'prototype' of undefined
  4. false positive was incorrect error for false as received value

The other tests confirm that toHaveProperty matcher is compatible with in operator.

For example, the first row that passes for a setter without a getter. It is what it is :(

received path value 23.6.0 24.0.0
new Foo() 'setter' undefined
foo2 'a' undefined
foo2 'c' 'c'
foo2 'val' true
new E() 'nodeType' 1
'' 'length' 0 in
memoized 'memo' []
{a: {}} 'a.b' undefined false negative false negative
false key false positive in
0 key in
'' key in
Symbol() key in
Object.create 'not' prototype
{} []

@pedrottimark
Copy link
Contributor Author

cc @ulrichb @jeysal @dubzzz

Copy link
Contributor

@jeysal jeysal left a comment

Choose a reason for hiding this comment

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

Can't say I completely understand what is going on in this matcher, but the added tests all seem correct to me. Just unfortunate that the snapshot names are getting a bit confusing because for many of them, the reason for the assertion result is not visible in the stringified obj 😐

@pedrottimark
Copy link
Contributor Author

@jeysal Yeah, although concise parallel arrays of test cases seem super at first glance:

  • hard to distinguish obvious original spec and subtle later regression
  • hard to match up with serialized title of snapshots, especially with instances

After enough time to be confident that the change in behavior is solid, when we collaborate to improve the report, let’s pay special attention to clear feedback when received is an instance.

@SimenB SimenB requested a review from thymikee February 25, 2019 23:21
Copy link
Contributor

@dubzzz dubzzz left a comment

Choose a reason for hiding this comment

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

As I was on the review, I just did it ;)
Looks good to me

// Beware: 'length' in string throws, but property does exist.
// However, string['length'] is number, not undefined.
result.hasEndProp = false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In case the try/catch is only about primitives, I would prefer ... && typeof object === "object" && prop in object over try/catch.

In case of other situations, where in would throw where we really have no alternative to try/catch, then I would prefer extending the comment in the catch-clause to make it clear for the next code reader (and prevent her/him refactoring to a typeof x === "object" check).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for constructive critique from viewpoint of reader. Yes, I agree that the comment is incomplete and unclear.

Here is some data. What do you suggest to improve either code or comment, or both?

Copy link
Contributor

Choose a reason for hiding this comment

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

In this case, I would prefer to avoid try/catch and instead typeof-check for object/function (or maybe negative check for number/string/boolean/symbol).

Reasoning: Here have to swallow/mask a thrown error completely (it wouldn't be possible to e.g. log it). In such situations it's more safe to just state all the expected exemptions than possibly hide non-in operator related (i.e. really unexpected) errors. If we later need some additional exemptions (e.g. for your mentioned Host object or whatever) just add them later (+ fixate them with test cases).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree. Thank you for clear and convincing reasoning.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, great! And thanks for fixing this issue!

Copy link
Member

Choose a reason for hiding this comment

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

This needs #7708...

Copy link
Member

Choose a reason for hiding this comment

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

Merged that, could we use import {isPrimitive} from 'jest-get-type';? And if that's insufficient, fix it so it is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you. I just pushed another refactoring commit :)

@SimenB SimenB merged commit ec5e2d0 into jestjs:master Feb 28, 2019
@pedrottimark pedrottimark deleted the non-object-hasProperty branch March 1, 2019 00:52
@mjesun
Copy link
Contributor

mjesun commented Mar 6, 2019

This is, BTW, a breaking change. Multiple tests are failing due to:

received path value 23.6.0 24.0.0
{a: {}} 'a.b' undefined false negative false negative

I thought the standard way of behaving for Jest was to consider key: undefined and the absence of the key itself the same.

@SimenB
Copy link
Member

SimenB commented Mar 6, 2019

I thought the standard way of behaving for Jest was to consider key: undefined and the absence of the key itself the same.

Yeah mostly, but we want to move away from it (why toStrictEqual is a thing). Should not break in a minor of course.

@mjesun
Copy link
Contributor

mjesun commented Mar 6, 2019

Makes sense, I agree with the change. Should we back-out for the minor? Or is it good to go as-is?

@SimenB
Copy link
Member

SimenB commented Mar 6, 2019

I think we should make sure it works like it does in 24.1.0 (it is a breaking change), and create an issue for reinstating the current behavior for Jest 25. Agreed?

We still want to fix #7942, though

@pedrottimark
Copy link
Contributor Author

@mjesun The purpose of the pull request was to fix a regression. I might have gone too far.

To help me figure out if we can keep the unbreaking part and defer the breaking part, can you give a minimal example of assertion that fails?

@pedrottimark
Copy link
Contributor Author

pedrottimark commented Mar 6, 2019

Aha, the bug in assignment to pass which caused contradictory results:

expect({a: {}}).toHaveProperty('a.b', undefined); // 23.6
expect({a: {}}).not.toHaveProperty('a.b'); // 23.6

Yeah, I will open pull request to change it back and comment out the test until Jest 25

@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Regression regarding not.toHaveProperty() and non-object input
7 participants