>(
+ (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
diff --git a/packages/utils/src/display.ts b/packages/utils/src/display.ts
index 0a47fb88f727..7c1aebc9da02 100644
--- a/packages/utils/src/display.ts
+++ b/packages/utils/src/display.ts
@@ -1,5 +1,6 @@
import type { PrettyFormatOptions } from '@vitest/pretty-format'
import {
+ createDOMElementFilter,
format as prettyFormat,
plugins as prettyFormatPlugins,
} from '@vitest/pretty-format'
@@ -42,22 +43,39 @@ const PLUGINS = [
export interface StringifyOptions extends PrettyFormatOptions {
maxLength?: number
+ filterNode?: string | ((node: any) => boolean)
}
export function stringify(
object: unknown,
maxDepth = 10,
- { maxLength, ...options }: StringifyOptions = {},
+ { maxLength, filterNode, ...options }: StringifyOptions = {},
): string {
const MAX_LENGTH = maxLength ?? 10000
let result
+ // Convert string selector to filter function
+ const filterFn = typeof filterNode === 'string'
+ ? createNodeFilterFromSelector(filterNode)
+ : filterNode
+
+ const plugins = filterFn
+ ? [
+ ReactTestComponent,
+ ReactElement,
+ createDOMElementFilter(filterFn),
+ DOMCollection,
+ Immutable,
+ AsymmetricMatcher,
+ ]
+ : PLUGINS
+
try {
result = prettyFormat(object, {
maxDepth,
escapeString: false,
// min: true,
- plugins: PLUGINS,
+ plugins,
...options,
})
}
@@ -67,17 +85,41 @@ export function stringify(
maxDepth,
escapeString: false,
// min: true,
- plugins: PLUGINS,
+ plugins,
...options,
})
}
// Prevents infinite loop https://github.com/vitest-dev/vitest/issues/7249
return result.length >= MAX_LENGTH && maxDepth > 1
- ? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), { maxLength, ...options })
+ ? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), { maxLength, filterNode, ...options })
: result
}
+function createNodeFilterFromSelector(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 formatRegExp: RegExp = /%[sdjifoOc%]/g
interface FormatOptions {
diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts
index 5d97ae4952cd..f0ca2a356091 100644
--- a/test/browser/test/utils.test.ts
+++ b/test/browser/test/utils.test.ts
@@ -1,4 +1,4 @@
-import { afterEach, expect, it, test } from 'vitest'
+import { afterEach, beforeEach, expect, it, test } from 'vitest'
import { commands, utils } from 'vitest/browser'
import { inspect } from 'vitest/internal/browser'
@@ -7,6 +7,10 @@ afterEach(() => {
document.body.innerHTML = ''
})
+beforeEach(() => {
+ utils.configurePrettyDOM({})
+})
+
it('utils package correctly uses loupe', async () => {
expect(inspect({ test: 1 })).toBe('{ test: 1 }')
})
@@ -109,3 +113,96 @@ test('changing the defaults works', async () => {
"
`)
})
+
+test('filterNode option filters out matching elements', async () => {
+ const div = document.createElement('div')
+ div.innerHTML = `
+
+
+
+ hidden content
+ visible content
+
+ `
+ document.body.append(div)
+
+ const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode: 'script, style, [data-test-hide]' }))
+
+ expect(result).not.toContain('console.log')
+ expect(result).not.toContain('color: red')
+ expect(result).not.toContain('hidden content')
+ expect(result).toContain('visible content')
+ expect(result).toMatchInlineSnapshot(`
+ "
+
+
+ visible content
+
+
+
"
+ `)
+})
+
+test('filterNode with configurePrettyDOM affects default behavior', async () => {
+ utils.configurePrettyDOM({ filterNode: 'script, style, [data-test-hide]' })
+
+ const div = document.createElement('div')
+ div.innerHTML = `
+
+
+
+ hidden content
+ visible content
+
+ `
+ document.body.append(div)
+
+ const result = await commands.stripVTControlCharacters(utils.prettyDOM(div))
+
+ expect(result).not.toContain('console.log')
+ expect(result).not.toContain('color: red')
+ expect(result).not.toContain('hidden content')
+ expect(result).toContain('visible content')
+ expect(result).toMatchInlineSnapshot(`
+ "
+
+
+ visible content
+
+
+
"
+ `)
+})
+
+test('filterNode with wildcard selector filters nested content', async () => {
+ const div = document.createElement('div')
+ div.innerHTML = `
+
+
+
nested hidden
+
deeply nested hidden
+
+
visible
+
+ `
+ document.body.append(div)
+
+ const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode: '[data-test-hide-content] *' }))
+
+ expect(result).not.toContain('nested hidden')
+ expect(result).not.toContain('deeply nested hidden')
+ expect(result).toContain('visible')
+ expect(result).toContain('data-test-hide-content')
+ expect(result).toMatchInlineSnapshot(`
+ ""
+ `)
+})