-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Skip undefined props when comparing nodes #662
Skip undefined props when comparing nodes #662
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think doing this in propsOfNode
is the proper way, since a lot of places call that - repetition inside an abstraction is OK.
Tests LGTM tho!
src/Utils.js
Outdated
for (let i = 0; i < leftKeys.length; i += 1) { | ||
const prop = leftKeys[i]; | ||
// we will check children later | ||
if (prop === 'children') { | ||
if (prop === 'children' || !left[prop]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be left[prop] == null
, since 0
is falsy too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, leftover from debugging.
src/Utils.js
Outdated
@@ -1,5 +1,6 @@ | |||
/* eslint no-use-before-define:0 */ | |||
import isEqual from 'lodash/isEqual'; | |||
import isNil from 'lodash/isNil'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm not sure why we need a dependency for == null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed the linter won't like non-strict equality check, I should've checked, my bad!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a non-strict check against null
is the sole exception :-)
src/Utils.js
Outdated
@@ -114,7 +115,7 @@ export function nodeEqual(a, b, lenComp = is) { | |||
} | |||
|
|||
if (!isTextualNode(a)) { | |||
const rightKeys = Object.keys(right); | |||
const rightKeys = Object.keys(right).filter(key => !isNil(right[key])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's just do key => right[key] != null
- it's clearer and built-in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, agree.
Apparently, moving
I guess |
If we're going to ignore In other words, if we're asking if a node has property "foo" equal to |
Ok, I see your point! Regarding this test: https://github.com/airbnb/enzyme/blob/master/test/ShallowWrapper-spec.jsx#L577, let me clarify one last thing. If there are nodes with props equal to expect(wrapper.find({ prop: undefined })).to.have.length(0);
expect(wrapper.find({ prop: null })).to.have.length(0); |
yeah, that's a good point/wrinkle. It seems like the options are, when finding If we choose the former path, |
I think there's a 3rd option. Check props in find(props) {
const hasNilProps = Object.keys(props).some(key => props[key] == null);
if (hasNilProps) {
throw new TypeError('Props can\'t have `null` or `undefined` values.');
}
// ...
} What do you think? |
I think that's probably best, but then that means that it's a breaking change. @lelandrichardson @aweary @blainekasten @nfcampos what do you think? |
this 3rd option is definitely the best of the three. this change should be accompanied with a note on the docs for |
I think @nfcampos's proposal is reasonable and makes this change clear to users. |
Agreed 👍 |
Glad we agree, I'll update the PR! |
ok, so it looks like React ignores |
@ljharb Good catch, will do! |
379ea7c
to
034cc0c
Compare
- `.find` with an object with `undefined` values should throw Fixes enzymejs#656.
PR updated, addressing all of the feedback above. Thanks to everyone! One question: I put the warning about an error when using |
34a6d8c
to
886e90d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great @vdemedes just please add the missing trailing commas to appease the linter and make travis green :)
docs/api/ShallowWrapper/equals.md
Outdated
@@ -32,4 +32,4 @@ expect(wrapper.equals(<div className="foo bar" />)).to.equal(true); | |||
when you are calling it you are calling it with a ReactElement or a JSX expression. | |||
- Keep in mind that this method determines equality based on the equality of the node's children as | |||
well. | |||
|
|||
- Following React's behavior, `.equals()` ignores properties, whose values are `undefined`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove the comma after 'properties'
886e90d
to
4827455
Compare
All good now, thanks guys for the feedback ;) |
src/ComplexSelector.js
Outdated
@@ -78,6 +78,13 @@ export default class ComplexSelector { | |||
return this.handleSelectors(selectors, wrapper); | |||
} | |||
|
|||
if (typeof selector === 'object' && selector !== null) { | |||
const hasUndefinedValues = Object.keys(selector).some(key => selector[key] === undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import values from 'object.values'
and you can do values(selector).some(value => value === undefined)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion, updated!
4827455
to
41b0c8a
Compare
src/Utils.js
Outdated
newProps[key] = props[key]; | ||
}); | ||
|
||
return newProps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alternatively:
return entries(props)
.filter(([, value]) => value !== undefined)
.reduce((acc, [key, value]) => assign({}, acc, { [key]: value }), {});
41b0c8a
to
ae25d12
Compare
ae25d12
to
e0c3c45
Compare
@ljharb should we get this in for v3? |
e0c3c45
to
eca06ae
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments
packages/enzyme/src/selectors.js
Outdated
@@ -141,6 +142,10 @@ export function buildPredicate(selector) { | |||
// If the selector is an non-empty object, treat the keys/values as props | |||
if (typeof selector === 'object') { | |||
if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) { | |||
const hasUndefinedValues = values(selector).some(value => value === undefined); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this one a test of the value itself but the others are testing typeof?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My habit is to always use typeof for safety; babel makes this unnecessary. I'll make them all be the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's now consistent in the project.
packages/enzyme/src/Utils.js
Outdated
return (node && node.props) || {}; | ||
return entries((node && node.props) || {}) | ||
.filter(([, value]) => typeof value !== 'undefined') | ||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is rather ineffecient, creating as many objects as you have keys, I know this is a testing library, but if you have a lot of tests it starts to add up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I could mutate the accumulator since the overall reduce is pure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
export function nodeMatchesObjectProps(node, props) { | ||
return isSubset(propsOfNode(node), props); | ||
return isSubset(propsOfNode(node), replaceUndefinedValues(props)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t understand why we need this change. Don’t we throw if the received props contain undefined values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In find
, yes, but I don't think that's the only place this is called.
eca06ae
to
9bb8a91
Compare
Fixes #656.
This PR ensures
null
andundefined
props are skipped when comparing nodes:A helper function
cleanProps
removes allnull
andundefined
props. I wanted to do that inpropsOfNode()
, but there are multiplereturn
statements, which would result in a lot of repetition.Let me know if this PR needs modification or anything else ;)