Skip to content
This repository was archived by the owner on Jul 30, 2020. It is now read-only.

Support React Native 0.63.x #136

Merged
merged 5 commits into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions examples/__tests__/react-navigation.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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';
import { render, fireEvent, cleanup } from '../../src';

jest
.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper')
Expand Down Expand Up @@ -56,6 +57,8 @@ function renderWithNavigation({ screens = {}, navigatorConfig = {} } = {}) {
return { ...render(<App detached />), navigationTestRenderer: App };
}

afterEach(cleanup);

test('full app rendering/navigating', async () => {
const { findByText, getByTestId, getByTitle } = renderWithNavigation();

Expand Down
18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +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-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",
"react-test-renderer": "16.13.1",
"redux": "^4.0.1",
"semantic-release": "^15.13.3",
"snapshot-diff": "0.5.1"
Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/__snapshots__/fetch.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ exports[`Fetch makes an API call and displays the greeting when load-greeting is
}
>
<View>
<TouchableOpacity
activeOpacity={0.2}
>
<TouchableOpacity>
<Text>
Fetch
</Text>
Expand Down
16 changes: 3 additions & 13 deletions src/__tests__/events.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import '@testing-library/jest-native/extend-expect';
import { Button, Image, Text, TextInput, TouchableHighlight } from 'react-native';
import { Button, Image, Text, TextInput } from 'react-native';

import { render, fireEvent, eventMap, getEventHandlerName, wait, cleanup } from '../';

Expand Down Expand Up @@ -117,20 +117,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(
<TouchableHighlight disabled onPress={jest.fn()}>
<Text>touchable</Text>
</TouchableHighlight>,
);
expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
expect(handleEvent).toBeCalledTimes(0);
});

test('calling an event that has no defined handler throws', () => {
const { getByText } = render(<Text>test</Text>);
expect(() => fireEvent.press(getByText('test'))).not.toThrow();
const text = getByText('test');
expect(() => fireEvent.changeText(text).toThrow());
});

test('calling an event sets nativeEvent properly', () => {
Expand Down
122 changes: 120 additions & 2 deletions src/__tests__/misc.js
Original file line number Diff line number Diff line change
@@ -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('<Pressable /> works', () => {
const fireZeMissiles = jest.fn();

function Wrapper() {
return (
<Pressable onPress={fireZeMissiles}>
<Text>missiles</Text>
</Pressable>
);
}
const { getByText } = render(<Wrapper />);

fireEvent.press(getByText('missiles'));
expect(fireZeMissiles).toBeCalledTimes(1);
});

test('<Picker /> works', () => {
function Wrapper() {
const [value, setValue] = React.useState('js');
Expand All @@ -23,6 +52,95 @@ test('<Picker /> works', () => {
expect(() => findByDisplayValue('js')).not.toThrow();
});

test('<ScrollView /> instance methods are mocked', () => {
function Wrapper() {
const ref = useRef();

useEffect(() => {
ref.current.scrollTo(0);
}, []);

return (
<ScrollView ref={ref}>
<Text>Some content</Text>
</ScrollView>
);
}
const { getByText, debug } = render(<Wrapper />);

expect(() => getByText('Some content')).not.toThrow();
});

test('<TextInput /> instance methods are mocked', () => {
function Wrapper() {
const ref = useRef();

useEffect(() => {
ref.current.clear();
}, []);

return <TextInput ref={ref} value={'yo'} />;
}
const { getByDisplayValue } = render(<Wrapper />);

expect(() => getByDisplayValue('yo')).not.toThrow();
});

test('calling a handler if a Touchable is disabled does not work', () => {
const handleEvent = jest.fn();
const { getByText } = render(
<Pressable disabled onPress={handleEvent}>
<Text>touchable</Text>
</Pressable>,
);
expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
expect(handleEvent).toBeCalledTimes(0);
});

test('calling a TouchableHighlight handler works', () => {
const handleEvent = jest.fn();
const { getByText } = render(
<TouchableHighlight onPress={handleEvent}>
<Text>touchable</Text>
</TouchableHighlight>,
);
expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
expect(handleEvent).toBeCalledTimes(1);
});

test('calling a TouchableNativeFeedback handler works', () => {
const handleEvent = jest.fn();
const { getByText } = render(
<TouchableNativeFeedback onPress={handleEvent}>
<Text>touchable</Text>
</TouchableNativeFeedback>,
);
expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
expect(handleEvent).toBeCalledTimes(1);
});

test('calling a TouchableOpacity handler works', () => {
const handleEvent = jest.fn();
const { getByText } = render(
<TouchableOpacity onPress={handleEvent}>
<Text>touchable</Text>
</TouchableOpacity>,
);
expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
expect(handleEvent).toBeCalledTimes(1);
});

test('calling a TouchableWithoutFeedback handler works ', () => {
const handleEvent = jest.fn();
const { getByText } = render(
<TouchableWithoutFeedback onPress={handleEvent}>
<Text>touchable</Text>
</TouchableWithoutFeedback>,
);
expect(() => fireEvent.press(getByText('touchable'))).not.toThrow();
expect(handleEvent).toBeCalledTimes(1);
});

test('fragments can show diffs', () => {
function TestComponent() {
const [count, setCount] = React.useState(0);
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/__tests__/queries.find.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test('find asynchronously finds elements', async () => {
<Button title="button" />
<TextInput placeholder="placeholder" />
<TextInput value="value" />
<TextInput accessibilityStates={['disabled']} />
<TextInput accessibilityState={{ disabled: false }} />
<Image accessibilityLabel="test-label" src="/lucy-ricardo.png" />
<Image accessibilityHint="test-hint" src="/lucy-ricardo.png" />
<View accessibilityRole="dialog" />
Expand Down
5 changes: 3 additions & 2 deletions src/lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/preset/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
8 changes: 6 additions & 2 deletions src/preset/mock-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -43,6 +43,10 @@ function mockComponent(component, path = component) {

Object.assign(Component.prototype, mockNativeMethods);

if (instanceMethods != null) {
Object.assign(Component.prototype, instanceMethods);
}

return Component;
}

Expand Down
65 changes: 53 additions & 12 deletions src/preset/mock-modules.js
Original file line number Diff line number Diff line change
@@ -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 => {
Expand Down Expand Up @@ -30,16 +32,55 @@ 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',
() => RefreshControlMock,
);

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');
Loading