Skip to content
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

Add .debug() method to ReactWrapper #158

Merged
merged 1 commit into from
Feb 2, 2016
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
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
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>
```
2 changes: 1 addition & 1 deletion docs/api/ShallowWrapper/debug.md
Original file line number Diff line number Diff line change
@@ -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.


Expand Down
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
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
},
Expand Down
67 changes: 67 additions & 0 deletions src/Debug.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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 '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just want to confirm - these two lines will not do anything special for true, or for Symbols.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm.... not that I'm aware of.

I don't actually know what calling this on <div>{true}</div> would do, but it should do whatever react itself does...

No idea how or why or what symbols would be doing getting called in this function....?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just checked and <div>{true}</div> gets rendered as <div />

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, wouldn't we want if (inst === true) return ''? or am i misunderstanding something.

what about <div>{Symbol('foo')}</div>?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb <div>{Symbol('foo')}</div> also renderes an empty div. I think this function isn't being used exactly how you imagine it. Though i'm not sure I event 100% understand it... lol


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');
}
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);
}
}
2 changes: 1 addition & 1 deletion src/ShallowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
*/
Expand Down
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
132 changes: 131 additions & 1 deletion src/__tests__/Debug-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -188,4 +189,133 @@ describe('debug', () => {

});

describeWithDOM('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>`);
});

it('renders passed children properly', () => {
class Foo extends React.Component {
render() {
return (
<div className="foo">
<span>From Foo</span>
{this.props.children}
</div>
);
}
}
class Bar extends React.Component {
render() {
return (
<div className="bar">
<Foo baz="bax">
<span>From Bar</span>
</Foo>
</div>
);
}
}

expect(mount(<Bar id="2" />).debug()).to.eql(
`<Bar id="2">
<div className="bar">
<Foo baz="bax">
<div className="foo">
<span>
From Foo
</span>
<span>
From Bar
</span>
</div>
</Foo>
</div>
</Bar>`);

});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to see an example where a component is rendering this.props.children somewhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. I'll add that.

});