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

Commit

Permalink
feat: include jest serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
bcarroll22 committed May 12, 2019
1 parent c837fc5 commit c5364a8
Show file tree
Hide file tree
Showing 38 changed files with 263 additions and 283 deletions.
1 change: 0 additions & 1 deletion cleanup-after-each.js

This file was deleted.

1 change: 1 addition & 0 deletions jest-preset.js
Expand Up @@ -5,5 +5,6 @@ module.exports = Object.assign(jestPreset, {
...jestPreset.transformIgnorePatterns,
'node_modules/(?!(react-native.*|@?react-navigation.*)/)',
],
snapshotSerializers: [require.resolve('./dist/preset/serializer.js')],
setupFiles: [...jestPreset.setupFiles, require.resolve('./dist/preset/setup.js')],
});
1 change: 1 addition & 0 deletions jest.config.js
Expand Up @@ -4,6 +4,7 @@ const ignores = ['/node_modules/', '/__tests__/helpers/', '__mocks__'];

module.exports = Object.assign(jestPreset, {
collectCoverageFrom: ['**/src/lib/**/*.js', '!**/src/preset/**/*.js'],
snapshotSerializers: [require.resolve('./src/preset/serializer.js')],
setupFiles: [...jestPreset.setupFiles, require.resolve('./src/preset/setup.js')],
testPathIgnorePatterns: [...ignores],
transformIgnorePatterns: ['node_modules/(?!(react-native.*|@?react-navigation.*)/)'],
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -7,7 +7,6 @@
"files": [
"dist",
"typings",
"cleanup-after-each.js",
"jest-preset.js"
],
"engines": {
Expand Down
8 changes: 7 additions & 1 deletion src/__tests__/__snapshots__/fetch.js.snap
Expand Up @@ -2,7 +2,13 @@

exports[`Fetch makes an API call and displays the greeting when load-greeting is clicked 1`] = `
<View
testID="ntl-container"
collapsable={true}
pointerEvents="box-none"
style={
Object {
"flex": 1,
}
}
>
<View>
<TouchableOpacity
Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/act.js
Expand Up @@ -2,9 +2,7 @@ import React from 'react';
import 'jest-native/extend-expect';
import { Button } from 'react-native';

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

afterEach(cleanup);
import { render, fireEvent } from '../';

test('render calls useEffect immediately', () => {
const effectCb = jest.fn();
Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/bugs.js
@@ -1,9 +1,7 @@
import React from 'react';
import { Text, View } from 'react-native';

import { cleanup, render, queryAllByProp } from '../';

afterEach(cleanup);
import { render, queryAllByProp } from '../';

// This is to ensure custom queries can be passed to render. In most cases, you
// wouldn't/shouldn't need to do this, but we do allow it so we'll test to
Expand Down
6 changes: 2 additions & 4 deletions src/__tests__/debug.js
@@ -1,9 +1,7 @@
import React from 'react';
import { Text } from 'react-native';

import { cleanup, render } from '../';

afterEach(cleanup);
import { render } from '../';

beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => {});
Expand All @@ -13,7 +11,7 @@ afterEach(() => {
console.log.mockRestore();
});

test('debug pretty prints the testRenderer', () => {
test('debug pretty prints the baseElement', () => {
const HelloWorld = () => <Text>Hello World</Text>;
const { debug } = render(<HelloWorld />);
debug();
Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/end-to-end.js
Expand Up @@ -2,9 +2,7 @@ import React from 'react';
import 'jest-native/extend-expect';
import { Text } from 'react-native';

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

afterEach(cleanup);
import { render, wait } from '../';

const fetchAMessage = () =>
new Promise(resolve => {
Expand Down
38 changes: 23 additions & 15 deletions src/__tests__/events.js
Expand Up @@ -2,9 +2,7 @@ import React from 'react';
import 'jest-native/extend-expect';
import { Button, Image, Text, TextInput, TouchableHighlight } from 'react-native';

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

afterEach(cleanup);
import { render, fireEvent, eventMap, NativeEvent, getEventHandlerName, wait } from '../';

Object.keys(eventMap).forEach(key => {
const handlerName = getEventHandlerName(key);
Expand All @@ -15,14 +13,17 @@ Object.keys(eventMap).forEach(key => {
config.validTargets.forEach(element => {
const spy = jest.fn();

const { getByTestId } = render(
const {
container: {
children: [button],
},
} = render(
React.createElement(element, {
[handlerName]: spy,
}),
);

const target = getByTestId('ntl-container').children[0];
fireEvent[key](target);
fireEvent[key](button);

expect(spy).toHaveBeenCalledTimes(1);
});
Expand All @@ -31,9 +32,13 @@ Object.keys(eventMap).forEach(key => {

test('onChange works', () => {
const handleChange = jest.fn();
const { getByTestId } = render(<TextInput onChange={handleChange} />);
const target = getByTestId('ntl-container').children[0];
fireEvent.change(target, { target: { value: 'a' } });
const {
container: {
children: [input],
},
} = render(<TextInput onChange={handleChange} />);

fireEvent.change(input, { target: { value: 'a' } });
expect(handleChange).toHaveBeenCalledTimes(1);
});

Expand Down Expand Up @@ -75,10 +80,9 @@ test('assigns target properties', async () => {

test('calling `fireEvent` directly works too', () => {
const handleEvent = jest.fn();
const { getByTestId } = render(<Button onPress={handleEvent} title="test" />);
const { container } = render(<Button onPress={handleEvent} title="test" />);

const target = getByTestId('ntl-container').children[0];
fireEvent(target, new NativeEvent('press'));
fireEvent(container.children[0], new NativeEvent('press'));
expect(handleEvent).toBeCalledTimes(1);
});

Expand All @@ -87,9 +91,13 @@ test('calling a custom event works as well', () => {
const onMyEvent = jest.fn(({ nativeEvent }) => expect(nativeEvent).toEqual({ value: 'testing' }));
const MyComponent = ({ onMyEvent }) => <TextInput value="test" onChange={onMyEvent} />;

const { getByTestId } = render(<MyComponent onMyEvent={onMyEvent} />);
const target = getByTestId('ntl-container').children[0];
fireEvent(target, new NativeEvent('myEvent', event));
const {
container: {
children: [input],
},
} = render(<MyComponent onMyEvent={onMyEvent} />);

fireEvent(input, new NativeEvent('myEvent', event));

expect(onMyEvent).toHaveBeenCalledWith({
nativeEvent: { value: 'testing' },
Expand Down
6 changes: 2 additions & 4 deletions src/__tests__/fetch.js
Expand Up @@ -2,9 +2,7 @@ import React from 'react';
import 'jest-native/extend-expect';
import { TouchableOpacity, Text, View } from 'react-native';

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

afterEach(cleanup);
import { render, fireEvent, wait } from '../';

global.fetch = require('jest-fetch-mock');

Expand Down Expand Up @@ -46,5 +44,5 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl
expect(fetch).toHaveBeenCalledWith(url);

expect(getByText('hello there')).toHaveTextContent('hello there');
expect(toJSON(container)).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
4 changes: 1 addition & 3 deletions src/__tests__/forms.js
@@ -1,8 +1,6 @@
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { cleanup, render, fireEvent } from '../';

afterEach(cleanup);
import { render, fireEvent } from '../';

function Login({ onSubmit, user }) {
return (
Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/misc.js
@@ -1,9 +1,7 @@
import React from 'react';
import { Picker } from 'react-native';

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

afterEach(cleanup);
import { fireEvent, render } from '../';

test('picker works', () => {
function Wrapper() {
Expand Down
14 changes: 9 additions & 5 deletions src/__tests__/render.js
@@ -1,9 +1,7 @@
import React from 'react';
import { SafeAreaView, View } from 'react-native';

import { cleanup, toJSON, render } from '../';

afterEach(cleanup);
import { render } from '../';

test('renders View', () => {
const { container } = render(<View />);
Expand All @@ -25,9 +23,15 @@ test('renders options.wrapper around node', () => {
});

expect(getByTestId('wrapper')).toBeTruthy();
expect(toJSON(container)).toMatchInlineSnapshot(`
expect(container).toMatchInlineSnapshot(`
<View
testID="ntl-container"
collapsable={true}
pointerEvents="box-none"
style={
Object {
"flex": 1,
}
}
>
<SafeAreaView
testID="wrapper"
Expand Down
4 changes: 1 addition & 3 deletions src/__tests__/rerender.js
Expand Up @@ -2,9 +2,7 @@ import React from 'react';
import 'jest-native/extend-expect';
import { Text } from 'react-native';

import { cleanup, render } from '../';

afterEach(cleanup);
import { render } from '../';

test('rerender will re-render the element', () => {
const Greeting = props => <Text>{props.message}</Text>;
Expand Down
10 changes: 5 additions & 5 deletions src/__tests__/stopwatch.js
@@ -1,9 +1,7 @@
import React from 'react';
import { Button, Text, View } from 'react-native';

import { cleanup, render, fireEvent, toJSON } from '../';

afterEach(cleanup);
import { render, fireEvent, prettyPrint } from '../';

class StopWatch extends React.Component {
state = { lapse: 0, running: false };
Expand Down Expand Up @@ -47,7 +45,9 @@ test('unmounts a component', async () => {
fireEvent.press(getByTitle('Start'));

unmount();

expect(toJSON(container)).toBeNull();
// hey there reader! You don't need to have an assertion like this one
// this is just me making sure that the unmount function works.
// You don't need to do this in your apps. Just rely on the fact that this works.
expect(prettyPrint(container)).toBe('null');
await wait(() => expect(console.error).not.toHaveBeenCalled());
});
41 changes: 10 additions & 31 deletions src/index.js
@@ -1,34 +1,25 @@
import React from 'react';
import { View } from 'react-native';
import TR from 'react-test-renderer';
import AppContainer from 'react-native/Libraries/ReactNative/AppContainer';

import {
toJSON,
fireEvent as rntlFireEvent,
getQueriesForElement,
NativeEvent,
prettyPrint,
proxyUnsafeProperties,
validComponentFilter,
proxyUnsafeProperties as proxy,
} from './lib';
import './preset/serializer';
import act from './act-compat';

const containerId = 'ntl-container';
const renderers = new Set();

function render(ui, { options = {}, wrapper: WrapperComponent, queries } = {}) {
const wrapUiIfNeeded = innerElement =>
WrapperComponent ? (
<AppContainer>
<View testID={containerId}>
<WrapperComponent>{innerElement}</WrapperComponent>
</View>
<WrapperComponent>{innerElement}</WrapperComponent>
</AppContainer>
) : (
<AppContainer>
<View testID={containerId}>{innerElement}</View>
</AppContainer>
<AppContainer>{innerElement}</AppContainer>
);

let testRenderer;
Expand All @@ -37,36 +28,24 @@ function render(ui, { options = {}, wrapper: WrapperComponent, queries } = {}) {
testRenderer = TR.create(wrapUiIfNeeded(ui), options);
});

renderers.add(testRenderer);

const baseElement = proxyUnsafeProperties(testRenderer.root);
const container = baseElement
.findAll(c => validComponentFilter(c))
.filter(n => n.getProp('testID') === containerId)[0];
const wrappers = proxy(testRenderer.root).findAll(n => n.getProp('pointerEvents') === 'box-none');
const baseElement = wrappers[0]; // Includes YellowBox and your render
const container = wrappers[1]; // Includes only your render

return {
baseElement,
container,
debug: (el = baseElement) => console.log(prettyPrint(toJSON(el))),
debug: (el = baseElement) => console.log(prettyPrint(el)),
unmount: () => testRenderer.unmount(),
rerender: rerenderUi => {
act(() => {
testRenderer.update(wrapUiIfNeeded(rerenderUi));
});
},
...getQueriesForElement(testRenderer, queries),
...getQueriesForElement(baseElement, queries),
};
}

function cleanup() {
renderers.forEach(cleanupRenderer);
}

function cleanupRenderer(renderer) {
renderer.unmount();
renderers.delete(renderer);
}

function fireEvent(...args) {
let returnValue;
act(() => {
Expand All @@ -86,4 +65,4 @@ Object.keys(rntlFireEvent).forEach(key => {
});

export * from './lib';
export { act, cleanup, fireEvent, render, NativeEvent };
export { act, fireEvent, render, NativeEvent };

0 comments on commit c5364a8

Please sign in to comment.