Skip to content

Commit 067f766

Browse files
Add .debug() method to ReactWrapper
1 parent 2a0de29 commit 067f766

File tree

8 files changed

+254
-2
lines changed

8 files changed

+254
-2
lines changed

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
* [unmount()](/docs/api/ReactWrapper/unmount.md)
8181
* [mount()](/docs/api/ReactWrapper/mount.md)
8282
* [update()](/docs/api/ReactWrapper/update.md)
83+
* [debug()](/docs/api/ReactWrapper/debug.md)
8384
* [type()](/docs/api/ReactWrapper/type.md)
8485
* [forEach(fn)](/docs/api/ReactWrapper/forEach.md)
8586
* [map(fn)](/docs/api/ReactWrapper/map.md)

docs/api/ReactWrapper/debug.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# `.debug() => String`
2+
3+
Returns an HTML-like string of the wrapper for debugging purposes. Useful to print out to the
4+
console when tests are not passing when you expect them to.
5+
6+
7+
#### Returns
8+
9+
`String`: The resulting string.
10+
11+
12+
13+
#### Examples
14+
15+
Say we have the following components:
16+
```jsx
17+
class Foo extends React.Component {
18+
render() {
19+
return (
20+
<div className="foo">
21+
<span>Foo</span>
22+
</div>
23+
);
24+
}
25+
}
26+
27+
class Bar extends React.Component {
28+
render() {
29+
return (
30+
<div className="bar">
31+
<span>Non-Foo</span>
32+
<Foo baz="bax" />
33+
</div>
34+
);
35+
}
36+
}
37+
```
38+
39+
In this case, running:
40+
```jsx
41+
console.log(mount(<Bar id="2" />).debug());
42+
```
43+
44+
Would output the following to the console:
45+
```jsx
46+
<Bar id="2">
47+
<div className="bar">
48+
<span>
49+
Non-Foo
50+
</span>
51+
<Foo baz="bax">
52+
<div className="foo">
53+
<span>
54+
Foo
55+
</span>
56+
</div>
57+
</Foo>
58+
</div>
59+
</Bar>
60+
```
61+
62+
Likewise, running:
63+
64+
```jsx
65+
console.log(mount(<Bar id="2" />).find(Foo).debug();
66+
```
67+
Would output the following to the console:
68+
```jsx
69+
<Foo baz="bax">
70+
<div className="foo">
71+
<span>
72+
Foo
73+
</span>
74+
</div>
75+
</Foo>
76+
```

docs/api/ShallowWrapper/debug.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# `.debug() => String`
22

3-
Returns an html-like string of the wrapper for debugging purposes. Useful to print out to the
3+
Returns an HTML-like string of the wrapper for debugging purposes. Useful to print out to the
44
console when tests are not passing when you expect them to.
55

66

docs/api/mount.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ A method that re-mounts the component.
145145
#### [`.update() => ReactWrapper`](ReactWrapper/update.md)
146146
Calls `.forceUpdate()` on the root component instance.
147147

148+
#### [`.debug() => String`](ReactWrapper/debug.md)
149+
Returns a string representation of the current render tree for debugging purposes.
150+
148151
#### [`.type() => String|Function`](ReactWrapper/type.md)
149152
Returns the type of the current node of the wrapper.
150153

src/Debug.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import {
22
childrenOfNode,
33
} from './ShallowTraversal';
4+
import {
5+
internalInstance,
6+
renderedChildrenOfInst,
7+
} from './MountedTraversal';
8+
import {
9+
isDOMComponent,
10+
isCompositeComponent,
11+
isElement,
12+
} from './react-compat';
413
import {
514
propsOfNode,
615
} from './Utils';
716
import { without, escape, compact } from 'underscore';
17+
import { REACT013, REACT014 } from './version';
818

919
export function typeName(node) {
1020
return typeof node.type === 'function'
@@ -63,3 +73,61 @@ export function debugNode(node, indentLength = 2) {
6373
export function debugNodes(nodes) {
6474
return nodes.map(debugNode).join('\n\n\n');
6575
}
76+
77+
export function debugInst(inst, indentLength = 2) {
78+
if (typeof inst === 'string' || typeof inst === 'number') return escape(inst);
79+
if (!inst) return '';
80+
81+
if (!inst.getPublicInstance) {
82+
const internal = internalInstance(inst);
83+
return debugInst(internal, indentLength);
84+
}
85+
86+
const publicInst = inst.getPublicInstance();
87+
88+
if (typeof publicInst === 'string' || typeof publicInst === 'number') return escape(publicInst);
89+
if (!publicInst) return '';
90+
91+
// do stuff with publicInst
92+
const currentElement = inst._currentElement;
93+
const type = typeName(currentElement);
94+
const props = propsString(currentElement);
95+
const children = [];
96+
if (isDOMComponent(publicInst)) {
97+
const renderedChildren = renderedChildrenOfInst(inst) || childrenOfNode(currentElement);
98+
let key;
99+
for (key in renderedChildren) {
100+
if (!renderedChildren.hasOwnProperty(key)) {
101+
continue;
102+
}
103+
children.push(renderedChildren[key]);
104+
}
105+
} else if (
106+
REACT014 &&
107+
isElement(currentElement) &&
108+
typeof currentElement.type === 'function'
109+
) {
110+
children.push(inst._renderedComponent);
111+
} else if (
112+
REACT013 &&
113+
isCompositeComponent(publicInst)
114+
) {
115+
children.push(inst._renderedComponent);
116+
}
117+
118+
const childrenStrs = compact(children.map(n => debugInst(n, indentLength)));
119+
120+
const beforeProps = props ? ' ' : '';
121+
const nodeClose = childrenStrs.length ? `</${type}>` : '/>';
122+
const afterProps = childrenStrs.length
123+
? '>'
124+
: ' ';
125+
const childrenIndented = childrenStrs.length
126+
? '\n' + childrenStrs.map(x => indent(indentLength + 2, x)).join('\n') + '\n'
127+
: '';
128+
return `<${type}${beforeProps}${props}${afterProps}${childrenIndented}${nodeClose}`;
129+
}
130+
131+
export function debugInsts(insts) {
132+
return insts.map(debugInst).join('\n\n\n');
133+
}

src/ReactWrapper.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import {
2020
mapNativeEventNames,
2121
containsChildrenSubArray,
2222
} from './Utils';
23+
import {
24+
debugInsts,
25+
} from './Debug';
2326

2427
/**
2528
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
@@ -699,4 +702,13 @@ export default class ReactWrapper {
699702
}
700703
return new ReactWrapper(node, this.root);
701704
}
705+
706+
/**
707+
* Returns an html-like string of the shallow render for debugging purposes.
708+
*
709+
* @returns {String}
710+
*/
711+
debug() {
712+
return debugInsts(this.nodes);
713+
}
702714
}

src/Utils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export function propsOfNode(node) {
1414
if (REACT013 && node && node._store) {
1515
return (node._store.props) || {};
1616
}
17+
if (node && node._reactInternalComponent && node._reactInternalComponent._currentElement) {
18+
return (node._reactInternalComponent._currentElement.props) || {};
19+
}
1720
return (node && node.props) || {};
1821
}
1922

src/__tests__/Debug-spec.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
indent,
66
debugNode,
77
} from '../Debug';
8-
import { itIf } from './_helpers';
8+
import { mount } from '../';
9+
import { describeWithDOM, itIf } from './_helpers';
910
import { REACT013 } from '../version';
1011

1112
describe('debug', () => {
@@ -188,4 +189,92 @@ describe('debug', () => {
188189

189190
});
190191

192+
describeWithDOM('debugInst(inst)', () => {
193+
it('renders basic debug of mounted components', () => {
194+
class Foo extends React.Component {
195+
render() {
196+
return (
197+
<div className="foo">
198+
<span>Foo</span>
199+
</div>
200+
);
201+
}
202+
}
203+
expect(mount(<Foo id="2" />).debug()).to.eql(
204+
`<Foo id="2">
205+
<div className="foo">
206+
<span>
207+
Foo
208+
</span>
209+
</div>
210+
</Foo>`);
211+
});
212+
213+
it('renders debug of compositional components', () => {
214+
class Foo extends React.Component {
215+
render() {
216+
return (
217+
<div className="foo">
218+
<span>Foo</span>
219+
</div>
220+
);
221+
}
222+
}
223+
class Bar extends React.Component {
224+
render() {
225+
return (
226+
<div className="bar">
227+
<span>Non-Foo</span>
228+
<Foo baz="bax" />
229+
</div>
230+
);
231+
}
232+
}
233+
expect(mount(<Bar id="2" />).debug()).to.eql(
234+
`<Bar id="2">
235+
<div className="bar">
236+
<span>
237+
Non-Foo
238+
</span>
239+
<Foo baz="bax">
240+
<div className="foo">
241+
<span>
242+
Foo
243+
</span>
244+
</div>
245+
</Foo>
246+
</div>
247+
</Bar>`);
248+
});
249+
250+
it('renders a subtree of a mounted tree', () => {
251+
class Foo extends React.Component {
252+
render() {
253+
return (
254+
<div className="foo">
255+
<span>Foo</span>
256+
</div>
257+
);
258+
}
259+
}
260+
class Bar extends React.Component {
261+
render() {
262+
return (
263+
<div className="bar">
264+
<span>Non-Foo</span>
265+
<Foo baz="bax" />
266+
</div>
267+
);
268+
}
269+
}
270+
expect(mount(<Bar id="2" />).find(Foo).debug()).to.eql(
271+
`<Foo baz="bax">
272+
<div className="foo">
273+
<span>
274+
Foo
275+
</span>
276+
</div>
277+
</Foo>`);
278+
});
279+
});
191280
});

0 commit comments

Comments
 (0)