-
Notifications
You must be signed in to change notification settings - Fork 470
feat: Add filterNode
option to prettyDOM
#907
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
Merged
kentcdodds
merged 13 commits into
testing-library:alpha
from
eps1lon:feat/pretty-dom-noise
Jun 11, 2021
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
820838c
Add expected behavior
eps1lon 2d68b77
WIP implement filterNode
eps1lon 582ad1d
REVERT LATER leverage https://github.com/facebook/jest/pull/11130
eps1lon 13f4714
Use a fork of DOMElement plugin
eps1lon 7bb11b3
Update snapshots
eps1lon 458c7f1
Expand what we mean by "useful error message"
eps1lon 4ee6ec8
Update src/DOMElementFilter.ts
eps1lon 41ac9b4
Bump pretty-format
eps1lon e514cc7
Update types
eps1lon 35c1824
Ignore coverage for fork
eps1lon d1a54bb
poke codecov
eps1lon bd68e32
Added test for undesired behavior
eps1lon e949974
Update types/pretty-dom.d.ts
eps1lon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
/** | ||
* Source: https://github.com/facebook/jest/blob/e7bb6a1e26ffab90611b2593912df15b69315611/packages/pretty-format/src/plugins/DOMElement.ts | ||
*/ | ||
/* eslint-disable -- trying to stay as close to the original as possible */ | ||
/* istanbul ignore file */ | ||
import type {Config, NewPlugin, Printer, Refs} from 'pretty-format' | ||
|
||
function escapeHTML(str: string): string { | ||
return str.replace(/</g, '<').replace(/>/g, '>') | ||
} | ||
// Return empty string if keys is empty. | ||
const printProps = ( | ||
keys: Array<string>, | ||
props: Record<string, unknown>, | ||
config: Config, | ||
indentation: string, | ||
depth: number, | ||
refs: Refs, | ||
printer: Printer, | ||
): string => { | ||
const indentationNext = indentation + config.indent | ||
const colors = config.colors | ||
return keys | ||
.map(key => { | ||
const value = props[key] | ||
let printed = printer(value, config, indentationNext, depth, refs) | ||
|
||
if (typeof value !== 'string') { | ||
if (printed.indexOf('\n') !== -1) { | ||
printed = | ||
config.spacingOuter + | ||
indentationNext + | ||
printed + | ||
config.spacingOuter + | ||
indentation | ||
} | ||
printed = '{' + printed + '}' | ||
} | ||
|
||
return ( | ||
config.spacingInner + | ||
indentation + | ||
colors.prop.open + | ||
key + | ||
colors.prop.close + | ||
'=' + | ||
colors.value.open + | ||
printed + | ||
colors.value.close | ||
) | ||
}) | ||
.join('') | ||
} | ||
|
||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#node_type_constants | ||
const NodeTypeTextNode = 3 | ||
|
||
// Return empty string if children is empty. | ||
const printChildren = ( | ||
children: Array<unknown>, | ||
config: Config, | ||
indentation: string, | ||
depth: number, | ||
refs: Refs, | ||
printer: Printer, | ||
): string => | ||
children | ||
.map(child => { | ||
const printedChild = | ||
typeof child === 'string' | ||
? printText(child, config) | ||
: printer(child, config, indentation, depth, refs) | ||
|
||
if ( | ||
printedChild === '' && | ||
typeof child === 'object' && | ||
child !== null && | ||
(child as Node).nodeType !== NodeTypeTextNode | ||
) { | ||
// A plugin serialized this Node to '' meaning we should ignore it. | ||
return '' | ||
} | ||
return config.spacingOuter + indentation + printedChild | ||
}) | ||
.join('') | ||
|
||
const printText = (text: string, config: Config): string => { | ||
const contentColor = config.colors.content | ||
return contentColor.open + escapeHTML(text) + contentColor.close | ||
} | ||
|
||
const printComment = (comment: string, config: Config): string => { | ||
const commentColor = config.colors.comment | ||
return ( | ||
commentColor.open + | ||
'<!--' + | ||
escapeHTML(comment) + | ||
'-->' + | ||
commentColor.close | ||
) | ||
} | ||
|
||
// Separate the functions to format props, children, and element, | ||
// so a plugin could override a particular function, if needed. | ||
// Too bad, so sad: the traditional (but unnecessary) space | ||
// in a self-closing tagColor requires a second test of printedProps. | ||
const printElement = ( | ||
type: string, | ||
printedProps: string, | ||
printedChildren: string, | ||
config: Config, | ||
indentation: string, | ||
): string => { | ||
const tagColor = config.colors.tag | ||
return ( | ||
tagColor.open + | ||
'<' + | ||
type + | ||
(printedProps && | ||
tagColor.close + | ||
printedProps + | ||
config.spacingOuter + | ||
indentation + | ||
tagColor.open) + | ||
(printedChildren | ||
? '>' + | ||
tagColor.close + | ||
printedChildren + | ||
config.spacingOuter + | ||
indentation + | ||
tagColor.open + | ||
'</' + | ||
type | ||
: (printedProps && !config.min ? '' : ' ') + '/') + | ||
'>' + | ||
tagColor.close | ||
) | ||
} | ||
|
||
const printElementAsLeaf = (type: string, config: Config): string => { | ||
const tagColor = config.colors.tag | ||
return ( | ||
tagColor.open + | ||
'<' + | ||
type + | ||
tagColor.close + | ||
' …' + | ||
tagColor.open + | ||
' />' + | ||
tagColor.close | ||
) | ||
} | ||
|
||
const ELEMENT_NODE = 1 | ||
const TEXT_NODE = 3 | ||
const COMMENT_NODE = 8 | ||
const FRAGMENT_NODE = 11 | ||
|
||
const ELEMENT_REGEXP = /^((HTML|SVG)\w*)?Element$/ | ||
|
||
const testNode = (val: any) => { | ||
const constructorName = val.constructor.name | ||
const {nodeType, tagName} = val | ||
const isCustomElement = | ||
(typeof tagName === 'string' && tagName.includes('-')) || | ||
(typeof val.hasAttribute === 'function' && val.hasAttribute('is')) | ||
|
||
return ( | ||
(nodeType === ELEMENT_NODE && | ||
(ELEMENT_REGEXP.test(constructorName) || isCustomElement)) || | ||
(nodeType === TEXT_NODE && constructorName === 'Text') || | ||
(nodeType === COMMENT_NODE && constructorName === 'Comment') || | ||
(nodeType === FRAGMENT_NODE && constructorName === 'DocumentFragment') | ||
) | ||
} | ||
|
||
export const test: NewPlugin['test'] = (val: any) => | ||
val?.constructor?.name && testNode(val) | ||
|
||
type HandledType = Element | Text | Comment | DocumentFragment | ||
|
||
function nodeIsText(node: HandledType): node is Text { | ||
return node.nodeType === TEXT_NODE | ||
} | ||
|
||
function nodeIsComment(node: HandledType): node is Comment { | ||
return node.nodeType === COMMENT_NODE | ||
} | ||
|
||
function nodeIsFragment(node: HandledType): node is DocumentFragment { | ||
return node.nodeType === FRAGMENT_NODE | ||
} | ||
|
||
export default function createDOMElementFilter( | ||
filterNode: (node: Node) => boolean, | ||
): NewPlugin { | ||
return { | ||
test: (val: any) => val?.constructor?.name && testNode(val), | ||
serialize: ( | ||
node: HandledType, | ||
config: Config, | ||
indentation: string, | ||
depth: number, | ||
refs: Refs, | ||
printer: Printer, | ||
) => { | ||
if (nodeIsText(node)) { | ||
return printText(node.data, config) | ||
} | ||
|
||
if (nodeIsComment(node)) { | ||
return printComment(node.data, config) | ||
} | ||
|
||
const type = nodeIsFragment(node) | ||
? `DocumentFragment` | ||
: node.tagName.toLowerCase() | ||
|
||
if (++depth > config.maxDepth) { | ||
return printElementAsLeaf(type, config) | ||
} | ||
|
||
return printElement( | ||
type, | ||
printProps( | ||
nodeIsFragment(node) | ||
? [] | ||
: Array.from(node.attributes) | ||
.map(attr => attr.name) | ||
.sort(), | ||
nodeIsFragment(node) | ||
? {} | ||
: Array.from(node.attributes).reduce<Record<string, string>>( | ||
(props, attribute) => { | ||
props[attribute.name] = attribute.value | ||
return props | ||
}, | ||
{}, | ||
), | ||
config, | ||
indentation + config.indent, | ||
depth, | ||
refs, | ||
printer, | ||
), | ||
printChildren( | ||
Array.prototype.slice | ||
.call(node.childNodes || node.children) | ||
.filter(filterNode), | ||
config, | ||
indentation + config.indent, | ||
depth, | ||
refs, | ||
printer, | ||
), | ||
config, | ||
indentation, | ||
) | ||
}, | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.