Currently, the behavior for shallow rendering when you simulate foo is to call props.onFoo, whether foo is a real react event or not. When mount though, it checks the react-dom's test utils for simulators and throws if your event is not in there.
Since calling foo.props().onFoo() won't update the wrapper anymore without first calling component.update() also (as of enzyme 3), it's generally nicer to use simulate('foo') so you can avoid the update call.
Would it be possible to change the fallback for mount from throwing to the behavior that exists in shallow? It would make it simpler to call nested prop functions and also make the api between shallow and mount more consistent.
Here's the relevant code.
//shallow
simulateEvent(node, event, ...args) {
const handler = node.props[propFromEvent(event)];
if (handler) {
withSetStateAllowed(() => {
// TODO(lmr): create/use synthetic events
// TODO(lmr): emulate React's event propagation
// ReactDOM.unstable_batchedUpdates(() => {
handler(...args);
// });
});
}
}
//mount
simulateEvent(node, event, mock) {
const mappedEvent = mapNativeEventNames(event);
const eventFn = TestUtils.Simulate[mappedEvent];
if (!eventFn) { //HERE: I'd like to not throw and instead call propFromEvent like above
throw new TypeError(`ReactWrapper::simulate() event '${event}' does not exist`);
}
// eslint-disable-next-line react/no-find-dom-node
eventFn(nodeToHostNode(node), mock);
},
I鈥檇 rather remove simulate altogether, tbh - it doesn鈥檛 faithfully simulate the React events as it is.
I鈥檇 definitely not want to encourage further usage of this already problematic API.
what's the problem with the way it simulates them? It's nice to have something that lets us trigger event handlers without needing a forceUpdate right after.
with simulate, events don't bubble, associated events don't fire (like if you simulate a change, there's no key events leading up to it), etc.
By far, the most robust approach is to extract the prop can invoke it directly.
hm. what about a method that did that for you but also called update for you on the root-wrapper object. I'm not sure how the internals of the component wrappers work. But if there was a way to internally walk back up the tree until you hit the root wrapper.
wrapper.find(Child).executeProp('onChange', 'newText') //or maybe just exec or execute
where that internally that called child's onChange prop with newText and then somehow called update() on wrapper. so the wrapper of Child returned by find would (at least internally) need a method called _getRootWrapper or something which returned wrapper
Something like that would be a nice addition since v3 came out since right now this requires 2 statements and is a bit awkward.
shortcutting a way to invoke a prop is an interesting idea.
Similarly, shortcutting a way to shallow-render (or mount-render) a prop containing an element might be interesting.
Can you give an example of the second one? Not sure what you mean by that?
shallow(<Component foo={<OtherComponent />} />).shallowProp('foo').is(OtherComponent) === true
oh as a shortcut for
let component = shallow(<Component foo={<OtherComponent />} />)
let foo = shallow(component.props().foo);
expect(foo.is(OtherComponent)).toBe(true)
?
Yes, except that you'd need let foo = shallow(<div>{component.props().foo}</div>); to get it right :-)
i don't think you do. i have this code in a real project and it passes
let footer = shallow(component.find(Modal).props().footerContent);
What that does is shallow-render the element itself - that won't let you make assertions about the direct content of footerContent.
sorry I'm confused. Can you give an example of something that wouldn't work with what i did?
@bdwain what you did there is render not footerContent - but whatever footerContent renders. Meaning, instead of asserting that footerContent is an "X" element, with "y" props, you're testing X itself - and those tests belong with X's tests, not in component's tests.
Hm. The component I was referencing looks like this.
render(){
return <Modal footerContent={this.footerContent}>....</Modal>
}
get footerContent(){
return <div><Button>....</Button>....</div>
}
and the test is able to do this
let footer = shallow(component.find(Modal).props().footerContent);
footer.find(Button).at(1).simulate('click');
That works because you've shallow-rendered the div - in other words, footer.find(Button) is a noop, because footer.is(Button) (footer.debug() will confirm)
Ah that makes sense. Thanks for clarifying.
Regardless though, I still think original request of shortcutting a way to execute props would be a nice thing to have.
invoke now exists; simulate will be removed whenever enzyme v4 happens; i think this can be closed.
Most helpful comment
invokenow exists; simulate will be removed whenever enzyme v4 happens; i think this can be closed.