Skip to content

Commit 27c5a97

Browse files
pedrottimarkcpojer
authored andcommitted
Call same printChildren as for React in HTMLElement plugin (#4275)
* Call same printChildren as for React in HTMLElement plugins * Call createTextNode in another existing test * Simplify regexp and factor testNode out of test * Remove HTML from type names and rename element arg as node
1 parent d8aa77c commit 27c5a97

2 files changed

Lines changed: 199 additions & 87 deletions

File tree

packages/pretty-format/src/__tests__/html_element.test.js

Lines changed: 162 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ describe('HTMLElement Plugin', () => {
6464
it('supports an HTML element with attribute and text content', () => {
6565
const parent = document.createElement('div');
6666
parent.setAttribute('style', 'color: #99424F');
67-
parent.innerHTML = 'Jest';
67+
const text = document.createTextNode('Jest');
68+
parent.appendChild(text);
6869

6970
expect(parent).toPrettyPrintTo(
7071
'<div\n style="color: #99424F"\n>\n Jest\n</div>',
@@ -73,7 +74,8 @@ describe('HTMLElement Plugin', () => {
7374

7475
it('supports an element with text content', () => {
7576
const parent = document.createElement('div');
76-
parent.innerHTML = 'texty texty';
77+
const child = document.createTextNode('texty texty');
78+
parent.appendChild(child);
7779

7880
expect(parent).toPrettyPrintTo('<div>\n texty texty\n</div>');
7981
});
@@ -99,6 +101,20 @@ describe('HTMLElement Plugin', () => {
99101
);
100102
});
101103

104+
it('supports nested elements with attribute and text content', () => {
105+
const parent = document.createElement('div');
106+
const child = document.createElement('span');
107+
parent.appendChild(child);
108+
109+
child.setAttribute('style', 'color: #99424F');
110+
const text = document.createTextNode('Jest');
111+
child.appendChild(text);
112+
113+
expect(parent).toPrettyPrintTo(
114+
'<div>\n <span\n style="color: #99424F"\n >\n Jest\n </span>\n</div>',
115+
);
116+
});
117+
102118
it('supports nested elements with text content', () => {
103119
const parent = document.createElement('div');
104120
const child = document.createElement('span');
@@ -128,57 +144,162 @@ describe('HTMLElement Plugin', () => {
128144
);
129145
});
130146

131-
it('trims unnecessary whitespace', () => {
132-
const parent = document.createElement('div');
133-
parent.innerHTML = `
134-
<span>
135-
some
136-
apple
137-
pseudo-multilne text
138-
</span>
139-
<span>text</span>
140-
`;
147+
it('supports multiline text node in pre', () => {
148+
const parent = document.createElement('pre');
149+
parent.innerHTML = [
150+
// prettier-ignore
151+
'function sum(a, b) {',
152+
' return a + b;',
153+
'}',
154+
].join('\n');
155+
156+
// Ouch. Two lines of text have same indentation for different reason:
157+
// First line of text node because it is at child level.
158+
// Second line of text node because they are in its content.
159+
expect(parent).toPrettyPrintTo(
160+
// prettier-ignore
161+
[
162+
'<pre>',
163+
' function sum(a, b) {',
164+
' return a + b;',
165+
'}',
166+
'</pre>'
167+
].join('\n'),
168+
);
169+
});
170+
171+
it('supports multiline text node preceding span in pre', () => {
172+
const parent = document.createElement('pre');
173+
parent.innerHTML = [
174+
'<span class="token keyword">function</span> sum(a, b) {',
175+
' <span class="token keyword">return</span> a + b;',
176+
'}',
177+
].join('\n');
141178

142179
expect(parent).toPrettyPrintTo(
143180
[
144-
'<div>',
145-
' <span>',
146-
' some apple pseudo-multilne text',
181+
'<pre>',
182+
' <span',
183+
' class="token keyword"',
184+
' >',
185+
' function',
147186
' </span>',
148-
' <span>',
149-
' text',
187+
' sum(a, b) {',
188+
' ',
189+
' <span',
190+
' class="token keyword"',
191+
' >',
192+
' return',
150193
' </span>',
151-
'</div>',
194+
' a + b;',
195+
'}',
196+
'</pre>',
152197
].join('\n'),
153198
);
154199
});
155200

156-
it('supports text node', () => {
157-
const parent = document.createElement('div');
158-
parent.innerHTML = 'some <span>text</span>';
201+
it('supports multiline text node in textarea', () => {
202+
const textarea = document.createElement('textarea');
203+
textarea.setAttribute('name', 'tagline');
204+
textarea.innerHTML = `Painless.
205+
JavaScript.
206+
Testing.`;
207+
208+
expect(textarea).toPrettyPrintTo(
209+
[
210+
'<textarea',
211+
' name="tagline"',
212+
'>',
213+
' Painless.',
214+
'JavaScript.',
215+
'Testing.',
216+
'</textarea>',
217+
].join('\n'),
218+
);
219+
});
220+
221+
it('supports empty text node', () => {
222+
// React 16 does not render text in comments (see below)
223+
const parent = document.createElement('span');
224+
const text = document.createTextNode('');
225+
parent.appendChild(text);
226+
const abbr = document.createElement('abbr');
227+
abbr.setAttribute('title', 'meter');
228+
abbr.innerHTML = 'm';
229+
parent.appendChild(abbr);
230+
231+
expect(parent).toPrettyPrintTo(
232+
[
233+
'<span>',
234+
' ',
235+
' <abbr',
236+
' title="meter"',
237+
' >',
238+
' m',
239+
' </abbr>',
240+
'</span>',
241+
].join('\n'),
242+
);
243+
});
244+
245+
it('supports non-empty text node', () => {
246+
// React 16 does not render text in comments (see below)
247+
const parent = document.createElement('p');
248+
parent.innerHTML = [
249+
'<strong>Jest</strong>',
250+
' means ',
251+
'<em>painless</em>',
252+
' Javascript testing',
253+
].join('');
159254

160-
// prettier-ignore
161-
expect(parent).toPrettyPrintTo([
162-
'<div>',
163-
' some ',
164-
' <span>',
165-
' text',
166-
' </span>',
167-
'</div>',
168-
].join('\n'));
255+
expect(parent).toPrettyPrintTo(
256+
[
257+
'<p>',
258+
' <strong>',
259+
' Jest',
260+
' </strong>',
261+
' means ',
262+
' <em>',
263+
' painless',
264+
' </em>',
265+
' Javascript testing',
266+
'</p>',
267+
].join('\n'),
268+
);
169269
});
170270

171271
it('supports comment node', () => {
172-
const parent = document.createElement('div');
173-
parent.innerHTML = 'some <!-- comments -->';
272+
// React 15 does render text in comments
273+
const parent = document.createElement('p');
274+
parent.innerHTML = [
275+
'<strong>Jest</strong>',
276+
'<!-- react-text: 3 -->',
277+
' means ',
278+
'<!-- /react-text -->',
279+
'<em>painless</em>',
280+
'<!-- react-text: 5 -->',
281+
' Javascript testing',
282+
'<!-- /react-text -->',
283+
].join('');
174284

175-
// prettier-ignore
176-
expect(parent).toPrettyPrintTo([
177-
'<div>',
178-
' some ',
179-
' <!-- comments -->',
180-
'</div>',
181-
].join('\n'));
285+
expect(parent).toPrettyPrintTo(
286+
[
287+
'<p>',
288+
' <strong>',
289+
' Jest',
290+
' </strong>',
291+
' <!-- react-text: 3 -->',
292+
' means ',
293+
' <!-- /react-text -->',
294+
' <em>',
295+
' painless',
296+
' </em>',
297+
' <!-- react-text: 5 -->',
298+
' Javascript testing',
299+
' <!-- /react-text -->',
300+
'</p>',
301+
].join('\n'),
302+
);
182303
});
183304

184305
it('supports indentation for array of elements', () => {
@@ -231,13 +352,13 @@ describe('HTMLElement Plugin', () => {
231352
' <dd>',
232353
' to talk in a ',
233354
' <em … />',
234-
' manner', // plugin incorrectly trims preceding space
355+
' manner',
235356
' </dd>',
236357
' <dd',
237358
' style="color: #99424F"',
238359
' >',
239360
' <em … />',
240-
' JavaScript testing', // plugin incorrectly trims preceding space
361+
' JavaScript testing',
241362
' </dd>',
242363
'</dl>',
243364
].join('\n'),

packages/pretty-format/src/plugins/html_element.js

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,55 +11,49 @@
1111
import type {Config, NewPlugin, Printer, Refs} from 'types/PrettyFormat';
1212

1313
import escapeHTML from './lib/escape_html';
14-
import {printElement, printElementAsLeaf, printProps} from './lib/markup';
14+
import {
15+
printChildren,
16+
printElement,
17+
printElementAsLeaf,
18+
printProps,
19+
} from './lib/markup';
1520

1621
type Attribute = {
1722
name: string,
1823
value: string,
1924
};
2025

21-
type HTMLElement = {
26+
type Element = {
2227
attributes: Array<Attribute>,
23-
childNodes: Array<HTMLElement | HTMLText | HTMLComment>,
28+
childNodes: Array<Element | Text | Comment>,
2429
nodeType: 1,
2530
tagName: string,
26-
textContent: string,
2731
};
28-
type HTMLText = {
32+
type Text = {
2933
data: string,
3034
nodeType: 3,
3135
};
32-
type HTMLComment = {
36+
type Comment = {
3337
data: string,
3438
nodeType: 8,
3539
};
3640

37-
const HTML_ELEMENT_REGEXP = /(HTML\w*?Element)|Text|Comment/;
41+
const ELEMENT_NODE = 1;
42+
const TEXT_NODE = 3;
43+
const COMMENT_NODE = 8;
3844

39-
export const test = (val: any) =>
40-
val !== undefined &&
41-
val !== null &&
42-
(val.nodeType === 1 || val.nodeType === 3 || val.nodeType === 8) &&
43-
val.constructor !== undefined &&
44-
val.constructor.name !== undefined &&
45-
HTML_ELEMENT_REGEXP.test(val.constructor.name);
45+
const ELEMENT_REGEXP = /^HTML\w*?Element$/;
4646

47-
// Return empty string if children is empty.
48-
function printChildren(children, config, indentation, depth, refs, printer) {
49-
const colors = config.colors;
50-
return children
51-
.map(
52-
node =>
53-
typeof node === 'string'
54-
? colors.content.open + escapeHTML(node) + colors.content.close
55-
: printer(node, config, indentation, depth, refs),
56-
)
57-
.filter(value => value.trim().length)
58-
.map(value => config.spacingOuter + indentation + value)
59-
.join('');
60-
}
47+
const testNode = (nodeType: any, name: any) =>
48+
(nodeType === ELEMENT_NODE && ELEMENT_REGEXP.test(name)) ||
49+
(nodeType === TEXT_NODE && name === 'Text') ||
50+
(nodeType === COMMENT_NODE && name === 'Comment');
6151

62-
const getType = element => element.tagName.toLowerCase();
52+
export const test = (val: any) =>
53+
val &&
54+
val.constructor &&
55+
val.constructor.name &&
56+
testNode(val.nodeType, val.constructor.name);
6357

6458
// Convert array of attribute objects to keys array and props object.
6559
const keysMapper = attribute => attribute.name;
@@ -69,49 +63,46 @@ const propsReducer = (props, attribute) => {
6963
};
7064

7165
export const serialize = (
72-
element: HTMLElement | HTMLText | HTMLComment,
66+
node: Element | Text | Comment,
7367
config: Config,
7468
indentation: string,
7569
depth: number,
7670
refs: Refs,
7771
printer: Printer,
7872
): string => {
79-
if (element.nodeType === 3) {
80-
return element.data
81-
.split('\n')
82-
.map(text => text.trimLeft())
83-
.filter(text => text.length)
84-
.join(' ');
73+
const colors = config.colors;
74+
if (node.nodeType === TEXT_NODE) {
75+
return colors.content.open + escapeHTML(node.data) + colors.content.close;
8576
}
8677

87-
const colors = config.colors;
88-
if (element.nodeType === 8) {
78+
if (node.nodeType === COMMENT_NODE) {
8979
return (
9080
colors.comment.open +
91-
'<!-- ' +
92-
element.data.trim() +
93-
' -->' +
81+
'<!--' +
82+
escapeHTML(node.data) +
83+
'-->' +
9484
colors.comment.close
9585
);
9686
}
9787

88+
const type = node.tagName.toLowerCase();
9889
if (++depth > config.maxDepth) {
99-
return printElementAsLeaf(getType(element), config);
90+
return printElementAsLeaf(type, config);
10091
}
10192

10293
return printElement(
103-
getType(element),
94+
type,
10495
printProps(
105-
Array.prototype.map.call(element.attributes, keysMapper).sort(),
106-
Array.prototype.reduce.call(element.attributes, propsReducer, {}),
96+
Array.prototype.map.call(node.attributes, keysMapper).sort(),
97+
Array.prototype.reduce.call(node.attributes, propsReducer, {}),
10798
config,
10899
indentation + config.indent,
109100
depth,
110101
refs,
111102
printer,
112103
),
113104
printChildren(
114-
Array.prototype.slice.call(element.childNodes),
105+
Array.prototype.slice.call(node.childNodes),
115106
config,
116107
indentation + config.indent,
117108
depth,

0 commit comments

Comments
 (0)