Skip to content

feat: add debug to render helpers for easier console debugging #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,33 @@ Unmount the in-memory tree, triggering the appropriate lifecycle events

When using React context providers, like Redux Provider, you'll likely want to wrap rendered component with them. In such cases it's convenient to create your custom `render` method. [Follow this great guide on how to set this up](https://github.com/kentcdodds/react-testing-library#custom-render).

### `debug: (message?: string) => void`

Prints deeply rendered component passed to `render` with optional message on top. Uses [debug.deep](#debug) under the hood, but it's easier to use.

```jsx
const { debug } = render(<Component />);

debug('optional message');
```

logs optional message and colored JSX:

```jsx
optional message

<TouchableOpacity
activeOpacity={0.2}
onPress={[Function bound fn]}
>
<Text>Press me</Text>
</TouchableOpacity>
```

### `debug.shallow: (message?: string) => void`

Prints shallowly rendered component passed to `render` with optional message on top. Uses [debug.shallow](#debug) under the hood, but it's easier to use.

### `toJSON: () => ?ReactTestRendererJSON`

Get the rendered component JSON representation, e.g. for snapshot testing.
Expand Down
138 changes: 138 additions & 0 deletions src/__tests__/__snapshots__/render.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,143 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`debug 1`] = `
"<View>
<Text>
Is the banana fresh?
</Text>
<Text
testID=\\"bananaFresh\\"
>
not fresh
</Text>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function bound touchableHandleResponderGrant]}
onResponderMove={[Function bound touchableHandleResponderMove]}
onResponderRelease={[Function bound touchableHandleResponderRelease]}
onResponderTerminate={[Function bound touchableHandleResponderTerminate]}
onResponderTerminationRequest={[Function bound touchableHandleResponderTerminationRequest]}
onStartShouldSetResponder={[Function bound touchableHandleStartShouldSetResponder]}
style={
Object {
\\"opacity\\": 1,
}
}
>
<Text>
Change freshness!
</Text>
</View>
</View>"
`;

exports[`debug changing component: bananaFresh button message should now be "fresh" 1`] = `
"<View>
<Text>
Is the banana fresh?
</Text>
<Text
testID=\\"bananaFresh\\"
>
fresh
</Text>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function bound touchableHandleResponderGrant]}
onResponderMove={[Function bound touchableHandleResponderMove]}
onResponderRelease={[Function bound touchableHandleResponderRelease]}
onResponderTerminate={[Function bound touchableHandleResponderTerminate]}
onResponderTerminationRequest={[Function bound touchableHandleResponderTerminationRequest]}
onStartShouldSetResponder={[Function bound touchableHandleStartShouldSetResponder]}
style={
Object {
\\"opacity\\": 1,
}
}
>
<Text>
Change freshness!
</Text>
</View>
</View>"
`;

exports[`debug: shallow 1`] = `
"<View>
<Text>
Is the banana fresh?
</Text>
<Text
testID=\\"bananaFresh\\"
>
not fresh
</Text>
<Button
onPress={[Function anonymous]}
type=\\"primary\\"
>
Change freshness!
</Button>
</View>"
`;

exports[`debug: shallow with message 1`] = `
"my other custom message

<View>
<Text>
Is the banana fresh?
</Text>
<Text
testID=\\"bananaFresh\\"
>
not fresh
</Text>
<Button
onPress={[Function anonymous]}
type=\\"primary\\"
>
Change freshness!
</Button>
</View>"
`;

exports[`debug: with message 1`] = `
"my custom message

<View>
<Text>
Is the banana fresh?
</Text>
<Text
testID=\\"bananaFresh\\"
>
not fresh
</Text>
<View
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function bound touchableHandleResponderGrant]}
onResponderMove={[Function bound touchableHandleResponderMove]}
onResponderRelease={[Function bound touchableHandleResponderRelease]}
onResponderTerminate={[Function bound touchableHandleResponderTerminate]}
onResponderTerminationRequest={[Function bound touchableHandleResponderTerminationRequest]}
onStartShouldSetResponder={[Function bound touchableHandleStartShouldSetResponder]}
style={
Object {
\\"opacity\\": 1,
}
}
>
<Text>
Change freshness!
</Text>
</View>
</View>"
`;

exports[`toJSON 1`] = `
<View
accessible={true}
Expand Down
7 changes: 4 additions & 3 deletions src/__tests__/debug.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { TouchableOpacity, Text } from 'react-native';
import stripAnsi from 'strip-ansi';
import { debug, render, fireEvent, flushMicrotasksQueue } from '..';
import debugShallow from '../helpers/debugShallow';

function TextComponent({ text }) {
return <Text>{text}</Text>;
Expand Down Expand Up @@ -43,11 +44,11 @@ test('debug', () => {

debug(component, 'test message');

expect(console.log).toBeCalledWith(output, 'test message');
expect(console.log).toBeCalledWith('test message\n\n', output);
});

test('debug.shallow', () => {
expect(debug.shallow).toBe(debug);
expect(debug.shallow).toBe(debugShallow);
});

test('debug.deep', () => {
Expand All @@ -65,7 +66,7 @@ test('debug.deep', () => {

debug.deep(component, 'test message');

expect(console.log).toBeCalledWith(output, 'test message');
expect(console.log).toBeCalledWith('test message\n\n', output);
});

test('debug.deep async test', async () => {
Expand Down
43 changes: 42 additions & 1 deletion src/__tests__/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { render } from '..';
import stripAnsi from 'strip-ansi';
import { render, fireEvent } from '..';

class Button extends React.Component<*> {
render() {
Expand Down Expand Up @@ -172,3 +173,43 @@ test('toJSON', () => {

expect(toJSON()).toMatchSnapshot();
});

test('debug', () => {
jest.spyOn(console, 'log').mockImplementation(x => x);

const { debug } = render(<Banana />);

debug();
debug('my custom message');
debug.shallow();
debug.shallow('my other custom message');

// eslint-disable-next-line no-console
const mockCalls = console.log.mock.calls;

expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
expect(stripAnsi(mockCalls[1][0] + mockCalls[1][1])).toMatchSnapshot(
'with message'
);
expect(stripAnsi(mockCalls[2][0])).toMatchSnapshot('shallow');
expect(stripAnsi(mockCalls[3][0] + mockCalls[3][1])).toMatchSnapshot(
'shallow with message'
);
});

test('debug changing component', () => {
jest.spyOn(console, 'log').mockImplementation(x => x);

const { getByProps, debug } = render(<Banana />);

fireEvent.press(getByProps({ type: 'primary' }));

debug();

// eslint-disable-next-line no-console
const mockCalls = console.log.mock.calls;

expect(stripAnsi(mockCalls[4][0])).toMatchSnapshot(
'bananaFresh button message should now be "fresh"'
);
});
40 changes: 15 additions & 25 deletions src/debug.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
// @flow
/* eslint-disable no-console */
import * as React from 'react';
import prettyFormat, { plugins } from 'pretty-format';
import shallow from './shallow';
import render from './render';

/**
* Log pretty-printed shallow test component instance
*/
function debugShallow(
instance: ReactTestInstance | React.Element<*>,
message?: any
) {
const { output } = shallow(instance);

console.log(format(output), message || '');
}
import debugShallow from './helpers/debugShallow';
import debugDeep from './helpers/debugDeep';
import format from './helpers/format';

/**
* Log pretty-printed deep test component instance
*/
function debugDeep(
function debugDeepElementOrInstance(
instance: React.Element<*> | ?ReactTestRendererJSON,
message?: any = ''
) {
Expand All @@ -29,21 +18,22 @@ function debugDeep(
// rendering ?ReactTestRendererJSON
// $FlowFixMe
const { toJSON } = render(instance);
console.log(format(toJSON()), message);
if (message) {
console.log(`${message}\n\n`, format(toJSON()));
} else {
console.log(format(toJSON()));
}
} catch (e) {
console.log(format(instance), message);
// $FlowFixMe
debugDeep(instance);
}
}

const format = input =>
prettyFormat(input, {
plugins: [plugins.ReactTestComponent, plugins.ReactElement],
highlight: true,
});

const debug = debugShallow;
function debug(instance: ReactTestInstance | React.Element<*>, message?: any) {
return debugShallow(instance, message);
}

debug.shallow = debugShallow;
debug.deep = debugDeep;
debug.deep = debugDeepElementOrInstance;

export default debug;
16 changes: 16 additions & 0 deletions src/helpers/debugDeep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @flow
import format from './format';

/**
* Log pretty-printed deep test component instance
*/
export default function debugDeep(
instance: ?ReactTestRendererJSON,
message?: any = ''
) {
if (message) {
console.log(`${message}\n\n`, format(instance));
} else {
console.log(format(instance));
}
}
20 changes: 20 additions & 0 deletions src/helpers/debugShallow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @flow
import * as React from 'react';
import shallow from '../shallow';
import format from './format';

/**
* Log pretty-printed shallow test component instance
*/
export default function debugShallow(
instance: ReactTestInstance | React.Element<*>,
message?: any
) {
const { output } = shallow(instance);

if (message) {
console.log(`${message}\n\n`, format(output));
} else {
console.log(format(output));
}
}
10 changes: 10 additions & 0 deletions src/helpers/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow
import prettyFormat, { plugins } from 'pretty-format';

const format = (input: ?ReactTestRendererJSON) =>
prettyFormat(input, {
plugins: [plugins.ReactTestComponent, plugins.ReactElement],
highlight: true,
});

export default format;
11 changes: 11 additions & 0 deletions src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as React from 'react';
import TestRenderer from 'react-test-renderer'; // eslint-disable-line import/no-extraneous-dependencies
import { getByAPI } from './helpers/getByAPI';
import { queryByAPI } from './helpers/queryByAPI';
import debugShallow from './helpers/debugShallow';
import debugDeep from './helpers/debugDeep';

/**
* Renders test component deeply using react-test-renderer and exposes helpers
Expand All @@ -21,5 +23,14 @@ export default function render(
update: renderer.update,
unmount: renderer.unmount,
toJSON: renderer.toJSON,
debug: debug(instance, renderer),
};
}

function debug(instance: ReactTestInstance, renderer) {
function debugImpl(message?: string) {
return debugDeep(renderer.toJSON(), message);
}
debugImpl.shallow = message => debugShallow(instance, message);
return debugImpl;
}
Loading