enzyme: wrapper.find('#name') returning two elements but only one element is shown in wrapper.html()

wrapper.find(‘#name’) returning two elements but only one element is shown in wrapper.html().

expected outcome

only one element would be returned by wrapper.find('#name') and my eventual goal is to get the val of that textarea.

wrapper.find(‘#name’)

image

wrapper.html()

Note that there is only the one element with id=name below <textarea id="name">Activate or redeem credit card</textarea>

<div class="App">
   <div id="siteModal" class="hide">
      <div class="modalInnerContainer">
         <div class="modalTitle">
            <!-- react-text: 5 --><!-- /react-text --><span class="modalExit">X</span>
         </div>
         <div class="modalContent"></div>
         <div class="modalActions"><button class="action">CANCEL</button><button class="action">CONTINUE</button></div>
      </div>
   </div>
   <div>
      <div class="flyoutPanel left ">
         <div class="flyoutPanelContent">
            <div class="panelHeader">
               <svg class="dsi dsiClose closePanel">
                  <use xlink:href="symbol-defs.svg#dsiClose"></use>
               </svg>
               <!-- react-text: 17 --><!-- /react-text -->
            </div>
            <div class="panelContent">
               <div>
                  <div class="sidePanelInnerContainer">
                     <div class="inputContainer">
                        <label for="name">Name</label>
                        <textarea id="name">Activate or redeem credit card</textarea>
                        <hr>
                     </div>
                     <div class="inputContainer">
                        <label for="description">Description</label>
                        <textarea id="description" name="description"></textarea>
                        <hr>
                     </div>
                  </div>
                  <button id="closeDrawer" style="visibility: hidden;">Close Flyout</button>
               </div>
            </div>
            <div class="panelFooter"></div>
         </div>
      </div>
      <a class="closeDrawer" style="z-index: 1;"></a>
      <div class="flyoutPanel right  show">
         <div class="flyoutPanelContent">
            <div class="panelHeader">
               <svg class="dsi dsiClose closePanel">
                  <use xlink:href="symbol-defs.svg#dsiClose"></use>
               </svg>
               <h3 class="title">Process Step Details</h3>
            </div>
            <div class="panelContent">
               <div>
                  <div class="sidePanelInnerContainer">
                     <div class="inputContainer">
                        <label for="name">Name</label>
                        <textarea id="name">Activate or redeem credit card</textarea>
                        <hr>
                     </div>
                     <div class="inputContainer">
                        <label for="description">Description</label>
                        <textarea id="description" name="description"></textarea>
                        <hr>
                     </div>
                  </div>
                  <button id="closeDrawer" style="visibility: hidden;">Close Flyout</button>
               </div>
            </div>
            <div class="panelFooter"></div>
         </div>
      </div>
      <a class="closeDrawer active" style="z-index: 1;"></a>
   </div>
   <div class="App-header">
      <span class="app-header-icon"><span class="letter">S</span><span class="dot">.</span></span><span class="app-header-segment">Entity Name Here</span>
      <span class="app-header-segment">
         <!-- react-text: 170 -->FY16 Year End Audit<!-- /react-text --><!-- react-text: 42 -->&nbsp;<!-- /react-text -->
      </span>
      <span class="app-header-segment green small-text">STAGE</span><span class="app-header-actions"><span class="temp-action">&nbsp;</span></span>
   </div>
   <div class="currentProcess">
      <div class="currentProcessNameContainer"><label id="processNameLabel" class="currentProcessLabel">Financial Statement Review</label><input type="hidden" id="processNameInput" class="currentProcessName" value="Financial Statement Review" style="width: 55%;"><span class="last-saved">Last saved: 8:23 PM</span><span class="currentProcessActions">&nbsp;</span></div>
      <div>
         <div class="mainContent">
            <div class="canvasHeader">
               <div class="canvasHeaderLeft">
                  <div style="display: inline-flex; width: 400px; height: 40px; background-color: rgba(0, 0, 0, 0.01); position: relative; z-index: 100;"></div>
               </div>
               <div class="canvasHeaderRight">
                  <svg class="dsi dsiUndo">
                     <use xlink:href="symbol-defs.svg#dsiUndo"></use>
                  </svg>
                  <svg class="dsi dsiRedo">
                     <use xlink:href="symbol-defs.svg#dsiRedo"></use>
                  </svg>
                  <svg class="dsi dsiEye">
                     <use xlink:href="symbol-defs.svg#dsiEye"></use>
                  </svg>
                  <svg class="dsi dsiZoom">
                     <use xlink:href="symbol-defs.svg#dsiZoom"></use>
                  </svg>
                  <svg class="dsi dsiMore">
                     <use xlink:href="symbol-defs.svg#dsiMore"></use>
                  </svg>
               </div>
            </div>
            <div class="cardsContainer" style="width: 100%; position: relative;">
               <div class="cardContainer">
                  <div class="toggleEngagementLibrary"></div>
                  <div class="cardInner mainCanvas" style="display: block; width: 100%; height: 865px; background-color: rgb(255, 255, 255);"></div>
               </div>
            </div>
            <div><button class="button primaryButton" type="submit" style="margin-left: 24px; margin-right: 10px; padding: 10px 16px;">Save</button><textarea id="customTextEditor" class="customTextEditorBlock"></textarea><textarea id="customOffSetTextEditor" class="customTextEditorBlock"></textarea></div>
         </div>
      </div>
   </div>
</div>

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 14
  • Comments: 47 (17 by maintainers)

Most upvoted comments

This is indeed correct behavior.

To filter out custom components, use .hostNodes() - ie, wrapper.find('#someID').hostNodes() and you’ll only get HTML elements.

@sarneeh because in enzyme 3, the nodes you get are both React component instances, and DOM nodes. If you want to just have DOM nodes, you use .hostNodes(). You’re getting 2 because one of them is Component, and the other is the div that Component renders.

I’ve also encountered this issue and don’t really understand the reason why it works like this now? Why do I need to run some hostNodes() function on my wrapper?

Mounting a single div with a className and querying this className with find returns me 2 elements now. IMO it’s something completely opposite to being developer-intuitive.

For example this:

const Component = () => <div className="class-1" />;
const wrapper = mount(<Component className="class-1" />);
console.log(wrapper.find('.class-1').length); // returns 2

Why would I expect to have 2 elements here?

Could someone explain this behaviour to me? @ljharb? 😃

I have a same issue in v3.

Chiming in - I am having the same issue here and was able to get more information using wrapper.find('#target_id').debug()

Here’s a simplified example:

<Component id="target_id" text="Foo">
  <span id="target_id">Foo</span>
</Component>

You can see that enzyme included the react pseudo-element in its markup, so that if id is the name of a prop it will match twice!

This was done with mount()

@RyanAtViceSoftware Have you found a solution for this issue? Currently having a similar experience trying to locate text in a component.

@ljharb Alright, I understand that. But why it has been decided that this behaviour should be the default one? Couldn’t it be reversed, so by default I’ll get the behaviour from Enzyme v2, and in v3 have an additional method to get all of them?

To be clear: I don’t want to hate anyone nor anything, I just want to understand the decision behind it, because maybe I’m missing something, and maybe I could learn something too 😄

hostNodes() solves it – but why is this necessary? Could at least be documented

what is that “evaluate expression” debugger that you are using? it seems cool 😄

https://github.com/airbnb/enzyme/issues/1174

@ljharb I got a hint from another issue card. This seems to be a compatibility issue with upgrading Enzyme v3. Tests are broken a lot ㅜㅜ. Other users seem to have some similar misunderstanding.

> mountedComponent.find("#foo")
ReactWrapper {length: 2, Symbol(enzyme.__unrendered__): null, Symbol(enzyme.__renderer__): {…}, Symbol(enzyme.__root__): ReactWrapper, Symbol(enzyme.__node__): {…}, …}

> mountedComponent.find("input#foo")
ReactWrapper {length: 1, Symbol(enzyme.__unrendered__): null, Symbol(enzyme.__renderer__): {…}, Symbol(enzyme.__root__): ReactWrapper, Symbol(enzyme.__node__): {…}, …}

Got the same issue. For me worked adjusting a little bit the selector. Not sure why though. Also, .hostNodes() worked as well.

oops, sorry 😃 .at(1) should do it

@badaljain i was using web storm at that time

A new issue might be warranted for that.

Sure: https://github.com/airbnb/enzyme/issues/2244

Thanks for looking into it.

Thanks, I did discover that from this thread (hooray). I was just providing a concrete example of how this is very surprising behavior. Maybe it could be aliased as getNode() or something.

Thanks, you are right, sorry… Just in case this was what I ended up doing (TypeScript):

export function find<T extends Element = Element>(
  wrapper: ReactWrapper,
  selectorOrPredicate?: string | ((w: Element) => boolean)
): T[] {
  return ((typeof selectorOrPredicate === 'string' ? wrapper.find(selectorOrPredicate) : wrapper) as ReactWrapper)
    .filterWhere(
      n =>
        typeof n.type() === 'string' &&
        (typeof selectorOrPredicate !== 'function' || selectorOrPredicate(n.getDOMNode()))
    )
    .map(n => n.getDOMNode()) as any
}

thanks and sorry for the inconvenience.