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

Replace print with serialize in HTMLElement plugin #4215

Merged
merged 2 commits into from
Aug 8, 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
74 changes: 74 additions & 0 deletions packages/pretty-format/src/__tests__/html_element.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ describe('HTMLElement Plugin', () => {
expect(parent).toPrettyPrintTo('<div\n class="classy"\n id="123"\n/>');
});

it('supports an HTML element with attribute and text content', () => {
const parent = document.createElement('div');
parent.setAttribute('style', 'color: #99424F');
parent.innerHTML = 'Jest';

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

it('supports an element with text content', () => {
const parent = document.createElement('div');
parent.innerHTML = 'texty texty';
Expand Down Expand Up @@ -170,4 +180,68 @@ describe('HTMLElement Plugin', () => {
'</div>',
].join('\n'));
});

it('supports indentation for array of elements', () => {
// For example, Array.prototype.slice.call(document.getElementsByTagName(…))
const dd1 = document.createElement('dd');
dd1.innerHTML = 'to talk in a playful manner';

const dd2 = document.createElement('dd');
dd2.innerHTML = 'painless JavaScript testing';
dd2.setAttribute('style', 'color: #99424F');

expect([dd1, dd2]).toPrettyPrintTo(
[
'Array [',
' <dd>',
' to talk in a playful manner',
' </dd>,',
' <dd',
' style="color: #99424F"',
' >',
' painless JavaScript testing',
' </dd>,',
']',
].join('\n'),
);
});

it('supports maxDepth option', () => {
const dt = document.createElement('dt');
dt.innerHTML = 'jest';

const dd1 = document.createElement('dd');
dd1.innerHTML = 'to talk in a <em>playful</em> manner';

const dd2 = document.createElement('dd');
dd2.innerHTML = '<em>painless</em> JavaScript testing';
dd2.setAttribute('style', 'color: #99424F');

const dl = document.createElement('dl');
dl.appendChild(dt);
dl.appendChild(dd1);
dl.appendChild(dd2);

expect(dl).toPrettyPrintTo(
[
'<dl>',
' <dt>',
' jest',
' </dt>',
' <dd>',
' to talk in a ',
' <em … />',
' manner', // plugin incorrectly trims preceding space
' </dd>',
' <dd',
' style="color: #99424F"',
' >',
' <em … />',
' JavaScript testing', // plugin incorrectly trims preceding space
' </dd>',
'</dl>',
].join('\n'),
{maxDepth: 2},
);
});
});
162 changes: 65 additions & 97 deletions packages/pretty-format/src/plugins/html_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@
* @flow
*/

import type {
Colors,
Indent,
PluginOptions,
Print,
Plugin,
} from 'types/PrettyFormat';
import type {Config, NewPlugin, Printer, Refs} from 'types/PrettyFormat';

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

type Attribute = {
name: string,
Expand All @@ -34,79 +29,63 @@ type HTMLText = {
data: string,
nodeType: 3,
};

type HTMLComment = {
data: string,
nodeType: 8,
};

const HTML_ELEMENT_REGEXP = /(HTML\w*?Element)|Text|Comment/;
export const test = isHTMLElement;

function isHTMLElement(value: any) {
return (
value !== undefined &&
value !== null &&
(value.nodeType === 1 || value.nodeType === 3 || value.nodeType === 8) &&
value.constructor !== undefined &&
value.constructor.name !== undefined &&
HTML_ELEMENT_REGEXP.test(value.constructor.name)
);
}

function printChildren(flatChildren, print, indent, colors, opts) {
return flatChildren
.map(node => {
if (typeof node === 'string') {
return colors.content.open + escapeHTML(node) + colors.content.close;
} else {
return print(node);
}
})
.filter(value => value.trim().length)
.join(opts.edgeSpacing);
}

function printAttributes(
attributes: Array<Attribute>,
print,
indent,
colors,
opts,
) {
return attributes
.sort(
(attributeA, attributeB) =>
attributeA.name === attributeB.name
? 0
: attributeA.name < attributeB.name ? -1 : 1,
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);

// 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),
)
.map(attribute => {
return (
opts.spacing +
indent(colors.prop.open + attribute.name + colors.prop.close + '=') +
colors.value.open +
print(attribute.value) +
colors.value.close
);
})
.filter(value => value.trim().length)
.map(value => config.spacingOuter + indentation + value)
.join('');
}

export const print = (
const getType = element => element.tagName.toLowerCase();

// Convert array of attribute objects to keys array and props object.
const keysMapper = attribute => attribute.name;
const propsReducer = (props, attribute) => {
props[attribute.name] = attribute.value;
return props;
};

export const serialize = (
element: HTMLElement | HTMLText | HTMLComment,
print: Print,
indent: Indent,
opts: PluginOptions,
colors: Colors,
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(' ');
} else if (element.nodeType === 8) {
}

const colors = config.colors;
if (element.nodeType === 8) {
return (
colors.comment.open +
'<!-- ' +
Expand All @@ -116,43 +95,32 @@ export const print = (
);
}

let result = colors.tag.open + '<';
const elementName = element.tagName.toLowerCase();
result += elementName + colors.tag.close;

const hasAttributes = element.attributes && element.attributes.length;
if (hasAttributes) {
const attributes = Array.prototype.slice.call(element.attributes);
result += printAttributes(attributes, print, indent, colors, opts);
}

const flatChildren = Array.prototype.slice.call(element.childNodes);
if (!flatChildren.length && element.textContent) {
flatChildren.push(element.textContent);
if (++depth > config.maxDepth) {
return printElementAsLeaf(getType(element), config);
}

const closeInNewLine = hasAttributes && !opts.min;
if (flatChildren.length) {
const children = printChildren(flatChildren, print, indent, colors, opts);
result +=
colors.tag.open +
(closeInNewLine ? '\n' : '') +
'>' +
colors.tag.close +
opts.edgeSpacing +
indent(children) +
opts.edgeSpacing +
colors.tag.open +
'</' +
elementName +
'>' +
colors.tag.close;
} else {
result +=
colors.tag.open + (closeInNewLine ? '\n' : ' ') + '/>' + colors.tag.close;
}

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

export default ({print, test}: Plugin);
export default ({serialize, test}: NewPlugin);