Enzyme: containsMatchingElement does not work with JSX expressions

Created on 21 Jul 2016  路  11Comments  路  Source: enzymejs/enzyme

Not sure if this is intended or not, but the following returns false. My expectation is that it should return true.

// component
import React from 'react';

const MyComponent = (props) => {
  const season = 2;
  const episode = 10;

  return (
   <section>
      <div className="thumbnail">Season {season}, Episode {episode}</div>
   </section>
  )
};

export default MyComponent;
// test
expect(component.containsMatchingElement(<div className="thumbnail">Season 2, Episode 10</div>)).to.equal(true);
// result: test fails

If I set the node's value explicitly to Season 2, Episode 10 the test passes, but not when using JSX expression.

bug help wanted

Most helpful comment

@ljharb Hey Jordan,

I think I was able to find a solution. All tests pass, including the one above.

Basically, I am joining an array of strings in the nodeEqual function before they get passed to childrenEqual. Since React element will also be an array - an array of objects, we can't blindly check "joined" version of left.children and right.children, hence the additional check for string equality.

if (leftHasChildren || rightHasChildren) {
   // ===== BEGIN =====
   let rightChildren = childrenToArray(right.children);
    if (Array.isArray(right.children) && right.children.join('') === left.children) {
      rightChildren = childrenToArray(right.children.join(''));
    }
   // ===== END =====
    if (!childrenEqual(
        childrenToArray(left.children),
        rightChildren,
        lenComp)) {
      return false;
    }
  }

If this looks good to you, I can submit a PR.

EDIT: This is not really a bug, but rather how React handles children apparently. Using JSX expression above transforms into 4 array elements inside the React.Children pseudo-array, even though it looks like a single element when rendered. However, I believe that Enzyme should be comparing "rendered" children, i.e. ["Season ", 2, "Episode ", 10] should be treated a single concatenated string, after all that's what the user sees on the page.

All 11 comments

hmm - it seems like it doesn't _contain_ a matching element, it _is_ a matching element. What does component.matchesElement(<div className="thumbnail">Season 2, Episode 10</div>) output?

@ljharb Sorry, I oversimplified the example. It is a nested child inside several divs.

ah k - that matters :-) in that case, i'd expect that to work. If you output component.debug(), what do you get?

The likely issue is that the component has ['Season ', season, ', Episode ', episode] as a child while your test element has the full text as a single string.

I've come across this problem too.

Nodes <div>mixed child</div> and <div>mixed {'child'}</div> are not considered equal/matching in nodeEqual().

Is there a reason for this behaviour? I'd say that comparison of the values before and after interpolation makes sense (and it makes sense for the nodes to match)

@ljharb After running component.debug() and printing results to console, I am getting the following output:

console.log

<div className="Thumbnail__meta">
Season 
2
, Episode 
10
</div>

or

console.dir

<div className="Thumbnail__meta">\nSeason \n2\n, Episode \n10\n</div>

Gotcha - seems like before comparing for matching elements, we need to replace all consecutive whitespace in text content with a single space.

@ljharb Hey Jordan,

I think I was able to find a solution. All tests pass, including the one above.

Basically, I am joining an array of strings in the nodeEqual function before they get passed to childrenEqual. Since React element will also be an array - an array of objects, we can't blindly check "joined" version of left.children and right.children, hence the additional check for string equality.

if (leftHasChildren || rightHasChildren) {
   // ===== BEGIN =====
   let rightChildren = childrenToArray(right.children);
    if (Array.isArray(right.children) && right.children.join('') === left.children) {
      rightChildren = childrenToArray(right.children.join(''));
    }
   // ===== END =====
    if (!childrenEqual(
        childrenToArray(left.children),
        rightChildren,
        lenComp)) {
      return false;
    }
  }

If this looks good to you, I can submit a PR.

EDIT: This is not really a bug, but rather how React handles children apparently. Using JSX expression above transforms into 4 array elements inside the React.Children pseudo-array, even though it looks like a single element when rendered. However, I believe that Enzyme should be comparing "rendered" children, i.e. ["Season ", 2, "Episode ", 10] should be treated a single concatenated string, after all that's what the user sees on the page.

I completely agree that this is just how React behaves, but that this is something enzyme should be papering over.

I'm not sure if your solution is the best one or not, but it seems plausible (caching right.children.join('') to avoid the double-join, ofc, and avoiding the extra "childrenToArray" call in the case where the conditional is entered).

A PR would be excellent, since the thing that will truly convince me is lots of test cases :-D

Have you seen the PR #512 have submitted? I believe it addresses the issue.

Has there been any updates on this issue? I'm running into the same problem and I don't see much activity on the PR

Was this page helpful?
0 / 5 - 0 ratings