Enzyme: Feature Request: Allow simulation of non-react events in mount

Created on 9 Nov 2017  路  17Comments  路  Source: enzymejs/enzyme

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);
      },
feature request

Most helpful comment

invoke now exists; simulate will be removed whenever enzyme v4 happens; i think this can be closed.

All 17 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AdamYahid picture AdamYahid  路  3Comments

ahuth picture ahuth  路  3Comments

blainekasten picture blainekasten  路  3Comments

andrewhl picture andrewhl  路  3Comments

aweary picture aweary  路  3Comments