-
-
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
Switch to using classList instead of className #448
Conversation
let classes = findDOMNode(inst).className || ''; | ||
classes = classes.replace(/\s/g, ' '); | ||
return ` ${classes} `.indexOf(` ${className} `) > -1; | ||
let classes = findDOMNode(inst).classList; |
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 use const
instead of let
if the value is never reassigned.
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 will break in browsers that do not support classList
, which includes IE 8 and 9, IE 10 and 11 for SVG (according to http://caniuse.com/#search=classlist), etc.
We simply must not use classList
unconditionally - feature-detecting its support and falling back would be fine though.
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.
Ah, yep good find, didn't think to check the browser support. Given that, and also in the interest of merging prior to #421 since this is still something that currently breaks testing, would just adding a check that classes is of type String, and returning false if it isn't, be enough?
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.
That would cripple existing functionality in those browsers where classList
is unsupported. enzyme needs to not just "not break" in older browsers, it needs to "work as perfectly as possible".
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.
So the question is, does implementing classList
provide any benefits over the current implementation? Since we'll have to implement a method to check className
for SVGs as well, is there a sufficient advantage here to include a check and multiple code paths?
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.
The only advantages I can see are a) if the className
approach can't be made to work, or b) in the far future when we could drop className
support, we'd be able to cleanly delete a branch of code.
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.
Sorry, I mean in my previous comment to add the type checking whilst keeping the old className
approach. Updated in latest commit.
👍 I think this is a good approach, very simple. Ideally, we'd add some additional tests to verify this fixes SVG support, but until #421 is merged we can't test with actual SVG instances, so I think this is OK for now if it doesn't break any existing behavior. |
#421 should be merged before we can merge something like this, so that we don't pull in something we can't test |
@@ -50,6 +50,9 @@ export function instHasClassName(inst, className) { | |||
return false; | |||
} | |||
let classes = findDOMNode(inst).className || ''; | |||
if (typeof classes !== 'string') { | |||
return false; |
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 would just cause SVGs to silently fail, I don't think that's what we want. What we should do is branch on classList
and use the old code path when it doesn't exist.
export function instHasClassName(inst, className) {
if (!isDOMComponent(inst)) {
return false;
}
const node = findDOMNode(inst);
if (node.classList) {
return node.classList.contains(className)
}
let classes = node.className || '';
if (typeof classes === 'object') {
classes = classes.baseVal;
}
classes = classes.replace(/\s/g, ' ');
return ` ${classes} `.indexOf(` ${className} `) > -1;
}
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.
That snippet above should introduce support for SVGs in all cases as well. We could also invert the if
block if we wanted to be able to cleanly delete the className
code in the future.
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.
(Note that node.classList
could be an empty string, so checking it for truthiness is not sufficient - also, rather than using string operations, why not the equivalent of .split(/\s*/g).includes(className.trim())
?)
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.
The classList
property is a DOMTokenList
(docs), so I think the truthiness test is okay?
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.
@ljharb the string operations are the current implementation, I just added the classList
check. We can definitely look at implementing the className
branch differently though.
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 have committed your suggested code @aweary. Kept the string operations as it stands, turns out Array.includes
isn't supported by IE/Edge
This will need a rebase before merging, to get rid of the merge commit. |
Rebased 👍 |
Can we add some tests that would fail (even if it's only in a few browsers) without this change? |
Okay, test added and commits squashed. I've confirmed that the test fails on branch karma (at ad37536), and passes when the fix is applied. The test passes regularly within the current test framework, with and without the fix, since the error is specific to browser environments. I wanted to leave a quick note on IE; seeing that the SVGAnimatedString docs state no support for Internet Explorer. I checked on MSDN for details of their implementation of the But seeing that the current implementation doesn't really change anything (instead just adding the capability to handle it in environments where an issue has been reported), I think this is good to go? |
Anything else I need to add to this? |
@horyd looks like you need to merge the changes from the |
@horyd I tested |
@aweary Great, thanks for checking that! Should be good to go now :) |
Hey guys, rebased this maybe half a dozen times now.. would love to get it merged :) |
@horyd LGTM, but our tests seem to be failing in Node 0.10, so we'll need to address that first |
@aweary I have downloaded @horyd branch locally and have all tests pass on v0.10.37. The error on CI is:
The CI have used eslint-plugin-import 1.10.1 (I checked the logs). On my local machine I have 1.10.2. I think setting: "eslint-plugin-import": "^1.10.2" in package.json will fix the CI. BTW I have also tested the @horyd PR in my app - it fixed my issue with mount and material-ui SelectField (which includes SVG icon element). |
The property className returns an object on some (possibly all?) SVG elements. The object behaves under the interface SVGAnimatedString, which does not have method "replace". typeof check conditional use of classList, object check on className Test added for SVG find by class
@aweary Done! :) |
Thanks again @horyd! |
No worries! 😃 |
The property className returns an object on some (possibly all?) SVG elements (despite stating in the linked documentation that it always returns a string. Maybe there is a different SVG implementation that I missed...). The object behaves under the interface SVGAnimatedString, which does not have method
replace
. If you have an SVG element somewhere within the mounted component then, as it iterates during a find, it processes the SVG element and throwsTypeError: classes.replace is not a function
.I tested classList and it behaves correctly for SVG elements and regular elements alike. It also has a handy
contains
method too.