Skip to content

Commit 9e72283

Browse files
authored
feat!: include shadow root contents in pretty-format output (#8545)
1 parent ea6d732 commit 9e72283

File tree

8 files changed

+127
-2
lines changed

8 files changed

+127
-2
lines changed

docs/config/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1664,7 +1664,7 @@ Will call [`vi.unstubAllGlobals`](/api/vi#vi-unstuballglobals) before each test.
16641664

16651665
- **Type:** `PrettyFormatOptions`
16661666

1667-
Format options for snapshot testing. These options are passed down to [`pretty-format`](https://www.npmjs.com/package/pretty-format).
1667+
Format options for snapshot testing. These options are passed down to our fork of [`pretty-format`](https://www.npmjs.com/package/pretty-format). In addition to the `pretty-format` options we support `printShadowRoot: boolean`.
16681668

16691669
::: tip
16701670
Beware that `plugins` field on this object will be ignored.

docs/guide/migration.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,39 @@ export default defineConfig({
273273
})
274274
```
275275

276+
### Snapshots using custom elements print the shadow root
277+
278+
In Vitest 4.0 snapshots that include custom elements will print the shadow root contents. To restore the previous behavior, set the [`printShadowRoot` option](/config/#snapshotformat) to `false`.
279+
280+
```js
281+
// before Vite 4.0
282+
exports[`custom element with shadow root 1`] = `
283+
"<body>
284+
<div>
285+
<custom-element />
286+
</div>
287+
</body>"
288+
`
289+
290+
// after Vite 4.0
291+
exports[`custom element with shadow root 1`] = `
292+
"<body>
293+
<div>
294+
<custom-element>
295+
#shadow-root
296+
<span
297+
class="some-name"
298+
data-test-id="33"
299+
id="5"
300+
>
301+
hello
302+
</span>
303+
</custom-element>
304+
</div>
305+
</body>"
306+
`
307+
```
308+
276309
### Deprecated APIs are Removed
277310

278311
Vitest 4.0 removes some deprecated APIs, including:

packages/pretty-format/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ export const DEFAULT_OPTIONS: Options = {
444444
plugins: [],
445445
printBasicPrototype: true,
446446
printFunctionName: true,
447+
printShadowRoot: true,
447448
theme: DEFAULT_THEME,
448449
} satisfies Options
449450

@@ -519,6 +520,7 @@ function getConfig(options?: OptionsReceived): Config {
519520
plugins: options?.plugins ?? DEFAULT_OPTIONS.plugins,
520521
printBasicPrototype: options?.printBasicPrototype ?? true,
521522
printFunctionName: getPrintFunctionName(options),
523+
printShadowRoot: options?.printShadowRoot ?? true,
522524
spacingInner: options?.min ? ' ' : '\n',
523525
spacingOuter: options?.min ? '' : '\n',
524526
}

packages/pretty-format/src/plugins/DOMElement.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
printElement,
1313
printElementAsLeaf,
1414
printProps,
15+
printShadowRoot,
1516
printText,
1617
} from './lib/markup'
1718

@@ -109,7 +110,10 @@ export const serialize: NewPlugin['serialize'] = (
109110
refs,
110111
printer,
111112
),
112-
printChildren(
113+
((nodeIsFragment(node) || !node.shadowRoot)
114+
? ''
115+
: printShadowRoot(Array.prototype.slice.call(node.shadowRoot.children), config, indentation + config.indent, depth, refs, printer))
116+
+ printChildren(
113117
Array.prototype.slice.call(node.childNodes || node.children),
114118
config,
115119
indentation + config.indent,

packages/pretty-format/src/plugins/lib/markup.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,23 @@ export function printChildren(children: Array<unknown>, config: Config, indentat
6262
.join('')
6363
}
6464

65+
export function printShadowRoot(children: Array<unknown>, config: Config, indentation: string, depth: number, refs: Refs, printer: Printer): string {
66+
if (config.printShadowRoot === false) {
67+
return ''
68+
}
69+
return [
70+
`${config.spacingOuter + indentation}#shadow-root`,
71+
printChildren(
72+
children,
73+
config,
74+
indentation + config.indent,
75+
depth,
76+
refs,
77+
printer,
78+
),
79+
].join('')
80+
}
81+
6582
export function printText(text: string, config: Config): string {
6683
const contentColor = config.colors.content
6784
return contentColor.open + escapeHTML(text) + contentColor.close

packages/pretty-format/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface PrettyFormatOptions {
4848
min?: boolean
4949
printBasicPrototype?: boolean
5050
printFunctionName?: boolean
51+
printShadowRoot?: boolean
5152
compareKeys?: CompareKeys
5253
plugins?: Plugins
5354
}
@@ -67,6 +68,7 @@ export interface Config {
6768
plugins: Plugins
6869
printBasicPrototype: boolean
6970
printFunctionName: boolean
71+
printShadowRoot: boolean
7072
spacingInner: string
7173
spacingOuter: string
7274
}

test/browser/test/__snapshots__/utils.test.ts.snap

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ exports[`prints the element with attributes 1`] = `
3636
</body>"
3737
`;
3838

39+
exports[`should be able to opt out of shadow DOM content 1`] = `
40+
"<body>
41+
<div>
42+
<no-shadow-root />
43+
</div>
44+
</body>"
45+
`;
46+
3947
exports[`should handle DOM content bigger than maxLength 1`] = `
4048
"<body>
4149
<div>
@@ -45,3 +53,20 @@ exports[`should handle DOM content bigger than maxLength 1`] = `
4553
</div>
4654
</body>..."
4755
`;
56+
57+
exports[`should handle shadow DOM content 1`] = `
58+
"<body>
59+
<div>
60+
<custom-element>
61+
#shadow-root
62+
<span
63+
class="some-name"
64+
data-test-id="33"
65+
id="5"
66+
>
67+
hello
68+
</span>
69+
</custom-element>
70+
</div>
71+
</body>"
72+
`;

test/browser/test/utils.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,45 @@ test('should handle DOM content bigger than maxLength', async () => {
5252
document.body.appendChild(parentDiv)
5353
expect(await commands.stripVTControlCharacters(prettyDOM(undefined, maxContent))).toMatchSnapshot()
5454
})
55+
56+
test('should handle shadow DOM content', async () => {
57+
class CustomElement extends HTMLElement {
58+
connectedCallback() {
59+
const shadowRoot = this.attachShadow({ mode: 'open' })
60+
const span = document.createElement('span')
61+
span.classList.add('some-name')
62+
span.setAttribute('data-test-id', '33')
63+
span.setAttribute('id', '5')
64+
span.textContent = 'hello'
65+
shadowRoot.appendChild(span)
66+
}
67+
}
68+
customElements.define('custom-element', CustomElement)
69+
70+
const div = document.createElement('div')
71+
div.innerHTML = '<custom-element></custom-element>'
72+
document.body.append(div)
73+
74+
expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot()
75+
})
76+
77+
test('should be able to opt out of shadow DOM content', async () => {
78+
class CustomElement extends HTMLElement {
79+
connectedCallback() {
80+
const shadowRoot = this.attachShadow({ mode: 'open' })
81+
const span = document.createElement('span')
82+
span.classList.add('some-name')
83+
span.setAttribute('data-test-id', '33')
84+
span.setAttribute('id', '5')
85+
span.textContent = 'hello'
86+
shadowRoot.appendChild(span)
87+
}
88+
}
89+
customElements.define('no-shadow-root', CustomElement)
90+
91+
const div = document.createElement('div')
92+
div.innerHTML = '<no-shadow-root></no-shadow-root>'
93+
document.body.append(div)
94+
95+
expect(await commands.stripVTControlCharacters(prettyDOM(undefined, undefined, { printShadowRoot: false }))).toMatchSnapshot()
96+
})

0 commit comments

Comments
 (0)