enzyme
enzyme copied to clipboard
can't find() element but wrapper.contains() it
Current behavior
I cannot find(Component|".selector")
but I can see it via html()
and via .contains()
Here are the most important parts of code. (I am using ava, t.log
is the test logger)
I am using a styled component here but it is the same when I add a custom className, id or data-attribute to attempt finding the element/Component.
code
const RetryButtonId = RetryButton.styledComponentId;
const retrySelector = `.${RetryButtonId}`;
t.log(retrySelector);
const retry = wrapper.find(RetryButton);
const _retry = wrapper.find(retrySelector);
const html = wrapper.html();
const match = html.match(new RegExp(`<button class="${RetryButtonId}.*?">Retry</button>`));
t.log(`match: ${match && match[0]}`);
t.log(`length "${retrySelector}": ${_retry.length}`);
t.log(`length "RetryButton": ${retry.length}`);
t.log(`exists "${retrySelector}": ${wrapper.exists(retrySelector)}`);
t.log(`contains "RetryButton": ${wrapper.contains(RetryButton)}`);
The logs show inconsistencies. While contains()
returns true
, exists()
returns false
.
The html()
matches the element but find()
and exists()
ignore it.
Logs
ℹ .sc-jTzLTM
ℹ match: <button class="sc-jTzLTM dlKhUw sc-bdVaJa kgpquR">Retry</button>
ℹ length ".sc-jTzLTM": 0
ℹ length "RetryButton": 0
ℹ exists ".sc-jTzLTM": false
ℹ contains "RetryButton": true
Expected behavior
contains()
, find()
, exists()
, html()
should return matching results.
Your environment
OS X 10.13.6
node: v10.10.0 npm: 6.4.1 yarn 1.12.3
API
- [ ] shallow
- [x] mount
- [ ] render
Version
library | version |
---|---|
enzyme | 3.8.0 |
react | 16.7.0 |
react-dom | 16.7.0 |
react-test-renderer | |
adapter (below) |
Adapter
- [x] enzyme-adapter-react-16
- [ ] enzyme-adapter-react-16.3
- [ ] enzyme-adapter-react-16.2
- [ ] enzyme-adapter-react-16.1
- [ ] enzyme-adapter-react-15
- [ ] enzyme-adapter-react-15.4
- [ ] enzyme-adapter-react-14
- [ ] enzyme-adapter-react-13
- [ ] enzyme-adapter-react-helper
- [ ] others ( )
I get the expected results if I call wrapper.update()
beforehand but this shouldn't be needed, right?
What does .debug() print out prior to the update? (.html()
should be ignored)
I’ll note that styled components tend to have some quirks which make working with them difficult. Could you provide the full component and test code?
Sorry I can't share the code since it's private. I'd have to set up a MVP for reproduction (probably won't have time).
Inspecting the debug tree is pretty hard. I tried it and wasn't very successful since the output is too cluttered (nested & extended components.). I gave up after a while and don't think I will try this method again in the near future.
I think I did notice that the "conditional section" wasn't in the tree. I'm not fully sure (cluttered output) and I moved on to try other methods.
My main concern was, that contains
and html
return absolutely different results than exists
and find
. This should simply not be an issue.
If you suggest not to use html
then this should be noted or be removed. What use is a method if it returns the wrong output? especially if several methods aside have conflicting output.
I’ll note that styled components tend to have some quirks which make working with them difficult. Could you provide the full component and test code?
As mentioned I' might not have time to create an MVP. This is the best I can do to explain those componennts.
const Button = styled.button`
background: red;
`
const MyButton = styled(Button)`
background: red;
`
class App extends Component (
state = {
there: false
}
toggle() {
this.setState(prevState => ({foo: !prevState.there}));
}
<ThemeProvider theme={myTheme}>
<React.Fragment>
<Button onClick={this.toggle}>Toggle</Button>
{this.state.there && <MyButton onClick={this.toggle}>Sometimes I'm here</Button>}
</React.Fragment>
</ThemeProvider>
)
const Wrapper = mount <App/>
const button = wrapper.find(Button).at(0);
// >> FAIL
// The next operation fails ("something about not present length... expected 1 but received 0")
// button.simulate(click, {target: {value: "Mock event"}});
// const myButton = wrapper.find(Button).at(1); // ReactWrapper[]
// myButton.simulate(click, {target: {value: "I'm conditional"}});
// >> FAIL
// The next operation fails ("something about not present length... expected 1 but received 0")
// wrapper.setState({there: true}, () => {
// EVEN INSIDE THE CALLBACK
// const myButton = wrapper.find(Button).at(1); // ReactWrapper[]
// myButton.simulate(click, {target: {value: "I'm conditional"}});
//});
// >> WINNER
wrapper.update()
// The next operation works as expected.
myButton.simulate(click, {target: {value: "I'm conditional"}});
I'm sorry it's late at night and This is the best I can do right now. (probably ever)
The purpose of .html
is that it uses the render
API, and produces HTML output. I agree that it should probably be removed.
contains
, exists
, and find
should definitely agree - although it's worth noting as well that simulate
doesn't actually simulate anything, it's just sugar for invoking a prop function - so you might indeed need a wrapper.update()
before things are consistent.
Thank you for your time.
I understand how simulate works. rendering is handled async from setting a state in react. I think this is why the issue occurs. I didn't see this in any documentation (which IMHO is too vague anyways). I tried several methods and was in some cases able to get the correct result after a await Promise.resolve()
or (await new Promise(r => setTimeout(r, n))
(which greatly helped me understand the issue).
I think better examples considering this topic would help. It seems to be a very common use case.
I’d be happy to accept a PR that made the docs more clear.
If I ever find the time... I am currently writing docs for another big library. (enquirer) so right now I'm rather busy.
I enjoy writing docs (mostly because I value them so much) so I might get back to this, ... no promises.
I usually write easy to follow guides alongside documentation. I might suggest a format (in January). I've had good responses to that format.
I suppose I hit a similar issue, but perhaps due to different reason as wrapper.update()
makes no difference:
it.each(['foo', 'bar'])(
'lorem ipsum',
(value) => {
// Logs 1
console.log(wrapper.find({ value }).length);
// Shows the nodes with matching `value`
// <mockConstructor>
// <WithStyles(MyElem) checked={true} value="foo" />
// <WithStyles(MyElem) checked={true} value="bar" />
// </mockConstructor>
console.log(wrapper.debug());
// Fails with: Method “props” is meant to be run on 1 node. 0 found instead.
expect(wrapper.find({ value }).props()).toMatchObject({ checked: true });
}
);
Any suggestions?
@maciej-gurban hm, that's strange. Could you file a new issue, ideally with a repro repo?
I am on a similar issue here. the component which is to render conditionally isn't showing up when using .find() but shows up on output of .contains()
// passes
loginWrapper.contains('input[placeholder="NewPassword');
// .props() meant to be run on 1 node , 0 found instead
loginWrapper.find('input[placeholder="New Password"]').props();
@shaleenmundra what's loginWrapper.debug()
say?
it doesn't return the "New Password" input component which .html() does .debug() is in sync with .find()
Any response on this thread?
@shaleenmundra it would still help if you could provide the literal output of .debug()
.
@toro705 if you have a similar issue, it'd be great if you could provide component code, test code, and wrapper.debug()
output.
Got the same issue ("react": "^17.0.2", "jest": "^27.0.6","enzyme": "^3.11.0")
it('should not accept file that is too small', async () => {
const file = new File([''], { type: 'image/png' });
Object.defineProperty(file, 'name', { value: 'file_too_small.jpg' });
Object.defineProperty(file, 'size', { value: 1571 });
await act(async () => {
await wrapper
.find('input')
.props()
.onChange({ target: { files: [file] } });
jest.advanceTimersByTime(10);
});
wrapper.update();
expect(wrapper.find('.file-name').text()).toBe('Please upload your image');
expect(wrapper.find('.file-clear-button').hasClass('hidden')).toBe(true);
expect(wrapper.find('.file-upload-icon').exists()).toBe(true);
expect(wrapper.find('.file-wrapper').hasClass('invalid')).toBe(true);
});
Last expect does not work without jest.advanceTimersByTime(10)
and wrapper.update()
- element cannot be found even when it's html contains that class.
@Inveth what's wrapper.debug()
look like after the update call?
@ljharb It has invalid class.
I did something else - I removed wrapper.update()
(leavingadvanceTimersByTime
) and wrapper.html()
does have that invalid class while wrapper.debug()
does not.
it('should not accept file that is too small', async () => {
const file = new File([''], { type: 'image/png' });
Object.defineProperty(file, 'name', { value: 'file_too_small.jpg' });
Object.defineProperty(file, 'size', { value: 1571 });
console.log(wrapper.html()); // does not have invalid class
console.log(wrapper.debug()); // does not have invalid class
await act(async () => {
await wrapper
.find('input')
.props()
.onChange({ target: { files: [file] } });
jest.advanceTimersByTime(10);
console.log(wrapper.html()); // has invalid class
console.log(wrapper.debug()); // does not have invalid class
});
console.log(wrapper.html()); // has invalid class
console.log(wrapper.debug()); // does not have invalid class
expect(wrapper.find('.file-name').text()).toBe('Please upload your image');
expect(wrapper.find('.file-clear-button').hasClass('hidden')).toBe(true);
expect(wrapper.find('.file-upload-icon').exists()).toBe(true);
expect(wrapper.find('.file-wrapper').hasClass('invalid')).toBe(true);
});
What happens if you add a wrapper.update()
after the act
call?