Skip to content

Commit

Permalink
Add ReactElement plugin (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon authored and cpojer committed Sep 1, 2016
1 parent a0c81c4 commit 824b7ca
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 15 deletions.
70 changes: 68 additions & 2 deletions __tests__/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ const prettyFormat = require('../');

const React = require('react');
const ReactTestComponent = require('../plugins/ReactTestComponent');
const ReactElement = require('../plugins/ReactElement');
const renderer = require('react/lib/ReactTestRenderer');

function returnArguments() {
return arguments;
}

function assertPrintedJSX(actual, expected) {
expect(
prettyFormat(actual, {
plugins: [ReactElement]
})
).toEqual(expected);
expect(
prettyFormat(renderer.create(actual).toJSON(), {
plugins: [ReactTestComponent]
plugins: [ReactTestComponent, ReactElement]
})
).toEqual(expected);
}
Expand Down Expand Up @@ -312,7 +318,7 @@ describe('prettyFormat()', () => {
expect(prettyFormat(set)).toEqual('"map"');
});

describe('ReactTestComponent plugin', () => {
describe('ReactTestComponent and ReactElement plugins', () => {
const Mouse = React.createClass({
getInitialState: () => {
return { mouse: 'mouse' };
Expand Down Expand Up @@ -346,6 +352,15 @@ describe('prettyFormat()', () => {
);
});

it('should support a single element with mixed children', () => {
assertPrintedJSX(
React.createElement('Mouse', null,
[[1, null], 2, undefined, [false, [3]]]
),
'<Mouse>\n 1\n 2\n 3\n</Mouse>'
);
});

it('should support props with strings', () => {
assertPrintedJSX(
React.createElement('Mouse', { style: 'color:red' }),
Expand Down Expand Up @@ -441,5 +456,56 @@ describe('prettyFormat()', () => {
'<Mouse\n abc={\n Object {\n "one": "1",\n "two": 2\n }\n }\n zeus="kentaromiura watched me fix this">\n <Mouse\n acbd={\n Object {\n "one": "1",\n "two": 2\n }\n }\n xyz={123}>\n NESTED\n </Mouse>\n</Mouse>'
);
});

it('should support a single element with React elements as props', () => {
assertPrintedJSX(
React.createElement('Mouse', {
prop: React.createElement('div')
}),
'<Mouse\n prop={<div />} />'
);
});

it('should support a single element with React elements with props', () => {
assertPrintedJSX(
React.createElement('Mouse', {
prop: React.createElement('div', {foo: 'bar'})
}),
'<Mouse\n prop={\n <div\n foo="bar" />\n } />'
);
});

it('should support a single element with React elements with a child', () => {
assertPrintedJSX(
React.createElement('Mouse', {
prop: React.createElement('div', null, 'mouse')
}),
'<Mouse\n prop={\n <div>\n mouse\n </div>\n } />'
);
});

it('should support a single element with React elements with children', () => {
assertPrintedJSX(
React.createElement('Mouse', {
prop: React.createElement('div', null, 'mouse', React.createElement('span', null, 'rat'))
}),
'<Mouse\n prop={\n <div>\n mouse\n <span>\n rat\n </span>\n </div>\n } />'
);
});

it('should support a single element with React elements with array children', () => {
assertPrintedJSX(
React.createElement('Mouse', {
prop: React.createElement('div', null,
'mouse',
[
React.createElement('span', {key: 1}, 'rat'),
React.createElement('span', {key: 2}, 'cat'),
]
)
}),
'<Mouse\n prop={\n <div>\n mouse\n <span>\n rat\n </span>\n <span>\n cat\n </span>\n </div>\n } />'
);
});
});
});
74 changes: 74 additions & 0 deletions plugins/ReactElement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

const printString = require('../printString');

const reactElement = Symbol.for('react.element');

function traverseChildren(opaqueChildren, cb) {
if (Array.isArray(opaqueChildren)) {
opaqueChildren.forEach(child => traverseChildren(child, cb));
} else if (opaqueChildren != null && opaqueChildren !== false) {
cb(opaqueChildren);
}
}

function printChildren(flatChildren, print, indent) {
return flatChildren.map(node => {
if (typeof node === 'object') {
return printElement(node, print, indent);
} else if (typeof node === 'string') {
return printString(node);
} else {
return print(node);
}
}).join('\n');
}

function printProps(props, print, indent) {
return Object.keys(props).sort().map(name => {
if (name === 'children') {
return '';
}

const prop = props[name];
let printed = print(prop);

if (typeof prop !== 'string') {
if (printed.indexOf('\n') !== -1) {
printed = '{\n' + indent(indent(printed) + '\n}');
} else {
printed = '{' + printed + '}';
}
}

return '\n' + indent(name + '=') + printed;
}).join('');
}

function printElement(element, print, indent) {
let result = '<' + element.type;
result += printProps(element.props, print, indent);

const opaqueChildren = element.props.children;
if (opaqueChildren) {
let flatChildren = [];
traverseChildren(opaqueChildren, child => {
flatChildren.push(child);
});
const children = printChildren(flatChildren, print, indent);
result += '>\n' + indent(children) + '\n</' + element.type + '>';
} else {
result += ' />';
}

return result;
}

module.exports = {
test(object) {
return object && object.$$typeof === reactElement;
},
print(val, print, indent) {
return printElement(val, print, indent);
}
};
26 changes: 13 additions & 13 deletions plugins/ReactTestComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const printString = require('../printString');
const reactTestInstance = Symbol.for('react.test.json');

function printChildren(children, print, indent) {
return children.map(child => printElement(child, print, indent)).join('\n');
return children.map(child => printInstance(child, print, indent)).join('\n');
}

function printProps(props, print, indent) {
Expand All @@ -25,22 +25,22 @@ function printProps(props, print, indent) {
}).join('');
}

function printElement(element, print, indent) {
if (typeof element == 'number') {
return print(element);
} else if (typeof element === 'string') {
return printString(element);
function printInstance(instance, print, indent) {
if (typeof instance == 'number') {
return print(instance);
} else if (typeof instance === 'string') {
return printString(instance);
}

let result = '<' + element.type;
let result = '<' + instance.type;

if (element.props) {
result += printProps(element.props, print, indent);
if (instance.props) {
result += printProps(instance.props, print, indent);
}

if (element.children) {
const children = printChildren(element.children, print, indent);
result += '>\n' + indent(children) + '\n</' + element.type + '>';
if (instance.children) {
const children = printChildren(instance.children, print, indent);
result += '>\n' + indent(children) + '\n</' + instance.type + '>';
} else {
result += ' />';
}
Expand All @@ -53,6 +53,6 @@ module.exports = {
return object && object.$$typeof === reactTestInstance;
},
print(val, print, indent) {
return printElement(val, print, indent);
return printInstance(val, print, indent);
}
};

1 comment on commit 824b7ca

@vvo
Copy link

@vvo vvo commented on 824b7ca Sep 2, 2016

Choose a reason for hiding this comment

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

Can we add it to the documentation? This is a nice plugin, I actually wrote a very similar one some time ago: https://github.com/algolia/react-element-to-jsx-string

Nice to see you took the same approach :p

Please sign in to comment.