wrapper.find('#name') returning two elements but only one element is shown in wrapper.html().
only one element would be returned by wrapper.find('#name') and my eventual goal is to get the val of that textarea.

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 --> <!-- /react-text -->
</span>
<span class="app-header-segment green small-text">STAGE</span><span class="app-header-actions"><span class="temp-action"> </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"> </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>
Is the wrapper using mount or shallow? What is the .html() of the list of 2 text areas?
Also what's the component code look like? It's possible cheerio is filtering out a duplicate ID.
Wow, thanks for the quick response. I'm using mount. I get an error when I try and do .html()

This is really weird to me also. This creates an error

but this works

so i don't know how i do an html on the second element.
wrapper.find('#name').eq(1).html()
no luck wrapper.find('#name').eq(1).html()

oops, sorry :-) .at(1) should do it
I guess I could always just RTFM... thanks man! That seemed to work. But I'm getting two elements which is unexpected and odd to me.

@RyanAtViceSoftware the next step is, could you provide the actual React component code that generates this, as well as the code you're using in your tests to create the wrapper?
@ljharb Here's the component and test
import React, { Component } from 'react';
import '../../../../node_modules/deloitte-symphony-core-fe/dist/scss/components/form.scss';
import '../../../assets/sass/tabstyles.scss';
import '../../../assets/sass/sidePanelContainer.scss';
import * as icActions from '../../../state/actions/internalControl.action';
import { updateRightDrawerContent } from '../../../state/actions/drawer.action';
import { CANVAS_OBJECT_TYPES } from '../../../constants/canvasObjectTypes';
class OperationDetails extends Component {
constructor(props) {
super(props);
this.closePanel = this.closePanel.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
closePanel() {
// TODO: Ryan - We should be firing an action here and using redux state
var closeButton = document.getElementById("closeDrawer");
closeButton.click();
}
handleInputChange(event, field) {
let target = event.target;
let content = this.props.content;
if (target !== undefined) {
const value = target.type === 'checkbox' ? target.checked : target.value;
content.dto[field] = value;
this.props.content.dispatch(icActions.updateProcessDtos(content.dto, CANVAS_OBJECT_TYPES.Operation));
this.props.content.dispatch(updateRightDrawerContent(content));
}
}
// TODO: Ryan - We should use container\presentational pattern here
render() {
const { content } = this.props;
if (content) {
return (
<div className="sidePanelInnerContainer">
<div className="inputContainer">
<label htmlFor="name">Name</label>
<textarea id="name"
value={(content.dto) ? content.dto.text : ''}
onChange={(e) => {
this.handleInputChange(e, "text");
}}
></textarea>
<hr />
</div>
<div className="inputContainer">
<label htmlFor="description">Description</label>
<textarea id="description" name="description" defaultValue={content.dto && content.dto.description} onChange={(e) => {
this.handleInputChange(e, "description")
}}></textarea>
<hr />
</div>
</div>
);
}
else {
return <div/>;
}
}
}
export default OperationDetails;
import React from 'react';
import chai, { expect } from 'chai';
import { Provider } from 'react-redux';
import { mount, shallow } from 'enzyme';
import createRootReducer from '../../../src/state/reducers/createRootReducer';
import createStore from '../../../src/state/stores/main.store';
import '../../setupTests';
import App from '../../../src/containers/App/App';
import behaviors from '../behaviors';
import expected from './rightDrawerExpected';
import chaiProperties from 'chai-properties';
const go = (process.env.NODE_ENV === 'production') ? require('../../../GoJs/1.6.23-go-src') :
require('../../../GoJs/1.6.23-go-src-debug');
import * as chartUtils from '../../../src/common/util/chart/canvas.utils';
import ProcessCanvasComponent from '../../../src/components/canvas/processCanvas';
import {stubComponent} from '../../testUtils';
chai.use(chaiProperties);
chai.use(function(_chai, _) {
_chai.Assertion.addMethod('withMessage', function(msg) {
_.flag(this, 'message', msg);
});
});
function getTestingStore(initialState) {
const store = createStore(createRootReducer(initialState));
const dispatch = action => {
store.dispatch(action);
return {
andExpect: function(expected) {
expect(store.getState(),
"Store state was not what was expected after dispatching the following action: "
+ JSON.stringify(action))
.to.have.properties(expected);
}
}
};
return {
dispatch,
getState: store.getState,
subscribe: store.subscribe,
replacementReducer: store.replaceReducer
};
}
describe('Given I open a process and expand the details on an operation', () => {
it(
'when I update the title in the details pane then the operation on the canvas also shows the title',
() => {
const symphonyContext = {
engagementId: '897c6642-2244-4b7c-8311-198ed54602ce'
};
const store = getTestingStore();
const diagramMock = {
toolManager: {
linkingTool: { linkValidation: function() {} },
relinkingTool: { linkValidation: function() {} }
},
startTransaction: function() {}
};
chartUtils.AddNodeShapes = function() {
};
chartUtils.AddPalette = function() {
};
go.GraphObject = {
make: function() {
return diagramMock;
}
};
stubComponent(ProcessCanvasComponent);
const wrapper = mount(<Provider store={store}><App
symphonyContext={symphonyContext}/></Provider>);
store.dispatch(behaviors.setSymponyContext);
store.dispatch(behaviors.setConnectionRules);
store.dispatch(behaviors.loadProcesses);
store.dispatch(behaviors.setSymphonyEngagement);
store.dispatch(behaviors.setCurrentProcess);
store.dispatch(behaviors.navigateToProcessFlowDiagram);
store.dispatch(behaviors.openOperationInRightDrawer);
const nameTextAreaWrapper = wrapper.find('#name').first();
const nameTextAreaHtml = nameTextAreaWrapper.html();
const expectedNameTextAreaHtml = '<textarea id="name">Activate or redeem credit card</textarea>';
expect(nameTextAreaHtml).to.equal(expectedNameTextAreaHtml);
nameTextAreaWrapper.simulate('keydown', { which: 'c' })
const nameTextAreaHtmlAfter = wrapper.find('#name').first().html();
const expectedNameTextAreaHtmlAfter = '<textarea id="name">Activate or redeem credit card</textarea>';
expect(nameTextAreaHtmlAfter).to.equal(expectedNameTextAreaHtmlAfter);
});
});
@RyanAtViceSoftware Have you found a solution for this issue? Currently having a similar experience trying to locate text in a component.
@frankmariette unfortunately no...
Is this still an issue in v3?
I have a same issue in v3.
@CoderK can you file a new issue, just in case?
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.
@RyanAtViceSoftware
You created a wrapper for Whole App instead of the component you are testing. It looks like you have one more component that uses name as id. Check other components.
@skrece22 there was not more than one component with name as id, that was the whole point of the issue and not I'm using mount, not shallow, and mount is intended for testing more than one component.
@RyanAtBarefoot OperationDetails must rendered twice. Please share github repo for this hope it willbe more helpful to fix the bug.
@RyanAtViceSoftware Whats the IDE you are using?
@badaljain i was using web storm at that time
I'm experiencing the same problem. Using Visual Studio Code and running code in console (npm test).
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()
This is indeed correct behavior.
To filter out custom components, use .hostNodes() - ie, wrapper.find('#someID').hostNodes() and you'll only get HTML elements.
hostNodes() solves it -- but why is this necessary? Could at least be documented
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? 馃槂
@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.
@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 馃槃
@sarneeh i believe this was chosen to match the way the react dev tools chrome extension works.
@ljharb thanks, hostNodes() works, but unfortunately it will make the wrapper unusable since it removes non-host nodes.
Question: what would be the correct way of obtaining only the host-nodes or alternatively the HTMLElement s that match find(selector) without modifying the wrapper ? How can I differentiate between host and non-host nodes ? I tried with typeof wrapper.type==='string'but didn't seem to work so far.
In my case I've been struggling with this problem, I'm using mount()and attachTo. I think the problem is in part because I'm using styled-components / emotion and it seems to be creating non-host nodes with lower case names, so wrapper.find('Something a') will return two nodes. I will try to build a working example, but in this case I think it's an issue (not sure if enzyme's or styled-components / emotion). Will try to research this with more detail and come back to you,
thanks, keep it up!
@cancerberoSgx .hostNodes doesn't modify the wrapper, it returns a new one. You can keep the old one as you like. Yes, typeof wrapper.type === 'string' would be the condition to use in findWhere; the definition of a host node is "it's got a string type". The capitalization isn't relevant.
In your case, I'd use .find(Something).find('a') rather than relying on the string form of the display name.
Please file a new issue if you have any further trouble!
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.
> 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.
@asumaran because of https://github.com/airbnb/enzyme/issues/836#issuecomment-401260477
In the following example, getting two results from getDOMNode() is surprising since only one DOM node exists:
function SomeWrapper({ as: AsComponent, id }) {
// Imagine that this does useful things :) There is a case for it.
return <AsComponent id={id} />;
}
mount(<SomeWrapper as="div" id="example-id" />)
.find('#example-id')
// This returns two items.
.getDOMNode();
@kumar303 .find returns two items; getDOMNode returns the result for each found item. If you want to restrict to host nodes, use .hostNodes().getDOMNode() instead.
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.
Is "node" a DOM node, or a React tree node? Would a React Native user expect "node" to mean "DOM node", when a Host Node for RN is something like Text or Image?
Is "node" a DOM node, or a React tree node?
In the case of getDOMNode(), it could be either. This is why I found it surprising. I expected it only to return DOM nodes.
Your comment is that getting two dom nodes is surprising - are you getting a non-dom node from that method?
Yes, the example in https://github.com/airbnb/enzyme/issues/836#issuecomment-531974276 gets a non-DOM node from getDOMNode(). The first node it gets is <SomeWrapper>, the second is <div />. I would only expect it to return <div /> since that's the only DOM node. This was all discussed by others earlier up in the thread but since the example component I showed is really only a wrapper, it's a good illustration of how this behavior is surprising.
Interesting. That does seem like a problem; getDOMNode should probably only return host nodes. A new issue might be warranted for that.
A new issue might be warranted for that.
Sure: https://github.com/airbnb/enzyme/issues/2244
Thanks for looking into it.
Hi All
Ran across the issue recently with
import Button from "react-bootstrap/Button";
...
<Button className="foo-class"></Button>
and my test was failing
expect(converterWrapper.find(".foo-class").toHaveLength(1); // ==> returns 2
When debugging converterWrapper.find(".foo-class").debug()
<Button className="conversion-form__switch-currency conversion-form__switch-currency-GBP" onClick={[Function: onClick]} variant="primary" active={false} disabled={false} type="button">
<button onClick={[Function: onClick]} disabled={false} type="button" className="foo-class btn btn-primary">
To:
GBP
</button>
</Button>
<button onClick={[Function: onClick]} disabled={false} type="button" className="foo-class btn btn-primary">
To:
GBP
</button>
So Enzyme generates Button and button which is why it returns 2 nodes
I haven't found a better solution than specifying Button
expect(converterWrapper.find("Button.foo-class").toHaveLength(1)
@jasonsrogers you can add .hostNodes() to filter it down to the HTML elements; separately, don't find by string when you can .find(Button).filter('.foo-class')
what is that "evaluate expression" debugger that you are using? it seems cool :D
Most helpful comment
This is indeed correct behavior.
To filter out custom components, use
.hostNodes()- ie,wrapper.find('#someID').hostNodes()and you'll only get HTML elements.