Skip to content

Commit 909916e

Browse files
thymikeeEsemesek
authored andcommitted
feat: add debug to render helpers for easier console debugging (#65)
* feat: add debug to render helpers for easier console debugging * bring back toJSON to docs * revert unnecessary change * update TS * remove cyclic dependency of debug <-> render
1 parent fa198c4 commit 909916e

File tree

11 files changed

+286
-29
lines changed

11 files changed

+286
-29
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,33 @@ Unmount the in-memory tree, triggering the appropriate lifecycle events
147147

148148
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).
149149

150+
### `debug: (message?: string) => void`
151+
152+
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.
153+
154+
```jsx
155+
const { debug } = render(<Component />);
156+
157+
debug('optional message');
158+
```
159+
160+
logs optional message and colored JSX:
161+
162+
```jsx
163+
optional message
164+
165+
<TouchableOpacity
166+
activeOpacity={0.2}
167+
onPress={[Function bound fn]}
168+
>
169+
<Text>Press me</Text>
170+
</TouchableOpacity>
171+
```
172+
173+
### `debug.shallow: (message?: string) => void`
174+
175+
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.
176+
150177
### `toJSON: () => ?ReactTestRendererJSON`
151178

152179
Get the rendered component JSON representation, e.g. for snapshot testing.

src/__tests__/__snapshots__/render.test.js.snap

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,143 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`debug 1`] = `
4+
"<View>
5+
<Text>
6+
Is the banana fresh?
7+
</Text>
8+
<Text
9+
testID=\\"bananaFresh\\"
10+
>
11+
not fresh
12+
</Text>
13+
<View
14+
accessible={true}
15+
isTVSelectable={true}
16+
onResponderGrant={[Function bound touchableHandleResponderGrant]}
17+
onResponderMove={[Function bound touchableHandleResponderMove]}
18+
onResponderRelease={[Function bound touchableHandleResponderRelease]}
19+
onResponderTerminate={[Function bound touchableHandleResponderTerminate]}
20+
onResponderTerminationRequest={[Function bound touchableHandleResponderTerminationRequest]}
21+
onStartShouldSetResponder={[Function bound touchableHandleStartShouldSetResponder]}
22+
style={
23+
Object {
24+
\\"opacity\\": 1,
25+
}
26+
}
27+
>
28+
<Text>
29+
Change freshness!
30+
</Text>
31+
</View>
32+
</View>"
33+
`;
34+
35+
exports[`debug changing component: bananaFresh button message should now be "fresh" 1`] = `
36+
"<View>
37+
<Text>
38+
Is the banana fresh?
39+
</Text>
40+
<Text
41+
testID=\\"bananaFresh\\"
42+
>
43+
fresh
44+
</Text>
45+
<View
46+
accessible={true}
47+
isTVSelectable={true}
48+
onResponderGrant={[Function bound touchableHandleResponderGrant]}
49+
onResponderMove={[Function bound touchableHandleResponderMove]}
50+
onResponderRelease={[Function bound touchableHandleResponderRelease]}
51+
onResponderTerminate={[Function bound touchableHandleResponderTerminate]}
52+
onResponderTerminationRequest={[Function bound touchableHandleResponderTerminationRequest]}
53+
onStartShouldSetResponder={[Function bound touchableHandleStartShouldSetResponder]}
54+
style={
55+
Object {
56+
\\"opacity\\": 1,
57+
}
58+
}
59+
>
60+
<Text>
61+
Change freshness!
62+
</Text>
63+
</View>
64+
</View>"
65+
`;
66+
67+
exports[`debug: shallow 1`] = `
68+
"<View>
69+
<Text>
70+
Is the banana fresh?
71+
</Text>
72+
<Text
73+
testID=\\"bananaFresh\\"
74+
>
75+
not fresh
76+
</Text>
77+
<Button
78+
onPress={[Function anonymous]}
79+
type=\\"primary\\"
80+
>
81+
Change freshness!
82+
</Button>
83+
</View>"
84+
`;
85+
86+
exports[`debug: shallow with message 1`] = `
87+
"my other custom message
88+
89+
<View>
90+
<Text>
91+
Is the banana fresh?
92+
</Text>
93+
<Text
94+
testID=\\"bananaFresh\\"
95+
>
96+
not fresh
97+
</Text>
98+
<Button
99+
onPress={[Function anonymous]}
100+
type=\\"primary\\"
101+
>
102+
Change freshness!
103+
</Button>
104+
</View>"
105+
`;
106+
107+
exports[`debug: with message 1`] = `
108+
"my custom message
109+
110+
<View>
111+
<Text>
112+
Is the banana fresh?
113+
</Text>
114+
<Text
115+
testID=\\"bananaFresh\\"
116+
>
117+
not fresh
118+
</Text>
119+
<View
120+
accessible={true}
121+
isTVSelectable={true}
122+
onResponderGrant={[Function bound touchableHandleResponderGrant]}
123+
onResponderMove={[Function bound touchableHandleResponderMove]}
124+
onResponderRelease={[Function bound touchableHandleResponderRelease]}
125+
onResponderTerminate={[Function bound touchableHandleResponderTerminate]}
126+
onResponderTerminationRequest={[Function bound touchableHandleResponderTerminationRequest]}
127+
onStartShouldSetResponder={[Function bound touchableHandleStartShouldSetResponder]}
128+
style={
129+
Object {
130+
\\"opacity\\": 1,
131+
}
132+
}
133+
>
134+
<Text>
135+
Change freshness!
136+
</Text>
137+
</View>
138+
</View>"
139+
`;
140+
3141
exports[`toJSON 1`] = `
4142
<View
5143
accessible={true}

src/__tests__/debug.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44
import { TouchableOpacity, Text } from 'react-native';
55
import stripAnsi from 'strip-ansi';
66
import { debug, render, fireEvent, flushMicrotasksQueue } from '..';
7+
import debugShallow from '../helpers/debugShallow';
78

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

4445
debug(component, 'test message');
4546

46-
expect(console.log).toBeCalledWith(output, 'test message');
47+
expect(console.log).toBeCalledWith('test message\n\n', output);
4748
});
4849

4950
test('debug.shallow', () => {
50-
expect(debug.shallow).toBe(debug);
51+
expect(debug.shallow).toBe(debugShallow);
5152
});
5253

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

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

68-
expect(console.log).toBeCalledWith(output, 'test message');
69+
expect(console.log).toBeCalledWith('test message\n\n', output);
6970
});
7071

7172
test('debug.deep async test', async () => {

src/__tests__/render.test.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
/* eslint-disable react/no-multi-comp */
33
import React from 'react';
44
import { View, Text, TouchableOpacity } from 'react-native';
5-
import { render } from '..';
5+
import stripAnsi from 'strip-ansi';
6+
import { render, fireEvent } from '..';
67

78
class Button extends React.Component<*> {
89
render() {
@@ -186,3 +187,43 @@ test('toJSON', () => {
186187

187188
expect(toJSON()).toMatchSnapshot();
188189
});
190+
191+
test('debug', () => {
192+
jest.spyOn(console, 'log').mockImplementation(x => x);
193+
194+
const { debug } = render(<Banana />);
195+
196+
debug();
197+
debug('my custom message');
198+
debug.shallow();
199+
debug.shallow('my other custom message');
200+
201+
// eslint-disable-next-line no-console
202+
const mockCalls = console.log.mock.calls;
203+
204+
expect(stripAnsi(mockCalls[0][0])).toMatchSnapshot();
205+
expect(stripAnsi(mockCalls[1][0] + mockCalls[1][1])).toMatchSnapshot(
206+
'with message'
207+
);
208+
expect(stripAnsi(mockCalls[2][0])).toMatchSnapshot('shallow');
209+
expect(stripAnsi(mockCalls[3][0] + mockCalls[3][1])).toMatchSnapshot(
210+
'shallow with message'
211+
);
212+
});
213+
214+
test('debug changing component', () => {
215+
jest.spyOn(console, 'log').mockImplementation(x => x);
216+
217+
const { getByProps, debug } = render(<Banana />);
218+
219+
fireEvent.press(getByProps({ type: 'primary' }));
220+
221+
debug();
222+
223+
// eslint-disable-next-line no-console
224+
const mockCalls = console.log.mock.calls;
225+
226+
expect(stripAnsi(mockCalls[4][0])).toMatchSnapshot(
227+
'bananaFresh button message should now be "fresh"'
228+
);
229+
});

src/debug.js

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
// @flow
22
/* eslint-disable no-console */
33
import * as React from 'react';
4-
import prettyFormat, { plugins } from 'pretty-format';
5-
import shallow from './shallow';
64
import render from './render';
7-
8-
/**
9-
* Log pretty-printed shallow test component instance
10-
*/
11-
function debugShallow(
12-
instance: ReactTestInstance | React.Element<*>,
13-
message?: any
14-
) {
15-
const { output } = shallow(instance);
16-
17-
console.log(format(output), message || '');
18-
}
5+
import debugShallow from './helpers/debugShallow';
6+
import debugDeep from './helpers/debugDeep';
7+
import format from './helpers/format';
198

209
/**
2110
* Log pretty-printed deep test component instance
2211
*/
23-
function debugDeep(
12+
function debugDeepElementOrInstance(
2413
instance: React.Element<*> | ?ReactTestRendererJSON,
2514
message?: any = ''
2615
) {
@@ -29,21 +18,22 @@ function debugDeep(
2918
// rendering ?ReactTestRendererJSON
3019
// $FlowFixMe
3120
const { toJSON } = render(instance);
32-
console.log(format(toJSON()), message);
21+
if (message) {
22+
console.log(`${message}\n\n`, format(toJSON()));
23+
} else {
24+
console.log(format(toJSON()));
25+
}
3326
} catch (e) {
34-
console.log(format(instance), message);
27+
// $FlowFixMe
28+
debugDeep(instance);
3529
}
3630
}
3731

38-
const format = input =>
39-
prettyFormat(input, {
40-
plugins: [plugins.ReactTestComponent, plugins.ReactElement],
41-
highlight: true,
42-
});
43-
44-
const debug = debugShallow;
32+
function debug(instance: ReactTestInstance | React.Element<*>, message?: any) {
33+
return debugShallow(instance, message);
34+
}
4535

4636
debug.shallow = debugShallow;
47-
debug.deep = debugDeep;
37+
debug.deep = debugDeepElementOrInstance;
4838

4939
export default debug;

src/helpers/debugDeep.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @flow
2+
import format from './format';
3+
4+
/**
5+
* Log pretty-printed deep test component instance
6+
*/
7+
export default function debugDeep(
8+
instance: ?ReactTestRendererJSON,
9+
message?: any = ''
10+
) {
11+
if (message) {
12+
console.log(`${message}\n\n`, format(instance));
13+
} else {
14+
console.log(format(instance));
15+
}
16+
}

src/helpers/debugShallow.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @flow
2+
import * as React from 'react';
3+
import shallow from '../shallow';
4+
import format from './format';
5+
6+
/**
7+
* Log pretty-printed shallow test component instance
8+
*/
9+
export default function debugShallow(
10+
instance: ReactTestInstance | React.Element<*>,
11+
message?: any
12+
) {
13+
const { output } = shallow(instance);
14+
15+
if (message) {
16+
console.log(`${message}\n\n`, format(output));
17+
} else {
18+
console.log(format(output));
19+
}
20+
}

src/helpers/format.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @flow
2+
import prettyFormat, { plugins } from 'pretty-format';
3+
4+
const format = (input: ?ReactTestRendererJSON) =>
5+
prettyFormat(input, {
6+
plugins: [plugins.ReactTestComponent, plugins.ReactElement],
7+
highlight: true,
8+
});
9+
10+
export default format;

src/render.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import * as React from 'react';
33
import TestRenderer from 'react-test-renderer'; // eslint-disable-line import/no-extraneous-dependencies
44
import { getByAPI } from './helpers/getByAPI';
55
import { queryByAPI } from './helpers/queryByAPI';
6+
import debugShallow from './helpers/debugShallow';
7+
import debugDeep from './helpers/debugDeep';
68

79
/**
810
* Renders test component deeply using react-test-renderer and exposes helpers
@@ -21,5 +23,14 @@ export default function render(
2123
update: renderer.update,
2224
unmount: renderer.unmount,
2325
toJSON: renderer.toJSON,
26+
debug: debug(instance, renderer),
2427
};
2528
}
29+
30+
function debug(instance: ReactTestInstance, renderer) {
31+
function debugImpl(message?: string) {
32+
return debugDeep(renderer.toJSON(), message);
33+
}
34+
debugImpl.shallow = message => debugShallow(instance, message);
35+
return debugImpl;
36+
}

0 commit comments

Comments
 (0)