Skip to content

Commit

Permalink
fix(mock-doc): do not pretty print whitespace senstive elements
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Sep 16, 2020
1 parent 10ea2fb commit de0dc65
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 19 deletions.
94 changes: 75 additions & 19 deletions src/mock-doc/serialize-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,14 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
const ignoreTag = opts.excludeTags != null && opts.excludeTags.includes(tagName);

if (ignoreTag === false) {
if (opts.newLines) {
const isWithinWhitespaceSensitiveNode =
opts.newLines || opts.indentSpaces > 0 ? isWithinWhitespaceSensitive(node) : false;
if (opts.newLines && !isWithinWhitespaceSensitiveNode) {
output.text.push('\n');
output.currentLineWidth = 0;
}

if (opts.indentSpaces > 0) {
if (opts.indentSpaces > 0 && !isWithinWhitespaceSensitiveNode) {
for (let i = 0; i < output.indent; i++) {
output.text.push(' ');
}
Expand All @@ -100,7 +102,10 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
output.currentLineWidth += tagName.length + 1;

const attrsLength = (node as HTMLElement).attributes.length;
const attributes = opts.prettyHtml && attrsLength > 1 ? cloneAttributes((node as HTMLElement).attributes as any, true) : (node as Element).attributes;
const attributes =
opts.prettyHtml && attrsLength > 1
? cloneAttributes((node as HTMLElement).attributes as any, true)
: (node as Element).attributes;

for (let i = 0; i < attrsLength; i++) {
const attr = attributes.item(i);
Expand Down Expand Up @@ -173,7 +178,10 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
if ((node as Element).hasAttribute('style')) {
const cssText = (node as HTMLElement).style.cssText;

if (opts.approximateLineWidth > 0 && output.currentLineWidth + cssText.length + 10 > opts.approximateLineWidth) {
if (
opts.approximateLineWidth > 0 &&
output.currentLineWidth + cssText.length + 10 > opts.approximateLineWidth
) {
output.text.push(`\nstyle="${cssText}">`);
output.currentLineWidth = 0;
} else {
Expand All @@ -194,7 +202,10 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S

if (
opts.newLines &&
(node.childNodes.length === 0 || (node.childNodes.length === 1 && node.childNodes[0].nodeType === NODE_TYPES.TEXT_NODE && node.childNodes[0].nodeValue.trim() === ''))
(node.childNodes.length === 0 ||
(node.childNodes.length === 1 &&
node.childNodes[0].nodeType === NODE_TYPES.TEXT_NODE &&
node.childNodes[0].nodeValue.trim() === ''))
) {
output.text.push('\n');
output.currentLineWidth = 0;
Expand All @@ -207,11 +218,16 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
}

if (opts.excludeTagContent == null || opts.excludeTagContent.includes(tagName) === false) {
const childNodes = tagName === 'template' ? (((node as any) as HTMLTemplateElement).content.childNodes as any) : node.childNodes;
const childNodes =
tagName === 'template' ? (((node as any) as HTMLTemplateElement).content.childNodes as any) : node.childNodes;
const childNodeLength = childNodes.length;

if (childNodeLength > 0) {
if (childNodeLength === 1 && childNodes[0].nodeType === NODE_TYPES.TEXT_NODE && (typeof childNodes[0].nodeValue !== 'string' || childNodes[0].nodeValue.trim() === '')) {
if (
childNodeLength === 1 &&
childNodes[0].nodeType === NODE_TYPES.TEXT_NODE &&
(typeof childNodes[0].nodeValue !== 'string' || childNodes[0].nodeValue.trim() === '')
) {
// skip over empty text nodes
} else {
if (opts.indentSpaces > 0 && ignoreTag === false) {
Expand All @@ -223,12 +239,14 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
}

if (ignoreTag === false) {
if (opts.newLines) {
const isWithinWhitespaceSensitiveNode =
opts.newLines || opts.indentSpaces > 0 ? isWithinWhitespaceSensitive(node) : false;
if (opts.newLines && !isWithinWhitespaceSensitiveNode) {
output.text.push('\n');
output.currentLineWidth = 0;
}

if (opts.indentSpaces > 0) {
if (opts.indentSpaces > 0 && !isWithinWhitespaceSensitiveNode) {
output.indent = output.indent - opts.indentSpaces;
for (let i = 0; i < output.indent; i++) {
output.text.push(' ');
Expand Down Expand Up @@ -287,12 +305,14 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
}
} else {
// this text node has text content
if (opts.newLines) {
const isWithinWhitespaceSensitiveNode =
opts.newLines || opts.indentSpaces > 0 || opts.prettyHtml ? isWithinWhitespaceSensitive(node) : false;
if (opts.newLines && !isWithinWhitespaceSensitiveNode) {
output.text.push('\n');
output.currentLineWidth = 0;
}

if (opts.indentSpaces > 0) {
if (opts.indentSpaces > 0 && !isWithinWhitespaceSensitiveNode) {
for (let i = 0; i < output.indent; i++) {
output.text.push(' ');
}
Expand All @@ -303,7 +323,10 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
if (textContentLength > 0) {
// this text node has text content

const parentTagName = node.parentNode != null && node.parentNode.nodeType === NODE_TYPES.ELEMENT_NODE ? node.parentNode.nodeName : null;
const parentTagName =
node.parentNode != null && node.parentNode.nodeType === NODE_TYPES.ELEMENT_NODE
? node.parentNode.nodeName
: null;
if (NON_ESCAPABLE_CONTENT.has(parentTagName)) {
// this text node cannot have its content escaped since it's going
// into an element like <style> or <script>
Expand All @@ -316,7 +339,7 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
output.currentLineWidth += textContentLength;
} else {
// this text node is going into a normal element and html can be escaped
if (opts.prettyHtml) {
if (opts.prettyHtml && !isWithinWhitespaceSensitiveNode) {
// pretty print the text node
output.text.push(escapeString(textContent.replace(/\s\s+/g, ' ').trim(), false));
output.currentLineWidth += textContentLength;
Expand All @@ -334,7 +357,10 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
textContentLength = textContent.length;
if (textContentLength > 1) {
if (/\s/.test(textContent.charAt(textContentLength - 1))) {
if (opts.approximateLineWidth > 0 && output.currentLineWidth + textContentLength > opts.approximateLineWidth) {
if (
opts.approximateLineWidth > 0 &&
output.currentLineWidth + textContentLength > opts.approximateLineWidth
) {
textContent = textContent.trimRight() + '\n';
output.currentLineWidth = 0;
} else {
Expand Down Expand Up @@ -365,12 +391,14 @@ function serializeToHtml(node: Node, opts: SerializeNodeToHtmlOptions, output: S
}
}

if (opts.newLines) {
const isWithinWhitespaceSensitiveNode =
opts.newLines || opts.indentSpaces > 0 ? isWithinWhitespaceSensitive(node) : false;
if (opts.newLines && !isWithinWhitespaceSensitiveNode) {
output.text.push('\n');
output.currentLineWidth = 0;
}

if (opts.indentSpaces > 0) {
if (opts.indentSpaces > 0 && !isWithinWhitespaceSensitiveNode) {
for (let i = 0; i < output.indent; i++) {
output.text.push(' ');
}
Expand Down Expand Up @@ -419,9 +447,26 @@ function isWithinWhitespaceSensitive(node: Node) {
return false;
}

/*@__PURE__*/ export const NON_ESCAPABLE_CONTENT = new Set(['STYLE', 'SCRIPT', 'IFRAME', 'NOSCRIPT', 'XMP', 'NOEMBED', 'NOFRAMES', 'PLAINTEXT']);
/*@__PURE__*/ export const NON_ESCAPABLE_CONTENT = new Set([
'STYLE',
'SCRIPT',
'IFRAME',
'NOSCRIPT',
'XMP',
'NOEMBED',
'NOFRAMES',
'PLAINTEXT',
]);

/*@__PURE__*/ export const WHITESPACE_SENSITIVE = new Set(['CODE', 'OUTPUT', 'PLAINTEXT', 'PRE', 'TEMPLATE', 'TEXTAREA']);
/*@__PURE__*/ export const WHITESPACE_SENSITIVE = new Set([
'CODE',
'OUTPUT',
'PLAINTEXT',
'PRE',
'SCRIPT',
'TEMPLATE',
'TEXTAREA',
]);

/*@__PURE__*/ const EMPTY_ELEMENTS = new Set([
'area',
Expand Down Expand Up @@ -491,7 +536,18 @@ function isWithinWhitespaceSensitive(node: Node) {
'visible',
]);

/*@__PURE__*/ const STRUCTURE_ELEMENTS = new Set(['html', 'body', 'head', 'iframe', 'meta', 'link', 'base', 'title', 'script', 'style']);
/*@__PURE__*/ const STRUCTURE_ELEMENTS = new Set([
'html',
'body',
'head',
'iframe',
'meta',
'link',
'base',
'title',
'script',
'style',
]);

interface SerializeOutput {
currentLineWidth: number;
Expand Down
36 changes: 36 additions & 0 deletions src/mock-doc/test/serialize-node.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,42 @@ describe('serializeNodeToHtml', () => {
expect(html).toBe(`<div><span>var </span><b> value </b><span> =</span><code> 88 </code>;</div>`);
});

it('do not pretty print <pre><code>', () => {
const elm = doc.createElement('div');

elm.innerHTML = `<pre><code><span>88</span></code></pre>`;

const html = serializeNodeToHtml(elm, { prettyHtml: true });
expect(html).toBe(`<pre><code><span>88</span></code></pre>`);
});

it('do not pretty print <pre><code> w/ highlights and new', () => {
const elm = doc.createElement('div');

elm.innerHTML = `<pre><code><span>install</span> cordova-plugin-purchase\nnpx cap update</code></pre>`;

const html = serializeNodeToHtml(elm, { prettyHtml: true });
expect(html).toBe(`<pre><code><span>install</span> cordova-plugin-purchase\nnpx cap update</code></pre>`);
});

it('do not pretty print <pre><code> w/ html comments', () => {
const elm = doc.createElement('div');

elm.innerHTML = `<pre><code><span><!--a-->88</span>c<!--b--></code></pre>`;

const html = serializeNodeToHtml(elm, { prettyHtml: true });
expect(html).toBe(`<pre><code><span><!--a-->88</span>c<!--b--></code></pre>`);
});

it('do not pretty print <script>', () => {
const elm = doc.createElement('div');

elm.innerHTML = `<script>value = '';</script>`;

const html = serializeNodeToHtml(elm, { prettyHtml: true });
expect(html).toBe(`<script>value = '';</script>`);
});

it('do not remove whitespace within <code>', () => {
const elm = doc.createElement('div');

Expand Down

0 comments on commit de0dc65

Please sign in to comment.