diff --git a/docs/README.md b/docs/README.md
index 5c6a1721a..bac5a2d17 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -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)
diff --git a/docs/api/ReactWrapper/debug.md b/docs/api/ReactWrapper/debug.md
new file mode 100644
index 000000000..5ae11b14c
--- /dev/null
+++ b/docs/api/ReactWrapper/debug.md
@@ -0,0 +1,76 @@
+# `.debug() => String`
+
+Returns an HTML-like string of the wrapper for debugging purposes. Useful to print out to the
+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 (
+
+ Foo
+
+ );
+ }
+}
+
+class Bar extends React.Component {
+ render() {
+ return (
+
+ Non-Foo
+
+
+ );
+ }
+}
+```
+
+In this case, running:
+```jsx
+console.log(mount().debug());
+```
+
+Would output the following to the console:
+```jsx
+
+
+
+ Non-Foo
+
+
+
+
+ Foo
+
+
+
+
+
+```
+
+Likewise, running:
+
+```jsx
+console.log(mount().find(Foo).debug();
+```
+Would output the following to the console:
+```jsx
+
+
+
+ Foo
+
+
+
+```
diff --git a/docs/api/ShallowWrapper/debug.md b/docs/api/ShallowWrapper/debug.md
index b97a7efa4..2ceb54bcb 100644
--- a/docs/api/ShallowWrapper/debug.md
+++ b/docs/api/ShallowWrapper/debug.md
@@ -1,6 +1,6 @@
# `.debug() => String`
-Returns an html-like string of the wrapper for debugging purposes. Useful to print out to the
+Returns an HTML-like string of the wrapper for debugging purposes. Useful to print out to the
console when tests are not passing when you expect them to.
diff --git a/docs/api/mount.md b/docs/api/mount.md
index 12ed68b22..ee1484b66 100644
--- a/docs/api/mount.md
+++ b/docs/api/mount.md
@@ -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.
diff --git a/package.json b/package.json
index 8986859e9..68def65a4 100644
--- a/package.json
+++ b/package.json
@@ -10,12 +10,12 @@
"version": "npm run build",
"clean": "rimraf build",
"lint": "eslint src/**",
- "test": "npm run lint && npm run tests-only",
- "tests-only": "mocha --compilers js:babel-core/register --recursive withDom.js src/**/__tests__/*.js",
"check": "npm run lint && npm run test:all",
"build": "babel src --out-dir build",
- "test:only": "mocha --compilers js:babel-core/register --watch withDom.js",
- "test:watch": "mocha --compilers js:babel-core/register --recursive withDom.js src/**/__tests__/*.js --watch",
+ "test": "npm run lint && npm run test:only",
+ "test:only": "mocha --require withDom.js --compilers js:babel-core/register --recursive src/**/__tests__/*.js",
+ "test:single": "mocha --require withDom.js --compilers js:babel-core/register --watch",
+ "test:watch": "mocha --require withDom.js --compilers js:babel-core/register --recursive src/**/__tests__/*.js --watch",
"test:describeWithDOMOnly": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/describeWithDOM/describeWithDOMOnly-spec.js",
"test:describeWithDOMSkip": "mocha --compilers js:babel-core/register --recursive src/**/__tests__/describeWithDOM/describeWithDOMSkip-spec.js",
"test:all": "npm run react:13 && npm test && npm run test:describeWithDOMOnly && npm run test:describeWithDOMSkip && npm run react:14 && npm test && npm run test:describeWithDOMOnly && npm run test:describeWithDOMSkip",
@@ -53,6 +53,7 @@
"cheerio": "^0.20.0",
"is-subset": "^0.1.1",
"object.assign": "^4.0.3",
+ "object.values": "^1.0.3",
"sinon": "^1.17.3",
"underscore": "^1.8.3"
},
diff --git a/src/Debug.js b/src/Debug.js
index 829296ead..2d16d03b9 100644
--- a/src/Debug.js
+++ b/src/Debug.js
@@ -1,10 +1,21 @@
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';
+import objectValues from 'object.values';
export function typeName(node) {
return typeof node.type === 'function'
@@ -63,3 +74,59 @@ 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);
+ if (!renderedChildren) {
+ children.push(...childrenOfNode(currentElement));
+ } else {
+ children.push(...objectValues(renderedChildren));
+ }
+ } 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');
+}
diff --git a/src/ReactWrapper.js b/src/ReactWrapper.js
index 9af154404..61dae3b76 100644
--- a/src/ReactWrapper.js
+++ b/src/ReactWrapper.js
@@ -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
@@ -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);
+ }
}
diff --git a/src/ShallowWrapper.js b/src/ShallowWrapper.js
index 424355e55..9616db946 100644
--- a/src/ShallowWrapper.js
+++ b/src/ShallowWrapper.js
@@ -686,7 +686,7 @@ export default class ShallowWrapper {
}
/**
- * Returns an html-like string of the shallow render for debugging purposes.
+ * Returns an HTML-like string of the shallow render for debugging purposes.
*
* @returns {String}
*/
diff --git a/src/Utils.js b/src/Utils.js
index 0f9021c1c..7d123001e 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -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) || {};
}
diff --git a/src/__tests__/Debug-spec.js b/src/__tests__/Debug-spec.js
index cc14aa438..fcfc56d19 100644
--- a/src/__tests__/Debug-spec.js
+++ b/src/__tests__/Debug-spec.js
@@ -5,7 +5,8 @@ import {
indent,
debugNode,
} from '../Debug';
-import { itIf } from './_helpers';
+import { mount } from '../';
+import { describeWithDOM, itIf } from './_helpers';
import { REACT013 } from '../version';
describe('debug', () => {
@@ -188,4 +189,133 @@ describe('debug', () => {
});
+ describeWithDOM('debugInst(inst)', () => {
+ it('renders basic debug of mounted components', () => {
+ class Foo extends React.Component {
+ render() {
+ return (
+
+ Foo
+
+ );
+ }
+ }
+ expect(mount().debug()).to.eql(
+`
+
+
+ Foo
+
+
+`);
+ });
+
+ it('renders debug of compositional components', () => {
+ class Foo extends React.Component {
+ render() {
+ return (
+
+ Foo
+
+ );
+ }
+ }
+ class Bar extends React.Component {
+ render() {
+ return (
+
+ Non-Foo
+
+
+ );
+ }
+ }
+ expect(mount().debug()).to.eql(
+ `
+
+
+ Non-Foo
+
+
+
+
+ Foo
+
+
+
+
+`);
+ });
+
+ it('renders a subtree of a mounted tree', () => {
+ class Foo extends React.Component {
+ render() {
+ return (
+
+ Foo
+
+ );
+ }
+ }
+ class Bar extends React.Component {
+ render() {
+ return (
+
+ Non-Foo
+
+
+ );
+ }
+ }
+ expect(mount().find(Foo).debug()).to.eql(
+ `
+
+
+ Foo
+
+
+`);
+ });
+
+ it('renders passed children properly', () => {
+ class Foo extends React.Component {
+ render() {
+ return (
+
+ From Foo
+ {this.props.children}
+
+ );
+ }
+ }
+ class Bar extends React.Component {
+ render() {
+ return (
+
+
+ From Bar
+
+
+ );
+ }
+ }
+
+ expect(mount().debug()).to.eql(
+`
+
+
+
+
+ From Foo
+
+
+ From Bar
+
+
+
+
+`);
+
+ });
+ });
});