Skip to content
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
* [instance()](/docs/api/ShallowWrapper/instance.md)
* [is(selector)](/docs/api/ShallowWrapper/is.md)
* [isEmpty()](/docs/api/ShallowWrapper/isEmpty.md)
* [invoke(event[, ...args])](/docs/api/ShallowWrapper/invoke.md)
* [key()](/docs/api/ShallowWrapper/key.md)
* [last()](/docs/api/ShallowWrapper/last.md)
* [map(fn)](/docs/api/ShallowWrapper/map.md)
Expand All @@ -61,7 +62,7 @@
* [setProps(nextProps)](/docs/api/ShallowWrapper/setProps.md)
* [setState(nextState[, callback])](/docs/api/ShallowWrapper/setState.md)
* [shallow([options])](/docs/api/ShallowWrapper/shallow.md)
* [simulate(event[, data])](/docs/api/ShallowWrapper/simulate.md)
* [simulate(event[, mock])](/docs/api/ShallowWrapper/simulate.md)
* [slice([begin[, end]])](/docs/api/ShallowWrapper/slice.md)
* [some(selector)](/docs/api/ShallowWrapper/some.md)
* [someWhere(predicate)](/docs/api/ShallowWrapper/someWhere.md)
Expand Down
51 changes: 51 additions & 0 deletions docs/api/ShallowWrapper/invoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# `.invoke(event[, ...args]) => Self`

Invoke event handlers


#### Arguments

1. `event` (`String`): The event name to be invoked
2. `...args` (`Any` [optional]): Arguments that will be passed to the event handler



#### Returns

`ShallowWrapper`: Returns itself.



#### Example

```jsx
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
const { count } = this.state;
return (
<div>
<div data-clicks={count}>
{count} clicks
</div>
<a onClick={() => this.setState({ count: count + 1 })}>
Increment
</a>
</div>
);
}
}

const wrapper = shallow(<Foo />);

expect(wrapper.find('[data-clicks=0]').length).to.equal(1);
wrapper.find('a').invoke('click');
expect(wrapper.find('[data-clicks=1]').length).to.equal(1);
```

#### Related Methods

- [`.simulate(event[, mock]) => Self`](simulate.md)
18 changes: 10 additions & 8 deletions docs/api/ShallowWrapper/simulate.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# `.simulate(event[, ...args]) => Self`
# `.simulate(event[, mock]) => Self`

Simulate events
Simulate events. The propagation behavior of browser events is simulated — however,
this method does not emit an actual event. Event handlers are called with a SyntheticEvent object.


#### Arguments

1. `event` (`String`): The event name to be simulated
2. `...args` (`Any` [optional]): A mock event object that will get passed through to the event
handlers.
2. `mock` (`Object` [optional]): A mock event object that will be merged with the event
object passed to the handlers.



Expand Down Expand Up @@ -50,9 +51,10 @@ expect(wrapper.find('.clicks-1').length).to.equal(1);

#### Common Gotchas

- Currently, event simulation for the shallow renderer does not propagate as one would normally
expect in a real environment. As a result, one must call `.simulate()` on the actual node that has
the event handler set.
- Even though the name would imply this simulates an actual event, `.simulate()` will in fact
- Even though the name would imply this simulates an actual event, `.simulate()` will in fact
target the component's prop based on the event you give it. For example, `.simulate('click')` will
actually get the `onClick` prop and call it.

#### Related Methods

- [`.invoke(event[, ...args]) => Self`](invoke.md)
5 changes: 4 additions & 1 deletion docs/api/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,12 @@ Returns the named prop of the current node.
#### [`.key() => String`](ShallowWrapper/key.md)
Returns the key of the current node.

#### [`.simulate(event[, data]) => ShallowWrapper`](ShallowWrapper/simulate.md)
#### [`.simulate(event[, mock]) => Self`](ShallowWrapper/simulate.md)
Simulates an event on the current node.

#### [`.invoke(event[, ...args]) => Self`](ShallowWrapper/invoke.md)
Invokes an event handler on the current node.

#### [`.setState(nextState) => ShallowWrapper`](ShallowWrapper/setState.md)
Manually sets state of the root component.

Expand Down
66 changes: 56 additions & 10 deletions src/ShallowWrapper.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import objectAssign from 'object.assign';
import flatten from 'lodash/flatten';
import unique from 'lodash/uniq';
import compact from 'lodash/compact';
Expand Down Expand Up @@ -36,6 +37,7 @@ import {
renderToStaticMarkup,
batchedUpdates,
isDOMComponentElement,
SyntheticEvent,
} from './react-compat';

/**
Expand Down Expand Up @@ -598,26 +600,70 @@ class ShallowWrapper {
}

/**
* Used to simulate events. Pass an eventname and (optionally) event arguments. This method of
* testing events should be met with some skepticism.
* Used to invoke event handlers. Pass an eventname and (optionally) event
* arguments. This method of testing events should be met with some
* skepticism.
*
* @param {String} event
* @param {Array} args
* @returns {ShallowWrapper}
*/
simulate(event, ...args) {
const handler = this.prop(propFromEvent(event));
if (handler) {
invoke(event, ...args) {
return this.single('invoke', () => {
const handler = this.prop(propFromEvent(event));
if (handler) {
withSetStateAllowed(() => {
batchedUpdates(() => {
handler(...args);
});
this.root.update();
});
}
return this;
});
}

/**
* Used to simulate events with propagation.
* Pass an eventname and an object with properties to assign to the event object.
* This method of testing events should be met with some skepticism.
*
* NOTE: can only be called on a wrapper of a single node.
*
* @param {String} event
* @param {Object} mock (optional)
* @returns {ShallowWrapper}
*/
simulate(event, mock) {
return this.single('simulate', () => {
const bubbleProp = propFromEvent(event);
const captureProp = `${bubbleProp}Capture`;
const e = new SyntheticEvent(undefined, undefined, { type: event, target: {} });
objectAssign(e, mock);
withSetStateAllowed(() => {
// TODO(lmr): create/use synthetic events
// TODO(lmr): emulate React's event propagation
const handlers = [
...this.parents().map(n => n.prop(captureProp)).reverse(),
this.prop(captureProp),
this.prop(bubbleProp),
...this.parents().map(n => n.prop(bubbleProp)),
];

batchedUpdates(() => {
handler(...args);
handlers.some((handler) => {
if (handler) {
handler(e);
if (e.isPropagationStopped()) {
return true;
}
}
return false;
});
});

this.root.update();
});
}
return this;
return this;
});
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/react-compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import objectAssign from 'object.assign';
import SyntheticEvent from 'react/lib/SyntheticEvent';
import { REACT013 } from './version';

let TestUtils;
Expand Down Expand Up @@ -185,4 +186,5 @@ export {
renderWithOptions,
unmountComponentAtNode,
batchedUpdates,
SyntheticEvent,
};
Loading