-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add filterNode option to prettyDOM for filtering browser assertion error output #9475
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
Changes from 2 commits
9ccdc54
5169daa
37380ac
7c66451
a6927f7
15d1697
8e179ee
4baa437
89b3b50
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -499,10 +499,52 @@ function configurePrettyDOM(options: StringifyOptions) { | |
| defaultOptions = options | ||
| } | ||
|
|
||
| /** | ||
| * Creates a filter function for prettyDOM that filters out nodes based on CSS selectors. | ||
| * This is similar to Testing Library's defaultIgnore configuration. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { utils } from 'vitest/browser' | ||
| * | ||
| * // Filter out script, style, and elements with data-test-hide attribute | ||
| * utils.configurePrettyDOM({ | ||
| * filterNode: utils.createNodeFilter('script, style, [data-test-hide]') | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we just make it accept a string for simplicity?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function already accepts a string selector. The return type is a filter function because |
||
| * }) | ||
| * ``` | ||
| * | ||
| * @param selector - CSS selector to filter out (e.g., 'script, style, [data-test-hide]') | ||
| * @returns A filter function that can be used with configurePrettyDOM | ||
| */ | ||
| function createNodeFilter(selector: string): (node: any) => boolean { | ||
| const ELEMENT_NODE = 1 | ||
| const COMMENT_NODE = 8 | ||
|
|
||
| return (node: any) => { | ||
| // Filter out comments | ||
| if (node.nodeType === COMMENT_NODE) { | ||
| return false | ||
| } | ||
|
|
||
| // Filter out elements matching the selector | ||
| if (node.nodeType === ELEMENT_NODE && node.matches) { | ||
| try { | ||
| return !node.matches(selector) | ||
| } | ||
| catch { | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| return true | ||
| } | ||
| } | ||
|
|
||
| export const utils = { | ||
| getElementError, | ||
| prettyDOM, | ||
| debug, | ||
| getElementLocatorSelectors, | ||
| configurePrettyDOM, | ||
| createNodeFilter, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| /** | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * | ||
| * This source code is licensed under the MIT license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
|
|
||
| import type { Config, NewPlugin, Printer, Refs } from '../types' | ||
| import { | ||
| printChildren, | ||
| printComment, | ||
| printElement, | ||
| printElementAsLeaf, | ||
| printProps, | ||
| printShadowRoot, | ||
| printText, | ||
| } from './lib/markup' | ||
|
|
||
| const ELEMENT_NODE = 1 | ||
| const TEXT_NODE = 3 | ||
| const COMMENT_NODE = 8 | ||
| const FRAGMENT_NODE = 11 | ||
|
|
||
| const ELEMENT_REGEXP = /^(?:(?:HTML|SVG)\w*)?Element$/ | ||
|
|
||
| function testHasAttribute(val: any) { | ||
| try { | ||
| return typeof val.hasAttribute === 'function' && val.hasAttribute('is') | ||
| } | ||
| catch { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| function testNode(val: any) { | ||
| const constructorName = val.constructor.name | ||
| const { nodeType, tagName } = val | ||
| const isCustomElement | ||
| = (typeof tagName === 'string' && tagName.includes('-')) | ||
| || testHasAttribute(val) | ||
|
|
||
| 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 interface FilterConfig extends Config { | ||
| filterNode?: (node: any) => boolean | ||
| } | ||
|
|
||
| function filterChildren(children: any[], filterNode?: (node: any) => boolean): any[] { | ||
| if (!filterNode) { | ||
| return children | ||
| } | ||
| return children.filter(filterNode) | ||
| } | ||
|
|
||
| export function createDOMElementFilter(filterNode?: (node: any) => boolean): NewPlugin { | ||
| return { | ||
| test, | ||
| 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) | ||
| } | ||
|
|
||
| const children = Array.prototype.slice.call(node.childNodes || node.children) | ||
| const filteredChildren = filterChildren(children, filterNode) | ||
|
|
||
| const shadowChildren = (nodeIsFragment(node) || !node.shadowRoot) | ||
| ? [] | ||
| : Array.prototype.slice.call(node.shadowRoot.children) | ||
| const filteredShadowChildren = filterChildren(shadowChildren, filterNode) | ||
|
|
||
| return printElement( | ||
| type, | ||
| printProps( | ||
| nodeIsFragment(node) | ||
| ? [] | ||
| : Array.from(node.attributes, attr => attr.name).sort(), | ||
| nodeIsFragment(node) | ||
| ? {} | ||
| : [...node.attributes].reduce<Record<string, string>>( | ||
| (props, attribute) => { | ||
| props[attribute.name] = attribute.value | ||
| return props | ||
| }, | ||
| {}, | ||
| ), | ||
| config, | ||
| indentation + config.indent, | ||
| depth, | ||
| refs, | ||
| printer, | ||
| ), | ||
| (filteredShadowChildren.length > 0 | ||
| ? printShadowRoot(filteredShadowChildren, config, indentation + config.indent, depth, refs, printer) | ||
| : '') | ||
| + printChildren( | ||
| filteredChildren, | ||
| config, | ||
| indentation + config.indent, | ||
| depth, | ||
| refs, | ||
| printer, | ||
| ), | ||
| config, | ||
| indentation, | ||
| ) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| export default createDOMElementFilter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we just make it accept a string for simplicity? (instead of a function). Then we can delete
createNodeFilterThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to accept string directly in commit 7c66451.
filterNodenow acceptsstring | ((node: any) => boolean). String values are CSS selectors, and the internalcreateNodeFilterFromSelectorfunction handles the conversion. RemovedcreateNodeFilterfrom exports.