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

Call same printChildren as for React in HTMLElement plugin #4275

Merged
merged 5 commits into from
Aug 23, 2017
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
203 changes: 162 additions & 41 deletions packages/pretty-format/src/__tests__/html_element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ describe('HTMLElement Plugin', () => {
it('supports an HTML element with attribute and text content', () => {
const parent = document.createElement('div');
parent.setAttribute('style', 'color: #99424F');
parent.innerHTML = 'Jest';
const text = document.createTextNode('Jest');
parent.appendChild(text);

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

it('supports an element with text content', () => {
const parent = document.createElement('div');
parent.innerHTML = 'texty texty';
const child = document.createTextNode('texty texty');
parent.appendChild(child);

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

it('supports nested elements with attribute and text content', () => {
const parent = document.createElement('div');
const child = document.createElement('span');
parent.appendChild(child);

child.setAttribute('style', 'color: #99424F');
const text = document.createTextNode('Jest');
child.appendChild(text);

expect(parent).toPrettyPrintTo(
'<div>\n <span\n style="color: #99424F"\n >\n Jest\n </span>\n</div>',
);
});

it('supports nested elements with text content', () => {
const parent = document.createElement('div');
const child = document.createElement('span');
Expand Down Expand Up @@ -128,57 +144,162 @@ describe('HTMLElement Plugin', () => {
);
});

it('trims unnecessary whitespace', () => {
const parent = document.createElement('div');
parent.innerHTML = `
<span>
some
apple
pseudo-multilne text
</span>
<span>text</span>
`;
it('supports multiline text node in pre', () => {
const parent = document.createElement('pre');
parent.innerHTML = [
// prettier-ignore
'function sum(a, b) {',
' return a + b;',
'}',
].join('\n');

// Ouch. Two lines of text have same indentation for different reason:
// First line of text node because it is at child level.
// Second line of text node because they are in its content.
expect(parent).toPrettyPrintTo(
// prettier-ignore
[
'<pre>',
' function sum(a, b) {',
' return a + b;',
'}',
'</pre>'
].join('\n'),
);
});

it('supports multiline text node preceding span in pre', () => {
const parent = document.createElement('pre');
parent.innerHTML = [
'<span class="token keyword">function</span> sum(a, b) {',
' <span class="token keyword">return</span> a + b;',
'}',
].join('\n');

expect(parent).toPrettyPrintTo(
[
'<div>',
' <span>',
' some apple pseudo-multilne text',
'<pre>',
' <span',
' class="token keyword"',
' >',
' function',
' </span>',
' <span>',
' text',
' sum(a, b) {',
' ',
' <span',
' class="token keyword"',
' >',
' return',
' </span>',
'</div>',
' a + b;',
'}',
'</pre>',
].join('\n'),
);
});

it('supports text node', () => {
const parent = document.createElement('div');
parent.innerHTML = 'some <span>text</span>';
it('supports multiline text node in textarea', () => {
const textarea = document.createElement('textarea');
textarea.setAttribute('name', 'tagline');
textarea.innerHTML = `Painless.
JavaScript.
Testing.`;

expect(textarea).toPrettyPrintTo(
[
'<textarea',
' name="tagline"',
'>',
' Painless.',
'JavaScript.',
'Testing.',
'</textarea>',
].join('\n'),
);
});

it('supports empty text node', () => {
// React 16 does not render text in comments (see below)
const parent = document.createElement('span');
const text = document.createTextNode('');
parent.appendChild(text);
const abbr = document.createElement('abbr');
abbr.setAttribute('title', 'meter');
abbr.innerHTML = 'm';
parent.appendChild(abbr);

expect(parent).toPrettyPrintTo(
[
'<span>',
' ',
' <abbr',
' title="meter"',
' >',
' m',
' </abbr>',
'</span>',
].join('\n'),
);
});

it('supports non-empty text node', () => {
// React 16 does not render text in comments (see below)
const parent = document.createElement('p');
parent.innerHTML = [
'<strong>Jest</strong>',
' means ',
'<em>painless</em>',
' Javascript testing',
].join('');

// prettier-ignore
expect(parent).toPrettyPrintTo([
'<div>',
' some ',
' <span>',
' text',
' </span>',
'</div>',
].join('\n'));
expect(parent).toPrettyPrintTo(
[
'<p>',
' <strong>',
' Jest',
' </strong>',
' means ',
' <em>',
' painless',
' </em>',
' Javascript testing',
'</p>',
].join('\n'),
);
});

it('supports comment node', () => {
const parent = document.createElement('div');
parent.innerHTML = 'some <!-- comments -->';
// React 15 does render text in comments
const parent = document.createElement('p');
parent.innerHTML = [
'<strong>Jest</strong>',
'<!-- react-text: 3 -->',
' means ',
'<!-- /react-text -->',
'<em>painless</em>',
'<!-- react-text: 5 -->',
' Javascript testing',
'<!-- /react-text -->',
].join('');

// prettier-ignore
expect(parent).toPrettyPrintTo([
'<div>',
' some ',
' <!-- comments -->',
'</div>',
].join('\n'));
expect(parent).toPrettyPrintTo(
[
'<p>',
' <strong>',
' Jest',
' </strong>',
' <!-- react-text: 3 -->',
' means ',
' <!-- /react-text -->',
' <em>',
' painless',
' </em>',
' <!-- react-text: 5 -->',
' Javascript testing',
' <!-- /react-text -->',
'</p>',
].join('\n'),
);
});

it('supports indentation for array of elements', () => {
Expand Down Expand Up @@ -231,13 +352,13 @@ describe('HTMLElement Plugin', () => {
' <dd>',
' to talk in a ',
' <em … />',
' manner', // plugin incorrectly trims preceding space
' manner',
' </dd>',
' <dd',
' style="color: #99424F"',
' >',
' <em … />',
' JavaScript testing', // plugin incorrectly trims preceding space
' JavaScript testing',
' </dd>',
'</dl>',
].join('\n'),
Expand Down
83 changes: 37 additions & 46 deletions packages/pretty-format/src/plugins/html_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,55 +11,49 @@
import type {Config, NewPlugin, Printer, Refs} from 'types/PrettyFormat';

import escapeHTML from './lib/escape_html';
import {printElement, printElementAsLeaf, printProps} from './lib/markup';
import {
printChildren,
printElement,
printElementAsLeaf,
printProps,
} from './lib/markup';

type Attribute = {
name: string,
value: string,
};

type HTMLElement = {
type Element = {
attributes: Array<Attribute>,
childNodes: Array<HTMLElement | HTMLText | HTMLComment>,
childNodes: Array<Element | Text | Comment>,
nodeType: 1,
tagName: string,
textContent: string,
};
type HTMLText = {
type Text = {
data: string,
nodeType: 3,
};
type HTMLComment = {
type Comment = {
data: string,
nodeType: 8,
};

const HTML_ELEMENT_REGEXP = /(HTML\w*?Element)|Text|Comment/;
const ELEMENT_NODE = 1;
const TEXT_NODE = 3;
const COMMENT_NODE = 8;

export const test = (val: any) =>
val !== undefined &&
val !== null &&
(val.nodeType === 1 || val.nodeType === 3 || val.nodeType === 8) &&
val.constructor !== undefined &&
val.constructor.name !== undefined &&
HTML_ELEMENT_REGEXP.test(val.constructor.name);
const ELEMENT_REGEXP = /^HTML\w*?Element$/;

// Return empty string if children is empty.
function printChildren(children, config, indentation, depth, refs, printer) {
const colors = config.colors;
return children
.map(
node =>
typeof node === 'string'
? colors.content.open + escapeHTML(node) + colors.content.close
: printer(node, config, indentation, depth, refs),
)
.filter(value => value.trim().length)
.map(value => config.spacingOuter + indentation + value)
.join('');
}
const testNode = (nodeType: any, name: any) =>
(nodeType === ELEMENT_NODE && ELEMENT_REGEXP.test(name)) ||
(nodeType === TEXT_NODE && name === 'Text') ||
(nodeType === COMMENT_NODE && name === 'Comment');

const getType = element => element.tagName.toLowerCase();
export const test = (val: any) =>
val &&
val.constructor &&
val.constructor.name &&
testNode(val.nodeType, val.constructor.name);

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

export const serialize = (
element: HTMLElement | HTMLText | HTMLComment,
node: Element | Text | Comment,
config: Config,
indentation: string,
depth: number,
refs: Refs,
printer: Printer,
): string => {
if (element.nodeType === 3) {
return element.data
.split('\n')
.map(text => text.trimLeft())
.filter(text => text.length)
.join(' ');
const colors = config.colors;
if (node.nodeType === TEXT_NODE) {
return colors.content.open + escapeHTML(node.data) + colors.content.close;
}

const colors = config.colors;
if (element.nodeType === 8) {
if (node.nodeType === COMMENT_NODE) {
return (
colors.comment.open +
'<!-- ' +
element.data.trim() +
' -->' +
'<!--' +
escapeHTML(node.data) +
'-->' +
colors.comment.close
);
}

const type = node.tagName.toLowerCase();
if (++depth > config.maxDepth) {
return printElementAsLeaf(getType(element), config);
return printElementAsLeaf(type, config);
}

return printElement(
getType(element),
type,
printProps(
Array.prototype.map.call(element.attributes, keysMapper).sort(),
Array.prototype.reduce.call(element.attributes, propsReducer, {}),
Array.prototype.map.call(node.attributes, keysMapper).sort(),
Array.prototype.reduce.call(node.attributes, propsReducer, {}),
config,
indentation + config.indent,
depth,
refs,
printer,
),
printChildren(
Array.prototype.slice.call(element.childNodes),
Array.prototype.slice.call(node.childNodes),
config,
indentation + config.indent,
depth,
Expand Down