Skip to content

Commit b83521a

Browse files
committed
Add pretty-format for handling complex JSX attribute values beautifully
1 parent 8171a0f commit b83521a

File tree

3 files changed

+165
-5
lines changed

3 files changed

+165
-5
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,8 @@
5050
"rollup-plugin-memory": "^1.0.0",
5151
"sinon": "^1.17.1",
5252
"sinon-chai": "^2.8.0"
53+
},
54+
"dependencies": {
55+
"pretty-format": "^3.5.0"
5356
}
5457
}

src/index.js

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import prettyFormat from 'pretty-format';
12

23
const SHALLOW = { shallow: true };
34

@@ -53,7 +54,7 @@ let memoize = (fn, mem={}) => v => mem[v] || (mem[v] = fn(v));
5354

5455
let indent = (s, char) => String(s).replace(/(\n+)/g, '$1' + (char || '\t'));
5556

56-
let isLargeString = s => (String(s).length>40 || String(s).indexOf('\n')!==-1 || String(s).indexOf('<')!==-1);
57+
let isLargeString = (s, length, ignoreLines) => (String(s).length>(length || 40) || (!ignoreLines && String(s).indexOf('\n')!==-1) || String(s).indexOf('<')!==-1);
5758

5859
function styleObjToCss(s) {
5960
let str = '';
@@ -89,6 +90,27 @@ function getNodeProps(vnode) {
8990
return props;
9091
}
9192

93+
// we have to patch in Array support, Possible issue in npm.im/pretty-format
94+
let preactPlugin = {
95+
test(object) {
96+
if (Array.isArray(object)) {
97+
return preactPlugin.test(object[0]);
98+
}
99+
// return object && Object.prototype.toString.call(object)==='[object VNode]';
100+
return object && typeof object==='object' && 'nodeName' in object && 'attributes' in object && 'children' in object;
101+
},
102+
print(val, print, indent) {
103+
if (Array.isArray(val)) {
104+
return 'Array [\n ' + val.map( v => preactPlugin.test(v) ? renderToString(v, preactPlugin.context, preactPlugin.opts, true) : v ).join(',\n ') + '\n]';
105+
}
106+
return renderToString(val, preactPlugin.context, preactPlugin.opts, true);
107+
}
108+
};
109+
110+
let prettyFormatOpts = {
111+
plugins: [preactPlugin]
112+
};
113+
92114
/** Render Preact JSX + Components to an HTML string.
93115
* @name render
94116
* @function
@@ -165,8 +187,7 @@ export default function renderToString(vnode, context, opts, inner) {
165187
}
166188

167189
// render JSX to HTML
168-
let s = `<${nodeName}`,
169-
html;
190+
let s = '', html;
170191

171192
if (attributes) {
172193
let attrs = objectKeys(attributes);
@@ -179,13 +200,31 @@ export default function renderToString(vnode, context, opts, inner) {
179200
v = attributes[name];
180201
if (name==='children') continue;
181202
if (!(opts && opts.allAttributes) && (name==='key' || name==='ref')) continue;
203+
204+
if (opts.jsx) {
205+
if (typeof v!=='string') {
206+
preactPlugin.context = context;
207+
preactPlugin.opts = opts;
208+
v = prettyFormat(v, prettyFormatOpts);
209+
if (~v.indexOf('\n')) {
210+
v = `${indent('\n'+v, indentChar)}\n`;
211+
}
212+
s += indent(`\n${name}={${v}}`, indentChar);
213+
}
214+
else {
215+
s += `\n${indentChar}${name}="${encodeEntities(v)}"`;
216+
}
217+
continue;
218+
}
219+
182220
if (name==='className') {
183221
if (attributes['class']) continue;
184222
name = 'class';
185223
}
186224
if (name==='style' && v && typeof v==='object') {
187225
v = styleObjToCss(v);
188226
}
227+
189228
if (name==='dangerouslySetInnerHTML') {
190229
html = v && v.__html;
191230
}
@@ -203,7 +242,25 @@ export default function renderToString(vnode, context, opts, inner) {
203242
}
204243
}
205244

206-
s += '>';
245+
// account for >1 multiline attribute
246+
let sub = s.replace(/^\n\s*/, ' ');
247+
if (sub!==s && !~sub.indexOf('\n')) s = sub;
248+
else if (~s.indexOf('\n')) s += '\n';
249+
// s += '>';
250+
251+
s = `<${nodeName}${s}>`;
252+
253+
// if (opts && opts.jsx) {
254+
// if (isLargeString(s, 80, true) || s.split('\n').length>3) {
255+
// s = `<${nodeName}${s}\n>`;
256+
// }
257+
// else {
258+
// s = `<${nodeName}${s}\n>`;
259+
// }
260+
// }
261+
// else {
262+
// s = `<${nodeName}${s}>`;
263+
// }
207264

208265
if (html) {
209266
// if multiline, indent.
@@ -216,7 +273,7 @@ export default function renderToString(vnode, context, opts, inner) {
216273
let len = children && children.length;
217274
if (len) {
218275
let pieces = [],
219-
hasLarge = false;
276+
hasLarge = ~s.indexOf('\n');
220277
for (let i=0; i<len; i++) {
221278
let child = children[i];
222279
if (!falsey(child)) {

test/jsx.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { render, shallowRender } from '../src';
2+
import { h, Component } from 'preact';
3+
import chai, { expect } from 'chai';
4+
import { spy, match } from 'sinon';
5+
import sinonChai from 'sinon-chai';
6+
chai.use(sinonChai);
7+
8+
// remove leading whitespace from tagged template literal
9+
// let dedent = ([str]) => str.replace(/^\n+/g,'').split( '\n'+str.match(/^\n+?(\s+)/)[1] ).join('\n');
10+
function dedent([str]) {
11+
// str = str.replace(/^\n+/g,'\n');
12+
return str.split( '\n'+str.match(/^\n*(\s+)/)[1] ).join('\n').replace(/(^\n+|\n+\s*$)/g, '');
13+
}
14+
15+
describe('jsx', () => {
16+
let renderJsx = jsx => render(jsx, {}, { jsx:true, xml:true, pretty:' ' }).replace(/ {2}/g, '\t');
17+
18+
it('should render as JSX', () => {
19+
let rendered = renderJsx(
20+
<section>
21+
<a href="/foo">foo</a>
22+
bar
23+
<p>hello</p>
24+
</section>
25+
);
26+
27+
expect(rendered).to.equal(dedent`
28+
<section>
29+
<a href="/foo">foo</a>
30+
bar
31+
<p>hello</p>
32+
</section>
33+
`);
34+
});
35+
36+
it('should render JSX attributes inline if short enough', () => {
37+
expect(renderJsx(
38+
<a b="c">bar</a>
39+
)).to.equal(dedent`
40+
<a b="c">bar</a>
41+
`);
42+
43+
expect(renderJsx(
44+
<a b>bar</a>
45+
)).to.equal(dedent`
46+
<a b={true}>bar</a>
47+
`);
48+
49+
expect(renderJsx(
50+
<a b={false}>bar</a>
51+
)).to.equal(dedent`
52+
<a b={false}>bar</a>
53+
`);
54+
});
55+
56+
it('should render JSX attributes as multiline if complex', () => {
57+
expect(renderJsx(
58+
<a b={['a','b','c','d']}>bar</a>
59+
)).to.equal(dedent`
60+
<a
61+
b={
62+
Array [
63+
"a",
64+
"b",
65+
"c",
66+
"d"
67+
]
68+
}
69+
>
70+
bar
71+
</a>
72+
`);
73+
});
74+
75+
it('should render attributes containing VNodes', () => {
76+
expect(renderJsx(
77+
<a b={<c />}>bar</a>
78+
)).to.equal(dedent`
79+
<a b={<c />}>bar</a>
80+
`);
81+
82+
expect(renderJsx(
83+
<a b={[
84+
<c />,
85+
<d f="g" />
86+
]}>bar</a>
87+
)).to.equal(dedent`
88+
<a
89+
b={
90+
Array [
91+
<c />,
92+
<d f="g" />
93+
]
94+
}
95+
>
96+
bar
97+
</a>
98+
`);
99+
});
100+
});

0 commit comments

Comments
 (0)