diff --git a/README.md b/README.md
index 41fe6db28..f82129b2b 100644
--- a/README.md
+++ b/README.md
@@ -25,11 +25,11 @@ This library is a replacement for [Enzyme](http://airbnb.io/enzyme/).
## Example
```jsx
-import { render } from 'react-native-testing-library';
+import { render, fireEvent } from 'react-native-testing-library';
import { QuestionsBoard } from '../QuestionsBoard';
function setAnswer(question, answer) {
- question.props.onChangeText(answer);
+ fireEvent.changeText(question, answer);
}
test('should verify two questions', () => {
@@ -39,7 +39,7 @@ test('should verify two questions', () => {
setAnswer(allQuestions[0], 'a1');
setAnswer(allQuestions[1], 'a2');
- getByText('submit').props.onPress();
+ fireEvent.press(getByText('submit'));
expect(props.verifyQuestions).toBeCalledWith({
'1': { q: 'q1', a: 'a1' },
@@ -148,6 +148,111 @@ test('Component has a structure', () => {
});
```
+## `FireEvent API`
+
+### `fireEvent: (element: ReactTestInstance, eventName: string, data?: *) => void`
+
+Invokes named event handler on the element or parent element in the tree.
+
+```jsx
+import { View } from 'react-native';
+import { render, fireEvent } from 'react-native-testing-library';
+import { MyComponent } from './MyComponent';
+
+const onEventMock = jest.fn();
+const { getByTestId } = render(
+
+);
+
+fireEvent(getByTestId('custom'), 'myCustomEvent');
+```
+
+### `fireEvent.press: (element: ReactTestInstance) => void`
+
+Invokes `press` event handler on the element or parent element in the tree.
+
+```jsx
+import { View, Text, TouchableOpacity } from 'react-native';
+import { render, fireEvent } from 'react-native-testing-library';
+
+const onPressMock = jest.fn();
+
+const { getByTestId } = render(
+
+
+ Press me
+
+
+);
+
+fireEvent.press(getByTestId('button'));
+```
+
+### `fireEvent.doublePress: (element: ReactTestInstance) => void`
+
+Invokes `doublePress` event handler on the element or parent element in the tree.
+
+```jsx
+import { TouchableOpacity, Text } from 'react-native';
+import { render, fireEvent } from 'react-native-testing-library';
+
+const onDoublePressMock = jest.fn();
+
+const { getByTestId } = render(
+
+ Click me
+
+);
+
+fireEvent.doublePress(getByTestId('button-text'));
+```
+
+### `fireEvent.changeText: (element: ReactTestInstance, data?: *) => void`
+
+Invokes `changeText` event handler on the element or parent element in the tree.
+
+```jsx
+import { View, TextInput } from 'react-native';
+import { render, fireEvent } from 'react-native-testing-library';
+
+const onChangeTextMock = jest.fn();
+const CHANGE_TEXT = 'content';
+
+const { getByTestId } = render(
+
+
+
+);
+
+fireEvent.changeText(getByTestId('text-input'), CHANGE_TEXT);
+```
+
+### `fireEvent.scroll: (element: ReactTestInstance, data?: *) => void`
+
+Invokes `scroll` event handler on the element or parent element in the tree.
+
+```jsx
+import { ScrollView, TextInput } from 'react-native';
+import { render, fireEvent } from 'react-native-testing-library';
+
+const onScrollMock = jest.fn();
+const eventData = {
+ nativeEvent: {
+ contentOffset: {
+ y: 200,
+ },
+ },
+};
+
+const { getByTestId } = render(
+
+ XD
+
+);
+
+fireEvent.scroll(getByTestId('scroll-view'), eventData);
+```
+
## `debug`
Log prettified shallowly rendered component or test instance (just like snapshot) to stdout.
@@ -178,6 +283,7 @@ test('fetch data', async () => {
```
+
[build-badge]: https://img.shields.io/circleci/project/github/callstack/react-native-testing-library/master.svg?style=flat-square
[build]: https://circleci.com/gh/callstack/react-native-testing-library
[version-badge]: https://img.shields.io/npm/v/react-native-testing-library.svg?style=flat-square
diff --git a/src/__mocks__/reactNativeMock.js b/src/__mocks__/reactNativeMock.js
new file mode 100644
index 000000000..7c38c54e5
--- /dev/null
+++ b/src/__mocks__/reactNativeMock.js
@@ -0,0 +1,6 @@
+// @flow
+export const View = (props: *) => props.children;
+export const ScrollView = (props: *) => props.children;
+export const Text = (props: *) => props.children;
+export const TextInput = () => null;
+export const TouchableOpacity = (props: *) => props.children;
diff --git a/src/__tests__/fireEvent.test.js b/src/__tests__/fireEvent.test.js
new file mode 100644
index 000000000..028d18391
--- /dev/null
+++ b/src/__tests__/fireEvent.test.js
@@ -0,0 +1,132 @@
+import React from 'react';
+import {
+ View,
+ TouchableOpacity,
+ Text,
+ ScrollView,
+ TextInput,
+} from '../__mocks__/reactNativeMock';
+import fireEvent from '../fireEvent';
+import { render } from '..';
+
+const OnPressComponent = ({ onPress }) => (
+
+
+ Press me
+
+
+);
+
+const WithoutEventComponent = () => (
+
+ Content
+
+);
+
+const CustomEventComponent = ({ onCustomEvent }) => (
+
+ Click me
+
+);
+
+describe('fireEvent', () => {
+ test('should invoke specified event', () => {
+ const onPressMock = jest.fn();
+ const { getByTestId } = render();
+
+ fireEvent(getByTestId('button'), 'press');
+
+ expect(onPressMock).toHaveBeenCalled();
+ });
+
+ test('should invoke specified event on parent element', () => {
+ const onPressMock = jest.fn();
+ const { getByTestId } = render();
+
+ fireEvent(getByTestId('text-button'), 'press');
+
+ expect(onPressMock).toHaveBeenCalled();
+ });
+
+ test('should throw an Error when event handler was not found', () => {
+ const { getByTestId } = render();
+
+ expect(() => fireEvent(getByTestId('text'), 'press')).toThrowError(
+ 'No handler function found for event: press'
+ );
+ });
+
+ test('should invoke event with custom name', () => {
+ const handlerMock = jest.fn();
+ const EVENT_DATA = 'event data';
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ fireEvent(getByTestId('custom'), 'customEvent', EVENT_DATA);
+
+ expect(handlerMock).toHaveBeenCalledWith(EVENT_DATA);
+ });
+});
+
+test('fireEvent.press', () => {
+ const onPressMock = jest.fn();
+ const { getByTestId } = render();
+
+ fireEvent.press(getByTestId('text-button'));
+
+ expect(onPressMock).toHaveBeenCalled();
+});
+
+test('fireEvent.scroll', () => {
+ const onScrollMock = jest.fn();
+ const eventData = {
+ nativeEvent: {
+ contentOffset: {
+ y: 200,
+ },
+ },
+ };
+
+ const { getByTestId } = render(
+
+ XD
+
+ );
+
+ fireEvent.scroll(getByTestId('scroll-view'), eventData);
+
+ expect(onScrollMock).toHaveBeenCalledWith(eventData);
+});
+
+test('fireEvent.doublePress', () => {
+ const onDoublePressMock = jest.fn();
+
+ const { getByTestId } = render(
+
+ Click me
+
+ );
+
+ fireEvent.doublePress(getByTestId('button-text'));
+
+ expect(onDoublePressMock).toHaveBeenCalled();
+});
+
+test('fireEvent.changeText', () => {
+ const onChangeTextMock = jest.fn();
+ const CHANGE_TEXT = 'content';
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ fireEvent.changeText(getByTestId('text-input'), CHANGE_TEXT);
+
+ expect(onChangeTextMock).toHaveBeenCalledWith(CHANGE_TEXT);
+});
diff --git a/src/__tests__/render.test.js b/src/__tests__/render.test.js
index 1c23015d2..a952ebd10 100644
--- a/src/__tests__/render.test.js
+++ b/src/__tests__/render.test.js
@@ -1,24 +1,8 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
-import { View, Text, TouchableOpacity } from 'react-native'; // eslint-disable-line import/no-unresolved
+import { View, Text, TouchableOpacity } from '../__mocks__/reactNativeMock';
import { render } from '..';
-jest.mock(
- 'react-native',
- () => ({
- View(props) {
- return props.children;
- },
- Text(props) {
- return props.children;
- },
- TouchableOpacity(props) {
- return props.children;
- },
- }),
- { virtual: true }
-);
-
class Button extends React.Component {
render() {
return (
diff --git a/src/fireEvent.js b/src/fireEvent.js
new file mode 100644
index 000000000..1cb51d1c1
--- /dev/null
+++ b/src/fireEvent.js
@@ -0,0 +1,68 @@
+// @flow
+import ErrorWithStack from './helpers/errorWithStack';
+
+const findEventHandler = (element: ReactTestInstance, eventName: string) => {
+ const eventHandler = toEventHandlerName(eventName);
+
+ if (typeof element.props[eventHandler] === 'function') {
+ return element.props[eventHandler];
+ }
+
+ if (element.parent === null) {
+ throw new ErrorWithStack(
+ `No handler function found for event: ${eventName}`,
+ invokeEvent
+ );
+ }
+
+ return findEventHandler(element.parent, eventName);
+};
+
+const invokeEvent = (
+ element: ReactTestInstance,
+ eventName: string,
+ data?: *
+) => {
+ const handler = findEventHandler(element, eventName);
+
+ return handler(data);
+};
+
+const toEventHandlerName = (eventName: string) =>
+ `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`;
+
+const pressHandler = (element: ReactTestInstance) =>
+ invokeEvent(element, 'press');
+const doublePressHandler = (element: ReactTestInstance) =>
+ invokeEvent(element, 'doublePress');
+const changeTextHandler = (element: ReactTestInstance, data?: *) =>
+ invokeEvent(element, 'changeText', data);
+const scrollHandler = (element: ReactTestInstance, data?: *) =>
+ invokeEvent(element, 'scroll', data);
+
+const EVENTS = [
+ {
+ name: 'press',
+ handler: pressHandler,
+ },
+ {
+ name: 'doublePress',
+ handler: doublePressHandler,
+ },
+ {
+ name: 'changeText',
+ handler: changeTextHandler,
+ },
+ {
+ name: 'scroll',
+ handler: scrollHandler,
+ },
+];
+
+const fireEvent = invokeEvent;
+
+EVENTS.forEach(event => {
+ fireEvent[event.name] = event.handler;
+});
+
+export default fireEvent;
diff --git a/src/index.js b/src/index.js
index c9014bd68..a2181079a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -3,8 +3,10 @@ import render from './render';
import shallow from './shallow';
import flushMicrotasksQueue from './flushMicrotasksQueue';
import debug from './debug';
+import fireEvent from './fireEvent';
export { render };
export { shallow };
export { flushMicrotasksQueue };
export { debug };
+export { fireEvent };