Skip to content

Commit

Permalink
Add .debug() method to ReactWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
lelandrichardson committed Feb 2, 2016
1 parent 2a0de29 commit 05c16c1
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
* [unmount()](/docs/api/ReactWrapper/unmount.md)
* [mount()](/docs/api/ReactWrapper/mount.md)
* [update()](/docs/api/ReactWrapper/update.md)
* [debug()](/docs/api/ReactWrapper/debug.md)
* [type()](/docs/api/ReactWrapper/type.md)
* [forEach(fn)](/docs/api/ReactWrapper/forEach.md)
* [map(fn)](/docs/api/ReactWrapper/map.md)
Expand Down
76 changes: 76 additions & 0 deletions docs/api/ReactWrapper/debug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# `.debug() => String`

Returns an html-like string of the wrapper for debugging purposes. Useful to print out to the

This comment has been minimized.

Copy link
@lencioni

lencioni Feb 2, 2016

Contributor

nit: html -> HTML

console when tests are not passing when you expect them to.


#### Returns

`String`: The resulting string.



#### Examples

Say we have the following components:
```jsx
class Foo extends React.Component {
render() {
return (
<div className="foo">
<span>Foo</span>
</div>
);
}
}

class Bar extends React.Component {
render() {
return (
<div className="bar">
<span>Non-Foo</span>
<Foo baz="bax" />
</div>
);
}
}
```

In this case, running:
```jsx
console.log(mount(<Bar id="2" />).debug());
```

Would output the following to the console:
```jsx
<Bar id="2">
<div className="bar">
<span>
Non-Foo
</span>
<Foo baz="bax">
<div className="foo">
<span>
Foo
</span>
</div>
</Foo>
</div>
</Bar>
```

Likewise, running:

```jsx
console.log(mount(<Bar id="2" />).find(Foo).debug();
```
Would output the following to the console:
```jsx
<Foo baz="bax">
<div className="foo">
<span>
Foo
</span>
</div>
</Foo>
```
3 changes: 3 additions & 0 deletions docs/api/mount.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ A method that re-mounts the component.
#### [`.update() => ReactWrapper`](ReactWrapper/update.md)
Calls `.forceUpdate()` on the root component instance.

#### [`.debug() => String`](ReactWrapper/debug.md)
Returns a string representation of the current render tree for debugging purposes.

#### [`.type() => String|Function`](ReactWrapper/type.md)
Returns the type of the current node of the wrapper.

Expand Down
68 changes: 68 additions & 0 deletions src/Debug.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import {
childrenOfNode,
} from './ShallowTraversal';
import {
internalInstance,
renderedChildrenOfInst,
} from './MountedTraversal';
import {
isDOMComponent,
isCompositeComponent,
isElement,
} from './react-compat';
import {
propsOfNode,
} from './Utils';
import { without, escape, compact } from 'underscore';
import { REACT013, REACT014 } from './version';

export function typeName(node) {
return typeof node.type === 'function'
Expand Down Expand Up @@ -63,3 +73,61 @@ export function debugNode(node, indentLength = 2) {
export function debugNodes(nodes) {
return nodes.map(debugNode).join('\n\n\n');
}

export function debugInst(inst, indentLength = 2) {
if (typeof inst === 'string' || typeof inst === 'number') return escape(inst);
if (!inst) return '';

if (!inst.getPublicInstance) {
const internal = internalInstance(inst);
return debugInst(internal, indentLength);
}

const publicInst = inst.getPublicInstance();

if (typeof publicInst === 'string' || typeof publicInst === 'number') return escape(publicInst);
if (!publicInst) return '';

// do stuff with publicInst
const currentElement = inst._currentElement;
const type = typeName(currentElement);
const props = propsString(currentElement);
const children = [];
if (isDOMComponent(publicInst)) {
const renderedChildren = renderedChildrenOfInst(inst) || childrenOfNode(currentElement);
let key;
for (key in renderedChildren) {
if (!renderedChildren.hasOwnProperty(key)) {
continue;
}
children.push(renderedChildren[key]);
}
} else if (
REACT014 &&
isElement(currentElement) &&
typeof currentElement.type === 'function'
) {
children.push(inst._renderedComponent);
} else if (
REACT013 &&
isCompositeComponent(publicInst)
) {
children.push(inst._renderedComponent);
}

const childrenStrs = compact(children.map(n => debugInst(n, indentLength)));

const beforeProps = props ? ' ' : '';
const nodeClose = childrenStrs.length ? `</${type}>` : '/>';
const afterProps = childrenStrs.length
? '>'
: ' ';
const childrenIndented = childrenStrs.length
? '\n' + childrenStrs.map(x => indent(indentLength + 2, x)).join('\n') + '\n'
: '';
return `<${type}${beforeProps}${props}${afterProps}${childrenIndented}${nodeClose}`;
}

export function debugInsts(insts) {
return insts.map(debugInst).join('\n\n\n');
}
12 changes: 12 additions & 0 deletions src/ReactWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {
mapNativeEventNames,
containsChildrenSubArray,
} from './Utils';
import {
debugInsts,
} from './Debug';

/**
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
Expand Down Expand Up @@ -699,4 +702,13 @@ export default class ReactWrapper {
}
return new ReactWrapper(node, this.root);
}

/**
* Returns an html-like string of the shallow render for debugging purposes.
*
* @returns {String}
*/
debug() {
return debugInsts(this.nodes);
}
}
3 changes: 3 additions & 0 deletions src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export function propsOfNode(node) {
if (REACT013 && node && node._store) {
return (node._store.props) || {};
}
if (node && node._reactInternalComponent && node._reactInternalComponent._currentElement) {
return (node._reactInternalComponent._currentElement.props) || {};
}
return (node && node.props) || {};
}

Expand Down
89 changes: 89 additions & 0 deletions src/__tests__/Debug-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
indent,
debugNode,
} from '../Debug';
import { mount } from '../';
import { itIf } from './_helpers';
import { REACT013 } from '../version';

Expand Down Expand Up @@ -188,4 +189,92 @@ describe('debug', () => {

});

describe('debugInst(inst)', () => {
it('renders basic debug of mounted components', () => {
class Foo extends React.Component {
render() {
return (
<div className="foo">
<span>Foo</span>
</div>
);
}
}
expect(mount(<Foo id="2" />).debug()).to.eql(
`<Foo id="2">
<div className="foo">
<span>
Foo
</span>
</div>
</Foo>`);
});

it('renders debug of compositional components', () => {
class Foo extends React.Component {
render() {
return (
<div className="foo">
<span>Foo</span>
</div>
);
}
}
class Bar extends React.Component {
render() {
return (
<div className="bar">
<span>Non-Foo</span>
<Foo baz="bax" />
</div>
);
}
}
expect(mount(<Bar id="2" />).debug()).to.eql(
`<Bar id="2">
<div className="bar">
<span>
Non-Foo
</span>
<Foo baz="bax">
<div className="foo">
<span>
Foo
</span>
</div>
</Foo>
</div>
</Bar>`);
});

it('renders a subtree of a mounted tree', () => {
class Foo extends React.Component {
render() {
return (
<div className="foo">
<span>Foo</span>
</div>
);
}
}
class Bar extends React.Component {
render() {
return (
<div className="bar">
<span>Non-Foo</span>
<Foo baz="bax" />
</div>
);
}
}
expect(mount(<Bar id="2" />).find(Foo).debug()).to.eql(
`<Foo baz="bax">
<div className="foo">
<span>
Foo
</span>
</div>
</Foo>`);
});
});
});

0 comments on commit 05c16c1

Please sign in to comment.