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

Switch to using classList instead of className #448

Merged
merged 1 commit into from
Jul 5, 2016
Merged

Switch to using classList instead of className #448

merged 1 commit into from
Jul 5, 2016

Conversation

horyd
Copy link
Contributor

@horyd horyd commented Jun 9, 2016

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 throws TypeError: 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.

@horyd horyd mentioned this pull request Jun 9, 2016
let classes = findDOMNode(inst).className || '';
classes = classes.replace(/\s/g, ' ');
return ` ${classes} `.indexOf(` ${className} `) > -1;
let classes = findDOMNode(inst).classList;
Copy link
Collaborator

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.

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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".

Copy link
Collaborator

@aweary aweary Jun 10, 2016

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

@aweary
Copy link
Collaborator

aweary commented Jun 9, 2016

👍 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.

@nfcampos
Copy link
Collaborator

nfcampos commented Jun 9, 2016

#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;
Copy link
Collaborator

@aweary aweary Jun 11, 2016

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;
}

Copy link
Collaborator

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.

Copy link
Member

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())?)

Copy link
Contributor Author

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?

Copy link
Collaborator

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.

Copy link
Contributor Author

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

@ljharb
Copy link
Member

ljharb commented Jun 13, 2016

This will need a rebase before merging, to get rid of the merge commit.

@horyd
Copy link
Contributor Author

horyd commented Jun 14, 2016

Rebased 👍

@ljharb
Copy link
Member

ljharb commented Jun 19, 2016

Can we add some tests that would fail (even if it's only in a few browsers) without this change?

@horyd
Copy link
Contributor Author

horyd commented Jun 20, 2016

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 className property (see here) and it states that it is also an Object, but nothing further. I'm not sure what the interface for this object is.

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?

@horyd
Copy link
Contributor Author

horyd commented Jun 27, 2016

Anything else I need to add to this?

@aweary
Copy link
Collaborator

aweary commented Jun 27, 2016

@horyd looks like you need to merge the changes from the master branch.

@aweary
Copy link
Collaborator

aweary commented Jun 27, 2016

@horyd I tested className for svgs in IE9/10/11 and className returned an SVGAnimatedString object, so it seems it is supported.

@horyd
Copy link
Contributor Author

horyd commented Jun 27, 2016

@aweary Great, thanks for checking that! Should be good to go now :)

@horyd
Copy link
Contributor Author

horyd commented Jul 4, 2016

Hey guys, rebased this maybe half a dozen times now.. would love to get it merged :)

@aweary
Copy link
Collaborator

aweary commented Jul 4, 2016

@horyd LGTM, but our tests seem to be failing in Node 0.10, so we'll need to address that first

@tb
Copy link

tb commented Jul 5, 2016

@aweary I have downloaded @horyd branch locally and have all tests pass on v0.10.37.

The error on CI is:

/home/travis/build/airbnb/enzyme/node_modules/eslint-plugin-import/lib/index.js:6
const rules = exports.rules = {
^^^^^

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).

@aweary
Copy link
Collaborator

aweary commented Jul 5, 2016

Thanks @tb, we actually cleared up the linting issue in #482

@horyd hate to do this to you, but if you could update this with master once more I think we can get this merged afterwards.

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
@horyd
Copy link
Contributor Author

horyd commented Jul 5, 2016

@aweary Done! :)

@aweary aweary merged commit e6ee9d7 into enzymejs:master Jul 5, 2016
@aweary
Copy link
Collaborator

aweary commented Jul 5, 2016

Thanks again @horyd!

@horyd
Copy link
Contributor Author

horyd commented Jul 6, 2016

No worries! 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants