From fbb8d30a9fc2f089a5a092ff2cb234133511d2b1 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Tue, 15 Aug 2017 10:54:57 -0400 Subject: [PATCH 1/4] Call same printChildren as for React in HTMLElement plugins --- .../src/__tests__/html_element.test.js | 202 ++++++++++++++---- .../pretty-format/src/plugins/html_element.js | 50 ++--- 2 files changed, 183 insertions(+), 69 deletions(-) diff --git a/packages/pretty-format/src/__tests__/html_element.test.js b/packages/pretty-format/src/__tests__/html_element.test.js index 8d9ff42fc89a..56433a907b50 100644 --- a/packages/pretty-format/src/__tests__/html_element.test.js +++ b/packages/pretty-format/src/__tests__/html_element.test.js @@ -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 child = document.createTextNode('Jest'); + parent.appendChild(child); expect(parent).toPrettyPrintTo( '\n Jest\n', @@ -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('
\n texty texty\n
'); }); @@ -99,6 +101,19 @@ 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'); + child.innerHTML = 'Jest'; + + expect(parent).toPrettyPrintTo( + '
\n \n Jest\n \n
', + ); + }); + it('supports nested elements with text content', () => { const parent = document.createElement('div'); const child = document.createElement('span'); @@ -128,57 +143,162 @@ describe('HTMLElement Plugin', () => { ); }); - it('trims unnecessary whitespace', () => { - const parent = document.createElement('div'); - parent.innerHTML = ` - - some - apple - pseudo-multilne text - - text - `; + 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 + [ + '
',
+        '  function sum(a, b) {',
+        '  return a + b;',
+        '}',
+        '
' + ].join('\n'), + ); + }); + + it('supports multiline text node preceding span in pre', () => { + const parent = document.createElement('pre'); + parent.innerHTML = [ + 'function sum(a, b) {', + ' return a + b;', + '}', + ].join('\n'); expect(parent).toPrettyPrintTo( [ - '
', - ' ', - ' some apple pseudo-multilne text', + '
',
+        '  ',
+        '    function',
         '  ',
-        '  ',
-        '    text',
+        '   sum(a, b) {',
+        '  ',
+        '  ',
+        '    return',
         '  ',
-        '
', + ' a + b;', + '}', + '', ].join('\n'), ); }); - it('supports text node', () => { - const parent = document.createElement('div'); - parent.innerHTML = 'some text'; + it('supports multiline text node in textarea', () => { + const textarea = document.createElement('textarea'); + textarea.setAttribute('name', 'tagline'); + textarea.innerHTML = `Painless. +JavaScript. +Testing.`; + + expect(textarea).toPrettyPrintTo( + [ + '', + ' Painless.', + 'JavaScript.', + 'Testing.', + '', + ].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( + [ + '', + ' ', + ' ', + ' m', + ' ', + '', + ].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 = [ + 'Jest', + ' means ', + 'painless', + ' Javascript testing', + ].join(''); - // prettier-ignore - expect(parent).toPrettyPrintTo([ - '
', - ' some ', - ' ', - ' text', - ' ', - '
', - ].join('\n')); + expect(parent).toPrettyPrintTo( + [ + '

', + ' ', + ' Jest', + ' ', + ' means ', + ' ', + ' painless', + ' ', + ' Javascript testing', + '

', + ].join('\n'), + ); }); it('supports comment node', () => { - const parent = document.createElement('div'); - parent.innerHTML = 'some '; + // React 15 does render text in comments + const parent = document.createElement('p'); + parent.innerHTML = [ + 'Jest', + '', + ' means ', + '', + 'painless', + '', + ' Javascript testing', + '', + ].join(''); - // prettier-ignore - expect(parent).toPrettyPrintTo([ - '
', - ' some ', - ' ', - '
', - ].join('\n')); + expect(parent).toPrettyPrintTo( + [ + '

', + ' ', + ' Jest', + ' ', + ' ', + ' means ', + ' ', + ' ', + ' painless', + ' ', + ' ', + ' Javascript testing', + ' ', + '

', + ].join('\n'), + ); }); it('supports indentation for array of elements', () => { @@ -231,13 +351,13 @@ describe('HTMLElement Plugin', () => { '
', ' to talk in a ', ' ', - ' manner', // plugin incorrectly trims preceding space + ' manner', '
', ' ', ' ', - ' JavaScript testing', // plugin incorrectly trims preceding space + ' JavaScript testing', ' ', '', ].join('\n'), diff --git a/packages/pretty-format/src/plugins/html_element.js b/packages/pretty-format/src/plugins/html_element.js index 508275640d84..a4760b3d4581 100644 --- a/packages/pretty-format/src/plugins/html_element.js +++ b/packages/pretty-format/src/plugins/html_element.js @@ -11,7 +11,12 @@ 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, @@ -34,31 +39,22 @@ type HTMLComment = { nodeType: 8, }; +const ELEMENT_NODE = 1; +const TEXT_NODE = 3; +const COMMENT_NODE = 8; + const HTML_ELEMENT_REGEXP = /(HTML\w*?Element)|Text|Comment/; export const test = (val: any) => val !== undefined && val !== null && - (val.nodeType === 1 || val.nodeType === 3 || val.nodeType === 8) && + (val.nodeType === ELEMENT_NODE || + val.nodeType === TEXT_NODE || + val.nodeType === COMMENT_NODE) && 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), - ) - .filter(value => value.trim().length) - .map(value => config.spacingOuter + indentation + value) - .join(''); -} - const getType = element => element.tagName.toLowerCase(); // Convert array of attribute objects to keys array and props object. @@ -76,21 +72,19 @@ export const serialize = ( 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 (element.nodeType === TEXT_NODE) { + return ( + colors.content.open + escapeHTML(element.data) + colors.content.close + ); } - const colors = config.colors; - if (element.nodeType === 8) { + if (element.nodeType === COMMENT_NODE) { return ( colors.comment.open + - '' + + '' + colors.comment.close ); } From 776f540ab2c28a3e01edc7d0b9b1ec9b585cb108 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Tue, 15 Aug 2017 11:13:36 -0400 Subject: [PATCH 2/4] Call createTextNode in another existing test --- packages/pretty-format/src/__tests__/html_element.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/pretty-format/src/__tests__/html_element.test.js b/packages/pretty-format/src/__tests__/html_element.test.js index 56433a907b50..eebe1bd4a0bb 100644 --- a/packages/pretty-format/src/__tests__/html_element.test.js +++ b/packages/pretty-format/src/__tests__/html_element.test.js @@ -64,8 +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'); - const child = document.createTextNode('Jest'); - parent.appendChild(child); + const text = document.createTextNode('Jest'); + parent.appendChild(text); expect(parent).toPrettyPrintTo( '\n Jest\n', @@ -107,7 +107,8 @@ describe('HTMLElement Plugin', () => { parent.appendChild(child); child.setAttribute('style', 'color: #99424F'); - child.innerHTML = 'Jest'; + const text = document.createTextNode('Jest'); + child.appendChild(text); expect(parent).toPrettyPrintTo( '
\n \n Jest\n \n
', From 98611144f4a8ed7660dbfba7a43fbd27f76be882 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Aug 2017 11:13:48 -0400 Subject: [PATCH 3/4] Simplify regexp and factor testNode out of test --- .../pretty-format/src/plugins/html_element.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/pretty-format/src/plugins/html_element.js b/packages/pretty-format/src/plugins/html_element.js index a4760b3d4581..e6e91bbb7262 100644 --- a/packages/pretty-format/src/plugins/html_element.js +++ b/packages/pretty-format/src/plugins/html_element.js @@ -43,17 +43,18 @@ const ELEMENT_NODE = 1; const TEXT_NODE = 3; const COMMENT_NODE = 8; -const HTML_ELEMENT_REGEXP = /(HTML\w*?Element)|Text|Comment/; +const ELEMENT_REGEXP = /^HTML\w*?Element$/; + +const testNode = (type: any, name: any) => + (type === ELEMENT_NODE && ELEMENT_REGEXP.test(name)) || + (type === TEXT_NODE && name === 'Text') || + (type === COMMENT_NODE && name === 'Comment'); export const test = (val: any) => - val !== undefined && - val !== null && - (val.nodeType === ELEMENT_NODE || - val.nodeType === TEXT_NODE || - val.nodeType === COMMENT_NODE) && - val.constructor !== undefined && - val.constructor.name !== undefined && - HTML_ELEMENT_REGEXP.test(val.constructor.name); + val && + val.constructor && + val.constructor.name && + testNode(val.nodeType, val.constructor.name); const getType = element => element.tagName.toLowerCase(); From e78a37c4bc1a3b9a3163b561876864bedb2e5d89 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Aug 2017 12:23:08 -0400 Subject: [PATCH 4/4] Remove HTML from type names and rename element arg as node --- .../pretty-format/src/plugins/html_element.js | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/pretty-format/src/plugins/html_element.js b/packages/pretty-format/src/plugins/html_element.js index e6e91bbb7262..a6b5c4082488 100644 --- a/packages/pretty-format/src/plugins/html_element.js +++ b/packages/pretty-format/src/plugins/html_element.js @@ -23,18 +23,17 @@ type Attribute = { value: string, }; -type HTMLElement = { +type Element = { attributes: Array, - childNodes: Array, + childNodes: Array, nodeType: 1, tagName: string, - textContent: string, }; -type HTMLText = { +type Text = { data: string, nodeType: 3, }; -type HTMLComment = { +type Comment = { data: string, nodeType: 8, }; @@ -45,10 +44,10 @@ const COMMENT_NODE = 8; const ELEMENT_REGEXP = /^HTML\w*?Element$/; -const testNode = (type: any, name: any) => - (type === ELEMENT_NODE && ELEMENT_REGEXP.test(name)) || - (type === TEXT_NODE && name === 'Text') || - (type === COMMENT_NODE && name === 'Comment'); +const testNode = (nodeType: any, name: any) => + (nodeType === ELEMENT_NODE && ELEMENT_REGEXP.test(name)) || + (nodeType === TEXT_NODE && name === 'Text') || + (nodeType === COMMENT_NODE && name === 'Comment'); export const test = (val: any) => val && @@ -56,8 +55,6 @@ export const test = (val: any) => val.constructor.name && testNode(val.nodeType, val.constructor.name); -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) => { @@ -66,7 +63,7 @@ const propsReducer = (props, attribute) => { }; export const serialize = ( - element: HTMLElement | HTMLText | HTMLComment, + node: Element | Text | Comment, config: Config, indentation: string, depth: number, @@ -74,31 +71,30 @@ export const serialize = ( printer: Printer, ): string => { const colors = config.colors; - if (element.nodeType === TEXT_NODE) { - return ( - colors.content.open + escapeHTML(element.data) + colors.content.close - ); + if (node.nodeType === TEXT_NODE) { + return colors.content.open + escapeHTML(node.data) + colors.content.close; } - if (element.nodeType === COMMENT_NODE) { + if (node.nodeType === COMMENT_NODE) { return ( colors.comment.open + '' + 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, @@ -106,7 +102,7 @@ export const serialize = ( printer, ), printChildren( - Array.prototype.slice.call(element.childNodes), + Array.prototype.slice.call(node.childNodes), config, indentation + config.indent, depth,