From 96b6b3f90e0bbd2e83791780170a7f701ae7673f Mon Sep 17 00:00:00 2001
From: daveols <10344370+daveols@users.noreply.github.com>
Date: Mon, 13 Jul 2020 18:18:04 +1000
Subject: [PATCH 1/5] update react-navigation test example to use v4
---
examples/__tests__/react-navigation.js | 3 ++-
package.json | 4 +++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/examples/__tests__/react-navigation.js b/examples/__tests__/react-navigation.js
index 1be2baa..9bd4b31 100644
--- a/examples/__tests__/react-navigation.js
+++ b/examples/__tests__/react-navigation.js
@@ -1,7 +1,8 @@
import '@testing-library/jest-native/extend-expect';
import React from 'react';
import { Button, Text, View } from 'react-native';
-import { createStackNavigator, createAppContainer, withNavigation } from 'react-navigation';
+import { createStackNavigator } from 'react-navigation-stack';
+import { createAppContainer, withNavigation } from 'react-navigation';
import { render, fireEvent } from '../../src';
diff --git a/package.json b/package.json
index 8a3e201..c5e7265 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,9 @@
"react-intl-native": "^2.1.2",
"react-native": "^0.61.1",
"react-native-gesture-handler": "^1.1.0",
- "react-navigation": "^3.5.1",
+ "react-native-screens": "^2.9.0",
+ "react-navigation": "^4.4.0",
+ "react-navigation-stack": "^1.7.3",
"react-redux": "^7.0.3",
"react-test-renderer": "16.9.0",
"redux": "^4.0.1",
From f4866e0645497f816899d02eb232aaf36f95f619 Mon Sep 17 00:00:00 2001
From: daveols <10344370+daveols@users.noreply.github.com>
Date: Tue, 14 Jul 2020 16:42:27 +1000
Subject: [PATCH 2/5] support react-native 0.63.0
---
examples/__tests__/react-navigation.js | 4 +-
package.json | 14 +--
src/__tests__/__snapshots__/fetch.js.snap | 4 +-
src/__tests__/events.js | 27 ++---
src/__tests__/misc.js | 122 +++++++++++++++++++++-
src/index.js | 2 +-
src/lib/__tests__/queries.find.js | 2 +-
src/lib/events.js | 5 +-
src/preset/configure.js | 1 +
src/preset/mock-component.js | 8 +-
src/preset/mock-modules.js | 64 +++++++++---
src/preset/mock-refresh-control.js | 19 ++++
src/preset/mock-scroll-view.js | 23 ++++
13 files changed, 251 insertions(+), 44 deletions(-)
create mode 100644 src/preset/mock-refresh-control.js
create mode 100644 src/preset/mock-scroll-view.js
diff --git a/examples/__tests__/react-navigation.js b/examples/__tests__/react-navigation.js
index 9bd4b31..86e0040 100644
--- a/examples/__tests__/react-navigation.js
+++ b/examples/__tests__/react-navigation.js
@@ -4,7 +4,7 @@ import { Button, Text, View } from 'react-native';
import { createStackNavigator } from 'react-navigation-stack';
import { createAppContainer, withNavigation } from 'react-navigation';
-import { render, fireEvent } from '../../src';
+import { render, fireEvent, cleanup } from '../../src';
jest
.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper')
@@ -57,6 +57,8 @@ function renderWithNavigation({ screens = {}, navigatorConfig = {} } = {}) {
return { ...render(), navigationTestRenderer: App };
}
+afterEach(cleanup);
+
test('full app rendering/navigating', async () => {
const { findByText, getByTestId, getByTitle } = renderWithNavigation();
diff --git a/package.json b/package.json
index c5e7265..ce6fb92 100644
--- a/package.json
+++ b/package.json
@@ -44,30 +44,30 @@
},
"devDependencies": {
"@babel/cli": "7.2.3",
- "@babel/core": "7.4.0",
+ "@babel/core": "7.8.4",
"@babel/runtime": "7.4.0",
- "@testing-library/jest-native": "^3.0.0",
+ "@testing-library/jest-native": "^3.1.0",
"commitizen": "^3.0.7",
"cz-conventional-changelog": "^2.1.0",
"husky": "^1.3.1",
"intl": "^1.2.5",
- "jest": "24.5.0",
+ "jest": "25.1.0",
"jest-fetch-mock": "^2.1.1",
"jest-in-case": "^1.0.2",
- "metro-react-native-babel-preset": "^0.52.0",
+ "metro-react-native-babel-preset": "^0.59.0",
"prettier": "^1.16.4",
"pretty-quick": "^1.10.0",
- "react": "16.9.0",
+ "react": "16.13.1",
"react-hooks-testing-library": "^0.5.0",
"react-intl": "^2.8.0",
"react-intl-native": "^2.1.2",
- "react-native": "^0.61.1",
+ "react-native": "^0.63.0",
"react-native-gesture-handler": "^1.1.0",
"react-native-screens": "^2.9.0",
"react-navigation": "^4.4.0",
"react-navigation-stack": "^1.7.3",
"react-redux": "^7.0.3",
- "react-test-renderer": "16.9.0",
+ "react-test-renderer": "16.13.1",
"redux": "^4.0.1",
"semantic-release": "^15.13.3",
"snapshot-diff": "0.5.1"
diff --git a/src/__tests__/__snapshots__/fetch.js.snap b/src/__tests__/__snapshots__/fetch.js.snap
index bfa83cf..13a7104 100644
--- a/src/__tests__/__snapshots__/fetch.js.snap
+++ b/src/__tests__/__snapshots__/fetch.js.snap
@@ -11,9 +11,7 @@ exports[`Fetch makes an API call and displays the greeting when load-greeting is
}
>
-
+
Fetch
diff --git a/src/__tests__/events.js b/src/__tests__/events.js
index 78dd80f..85e40f8 100644
--- a/src/__tests__/events.js
+++ b/src/__tests__/events.js
@@ -1,6 +1,17 @@
import React from 'react';
import '@testing-library/jest-native/extend-expect';
-import { Button, Image, Text, TextInput, TouchableHighlight } from 'react-native';
+import {
+ ActivityIndicator,
+ Button,
+ Image,
+ Pressable,
+ Text,
+ TextInput,
+ TouchableNativeFeedback,
+ TouchableHighlight,
+ TouchableOpacity,
+ TouchableWithoutFeedback,
+} from 'react-native';
import { render, fireEvent, eventMap, getEventHandlerName, wait, cleanup } from '../';
@@ -117,20 +128,10 @@ test('calling a handler if a Button is disabled does not work', () => {
expect(handleEvent).toBeCalledTimes(0);
});
-test('calling a handler if a Touchable is disabled does not work', () => {
- const handleEvent = jest.fn();
- const { getByText } = render(
-
- touchable
- ,
- );
- expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
- expect(handleEvent).toBeCalledTimes(0);
-});
-
test('calling an event that has no defined handler throws', () => {
const { getByText } = render(test);
- expect(() => fireEvent.press(getByText('test'))).not.toThrow();
+ const text = getByText('test');
+ expect(() => fireEvent.changeText(text).toThrow());
});
test('calling an event sets nativeEvent properly', () => {
diff --git a/src/__tests__/misc.js b/src/__tests__/misc.js
index cc59f72..c0937a6 100644
--- a/src/__tests__/misc.js
+++ b/src/__tests__/misc.js
@@ -1,11 +1,40 @@
-import React from 'react';
-import { Button, Picker, Switch, Text, View, TextInput } from 'react-native';
+import React, { useEffect, useRef } from 'react';
+import {
+ Button,
+ Picker,
+ Pressable,
+ ScrollView,
+ Switch,
+ Text,
+ TextInput,
+ TouchableHighlight,
+ TouchableNativeFeedback,
+ TouchableOpacity,
+ TouchableWithoutFeedback,
+ View,
+} from 'react-native';
import { toMatchDiffSnapshot } from 'snapshot-diff';
import { cleanup, fireEvent, render } from '../';
afterEach(cleanup);
+test(' works', () => {
+ const fireZeMissiles = jest.fn();
+
+ function Wrapper() {
+ return (
+
+ missiles
+
+ );
+ }
+ const { getByText } = render();
+
+ fireEvent.press(getByText('missiles'));
+ expect(fireZeMissiles).toBeCalledTimes(1);
+});
+
test(' works', () => {
function Wrapper() {
const [value, setValue] = React.useState('js');
@@ -23,6 +52,95 @@ test(' works', () => {
expect(() => findByDisplayValue('js')).not.toThrow();
});
+test(' instance methods are mocked', () => {
+ function Wrapper() {
+ const ref = useRef();
+
+ useEffect(() => {
+ ref.current.scrollTo(0);
+ }, []);
+
+ return (
+
+ Some content
+
+ );
+ }
+ const { getByText, debug } = render();
+
+ expect(() => getByText('Some content')).not.toThrow();
+});
+
+test(' instance methods are mocked', () => {
+ function Wrapper() {
+ const ref = useRef();
+
+ useEffect(() => {
+ ref.current.clear();
+ }, []);
+
+ return ;
+ }
+ const { getByDisplayValue } = render();
+
+ expect(() => getByDisplayValue('yo')).not.toThrow();
+});
+
+test('calling a handler if a Touchable is disabled does not work', () => {
+ const handleEvent = jest.fn();
+ const { getByText } = render(
+
+ touchable
+ ,
+ );
+ expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
+ expect(handleEvent).toBeCalledTimes(0);
+});
+
+test('calling a TouchableHighlight handler works', () => {
+ const handleEvent = jest.fn();
+ const { getByText } = render(
+
+ touchable
+ ,
+ );
+ expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
+ expect(handleEvent).toBeCalledTimes(1);
+});
+
+test('calling a TouchableNativeFeedback handler works', () => {
+ const handleEvent = jest.fn();
+ const { getByText } = render(
+
+ touchable
+ ,
+ );
+ expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
+ expect(handleEvent).toBeCalledTimes(1);
+});
+
+test('calling a TouchableOpacity handler works', () => {
+ const handleEvent = jest.fn();
+ const { getByText } = render(
+
+ touchable
+ ,
+ );
+ expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
+ expect(handleEvent).toBeCalledTimes(1);
+});
+
+test('calling a TouchableWithoutFeedback handler works ', () => {
+ const handleEvent = jest.fn();
+ const { getByText } = render(
+
+ touchable
+ ,
+ );
+ expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
+ expect(handleEvent).toBeCalledTimes(1);
+});
+
test('fragments can show diffs', () => {
function TestComponent() {
const [count, setCount] = React.useState(0);
diff --git a/src/index.js b/src/index.js
index 6f7ac4e..0b62fe3 100644
--- a/src/index.js
+++ b/src/index.js
@@ -35,7 +35,7 @@ function render(ui, { options = {}, wrapper: WrapperComponent, queries } = {}) {
renderers.add(testRenderer);
const wrappers = proxyElement(testRenderer.root).findAll(n => n.type === 'View');
- const baseElement = wrappers[0]; // Includes YellowBox and your render
+ const baseElement = wrappers[0];
const container = wrappers[1]; // Includes only your render
return {
diff --git a/src/lib/__tests__/queries.find.js b/src/lib/__tests__/queries.find.js
index f92aba8..6bbebd6 100644
--- a/src/lib/__tests__/queries.find.js
+++ b/src/lib/__tests__/queries.find.js
@@ -33,7 +33,7 @@ test('find asynchronously finds elements', async () => {
-
+
diff --git a/src/lib/events.js b/src/lib/events.js
index 6a6c495..2775443 100644
--- a/src/lib/events.js
+++ b/src/lib/events.js
@@ -28,6 +28,7 @@ const eventMap = {
Image: ['error', 'layout', 'load', 'loadEnd', 'loadStart', 'partialLoad', 'progress'],
Modal: ['dismiss', 'orientationChange', 'requestClose', 'show'],
Picker: [...viewEvents, 'valueChange'],
+ Pressable: ['longPress', 'press', 'pressIn', 'pressOut'],
RefreshControl: [...viewEvents, 'refresh'],
SafeAreaView: [...viewEvents],
ScrollView: [
@@ -108,8 +109,8 @@ function isValidTarget(element, event) {
}
function isDisabled(element) {
- const { accessibilityStates = [], disabled } = element.props;
- return disabled || accessibilityStates.includes('disabled');
+ const { accessibilityState = {}, disabled } = element.props;
+ return disabled || accessibilityState.disabled;
}
function findEventTarget(element, event) {
diff --git a/src/preset/configure.js b/src/preset/configure.js
index 30fe655..ed1594c 100644
--- a/src/preset/configure.js
+++ b/src/preset/configure.js
@@ -22,6 +22,7 @@ configureNTL({
'react-native/Libraries/Image/Image',
'react-native/Libraries/Modal/Modal',
'react-native/Libraries/Components/Picker/Picker',
+ 'react-native/Libraries/Components/Pressable/Pressable',
'react-native/Libraries/Components/RefreshControl/RefreshControl',
'react-native/Libraries/Components/SafeAreaView/SafeAreaView',
'react-native/Libraries/Components/ScrollView/ScrollView',
diff --git a/src/preset/mock-component.js b/src/preset/mock-component.js
index f675ac0..2a9937c 100644
--- a/src/preset/mock-component.js
+++ b/src/preset/mock-component.js
@@ -2,10 +2,11 @@ import React from 'react';
import { mockNativeMethods } from './mock-native-methods';
-function mockComponent(component, path = component) {
+function mockComponent(path, { instanceMethods, displayName: customDisplayName } = {}) {
const RealComponent = jest.requireActual(path);
const displayName =
+ customDisplayName ||
RealComponent.displayName ||
RealComponent.name ||
(RealComponent.render // handle React.forwardRef
@@ -19,7 +20,6 @@ function mockComponent(component, path = component) {
render() {
const props = Object.assign({}, RealComponent.defaultProps);
-
if (this.props) {
Object.keys(this.props).forEach(prop => {
// We can't just assign props on top of defaultProps
@@ -43,6 +43,10 @@ function mockComponent(component, path = component) {
Object.assign(Component.prototype, mockNativeMethods);
+ if (instanceMethods != null) {
+ Object.assign(Component.prototype, instanceMethods);
+ }
+
return Component;
}
diff --git a/src/preset/mock-modules.js b/src/preset/mock-modules.js
index 566bc78..0f0e137 100644
--- a/src/preset/mock-modules.js
+++ b/src/preset/mock-modules.js
@@ -1,6 +1,8 @@
import { getConfig } from '../lib';
import { mockComponent } from './mock-component';
-import { mockNativeMethods } from './mock-native-methods';
+
+import { mockScrollView } from './mock-scroll-view';
+import RefreshControlMock from './mock-refresh-control';
// Un-mock the react-native components so we can do it ourselves
getConfig('coreComponents').forEach(component => {
@@ -30,16 +32,54 @@ jest.doMock('react-native/Libraries/Components/Picker/Picker', () => {
return Picker;
});
-// Re-mock ReactNative with native methods mocked
-jest
- .mock('react-native/Libraries/Animated/src/NativeAnimatedHelper')
- .doMock('react-native/Libraries/Renderer/shims/ReactNative', () => {
- const ReactNative = jest.requireActual('react-native/Libraries/Renderer/shims/ReactNative');
- const NativeMethodsMixin =
- ReactNative.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.NativeMethodsMixin;
+// Mock some other tricky ones
+jest.doMock('react-native/Libraries/Components/ScrollView/ScrollView', () => {
+ const baseComponent = mockComponent('react-native/Libraries/Components/ScrollView/ScrollView', {
+ instanceMethods: {
+ getScrollResponder: jest.fn(),
+ getScrollableNode: jest.fn(),
+ getInnerViewNode: jest.fn(),
+ getInnerViewRef: jest.fn(),
+ getNativeScrollRef: jest.fn(),
+ scrollTo: jest.fn(),
+ scrollToEnd: jest.fn(),
+ flashScrollIndicators: jest.fn(),
+ scrollResponderZoomTo: jest.fn(),
+ scrollResponderScrollNativeHandleToKeyboard: jest.fn(),
+ },
+ });
+ return mockScrollView(baseComponent);
+});
- Object.assign(NativeMethodsMixin, mockNativeMethods);
- Object.assign(ReactNative.NativeComponent.prototype, mockNativeMethods);
+jest.doMock('react-native/Libraries/Components/RefreshControl/RefreshControl', () =>
+ jest.requireActual('./mock-refresh-control'),
+);
- return ReactNative;
- });
+jest.doMock('react-native/Libraries/Components/TextInput/TextInput', () =>
+ mockComponent('react-native/Libraries/Components/TextInput/TextInput', {
+ instanceMethods: {
+ isFocused: jest.fn(),
+ clear: jest.fn(),
+ getNativeRef: jest.fn(),
+ },
+ }),
+);
+
+jest.doMock('react-native/Libraries/Components/Touchable/TouchableHighlight', () =>
+ mockComponent('react-native/Libraries/Components/Touchable/TouchableHighlight', {
+ displayName: 'TouchableHighlight',
+ }),
+);
+
+jest.doMock('react-native/Libraries/Components/Touchable/TouchableOpacity', () =>
+ mockComponent('react-native/Libraries/Components/Touchable/TouchableOpacity', {
+ displayName: 'TouchableOpacity',
+ }),
+);
+
+// Re-mock ReactNative
+jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
+jest.mock('react-native/Libraries/Renderer/shims/ReactNative');
+
+// Mock LogBox
+jest.mock('react-native/Libraries/LogBox/LogBox');
diff --git a/src/preset/mock-refresh-control.js b/src/preset/mock-refresh-control.js
new file mode 100644
index 0000000..a40eff8
--- /dev/null
+++ b/src/preset/mock-refresh-control.js
@@ -0,0 +1,19 @@
+const React = require('react');
+
+const requireNativeComponent = jest.requireActual(
+ 'react-native/Libraries/ReactNative/requireNativeComponent',
+);
+const RCTRefreshControl = requireNativeComponent('RCTRefreshControl');
+
+class RefreshControlMock extends React.Component {
+ static latestRef;
+
+ componentDidMount() {
+ RefreshControlMock.latestRef = this;
+ }
+ render() {
+ return ;
+ }
+}
+
+module.exports = RefreshControlMock;
diff --git a/src/preset/mock-scroll-view.js b/src/preset/mock-scroll-view.js
new file mode 100644
index 0000000..0d35f29
--- /dev/null
+++ b/src/preset/mock-scroll-view.js
@@ -0,0 +1,23 @@
+import React from 'react';
+const View = require('react-native/Libraries/Components/View/View');
+
+const requireNativeComponent = jest.requireActual(
+ 'react-native/Libraries/ReactNative/requireNativeComponent',
+);
+const RCTScrollView = requireNativeComponent('RCTScrollView');
+
+function mockScrollView(BaseComponent) {
+ class ScrollViewMock extends BaseComponent {
+ render() {
+ return (
+
+ {this.props.refreshControl}
+ {this.props.children}
+
+ );
+ }
+ }
+ return ScrollViewMock;
+}
+
+export { mockScrollView };
From 3ce8ef8a90daacb3aada752680cf6a7b797118b2 Mon Sep 17 00:00:00 2001
From: daveols <10344370+daveols@users.noreply.github.com>
Date: Tue, 14 Jul 2020 17:00:00 +1000
Subject: [PATCH 3/5] fix RefreshControl mock
---
src/preset/mock-modules.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/preset/mock-modules.js b/src/preset/mock-modules.js
index 0f0e137..f391334 100644
--- a/src/preset/mock-modules.js
+++ b/src/preset/mock-modules.js
@@ -51,8 +51,9 @@ jest.doMock('react-native/Libraries/Components/ScrollView/ScrollView', () => {
return mockScrollView(baseComponent);
});
-jest.doMock('react-native/Libraries/Components/RefreshControl/RefreshControl', () =>
- jest.requireActual('./mock-refresh-control'),
+jest.doMock(
+ 'react-native/Libraries/Components/RefreshControl/RefreshControl',
+ () => RefreshControlMock,
);
jest.doMock('react-native/Libraries/Components/TextInput/TextInput', () =>
From 0d26f9949af7ce2c00cf22303797fbbdbcb46015 Mon Sep 17 00:00:00 2001
From: daveols <10344370+daveols@users.noreply.github.com>
Date: Tue, 14 Jul 2020 17:08:32 +1000
Subject: [PATCH 4/5] lint
---
src/__tests__/events.js | 13 +------------
1 file changed, 1 insertion(+), 12 deletions(-)
diff --git a/src/__tests__/events.js b/src/__tests__/events.js
index 85e40f8..7368d58 100644
--- a/src/__tests__/events.js
+++ b/src/__tests__/events.js
@@ -1,17 +1,6 @@
import React from 'react';
import '@testing-library/jest-native/extend-expect';
-import {
- ActivityIndicator,
- Button,
- Image,
- Pressable,
- Text,
- TextInput,
- TouchableNativeFeedback,
- TouchableHighlight,
- TouchableOpacity,
- TouchableWithoutFeedback,
-} from 'react-native';
+import { Button, Image, Text, TextInput } from 'react-native';
import { render, fireEvent, eventMap, getEventHandlerName, wait, cleanup } from '../';
From 82ac4a67555d053947e773c58e64abc504b76a9a Mon Sep 17 00:00:00 2001
From: daveols <10344370+daveols@users.noreply.github.com>
Date: Wed, 15 Jul 2020 11:23:18 +1000
Subject: [PATCH 5/5] add back disabled touchable test
---
src/__tests__/events.js | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/__tests__/events.js b/src/__tests__/events.js
index 7368d58..93b7afa 100644
--- a/src/__tests__/events.js
+++ b/src/__tests__/events.js
@@ -1,6 +1,6 @@
import React from 'react';
import '@testing-library/jest-native/extend-expect';
-import { Button, Image, Text, TextInput } from 'react-native';
+import { Button, Image, Text, TextInput, TouchableHighlight } from 'react-native';
import { render, fireEvent, eventMap, getEventHandlerName, wait, cleanup } from '../';
@@ -117,6 +117,17 @@ test('calling a handler if a Button is disabled does not work', () => {
expect(handleEvent).toBeCalledTimes(0);
});
+test('calling a handler if a Touchable is disabled does not work', () => {
+ const handleEvent = jest.fn();
+ const { getByText } = render(
+
+ touchable
+ ,
+ );
+ fireEvent.press(getByText('touchable'));
+ expect(handleEvent).toBeCalledTimes(0);
+});
+
test('calling an event that has no defined handler throws', () => {
const { getByText } = render(test);
const text = getByText('test');