enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.96k stars 2.01k forks source link

CSS selectors match component props rather than rendered DOM #2560

Closed iangreenleaf closed 2 years ago

iangreenleaf commented 2 years ago

Current behavior

Using CSS selectors for a class name will match a component with that value in a className prop, even if it's not in the output at all. For example:

      const MyElement = (_props) => (<div className='abcd' />);
      const el = mount(<MyElement className='foobar' />);
      expect(el.exists('.foobar')).toBe(true); // passes

Surprisingly, this is not the case in shallow, just mount. render works correctly, as you might expect since it's parsing the HTML output.

Expected behavior

I would expect CSS selectors to match against the rendered output, and to fail if a class name is not present in the HTML.

Your environment

node 14.19.1

API

Version

library version
enzyme 3.10.0
react 16.12.0
react-dom 16.12.0
react-test-renderer 16.12.0
adapter (below)

Adapter

ljharb commented 2 years ago

In shallow, the wrapper is what's being rendered. In mount, the wrapper is the component itself - so that part isn't surprising. If your component renders a custom component with a className, using shallow, the selector would match it as well.

What's the output of el.debug()? That might make the behavior clearer.

iangreenleaf commented 2 years ago

Here's the output of el.debug():

    <MyElement className="foobar">
      <div className="abcd" />
    </MyElement>
ljharb commented 2 years ago

As you can see, .foobar matches the outer element. If you do el.children().exists('.foobar'), or el.hostNodes().exists('.foobar'), i'd expect both to be false.

iangreenleaf commented 2 years ago

My main point of contention is that el.html(), and the actual HTML that makes it to a browser, is:

  <div className="abcd" />

So when we query for a CSS selector of .foobar, it's implied that we want a DOM node with class="foobar", which doesn't exist. Enzyme is spotting a component with a prop of className: "foobar" and treating it as the same thing, but it's really not.

ljharb commented 2 years ago

It's not implied, though - because when working with enzyme wrappers, you're working with the component tree, not the final DOM that the browser sees (especially since one of the most important use cases of enzyme is testing server-rendering, where there's no DOM at all).

I understand this is violating your personal expectations, but it's the way enzyme's always worked, quite intentionally. This confusion is why .hostNodes() was added, though - so you can quickly filter down to just the DOM elements, and then operate on the resulting wrapper instead.

iangreenleaf commented 2 years ago

Fair enough. I do think this is weird behavior, but I can understand how enzyme is built around some assumptions that lead to this happening.