diff --git a/docs/config/index.md b/docs/config/index.md index 3d18fd408..0f615b6d8 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -1664,7 +1664,7 @@ Will call [`vi.unstubAllGlobals`](/api/vi#vi-unstuballglobals) before each test. - **Type:** `PrettyFormatOptions` -Format options for snapshot testing. These options are passed down to [`pretty-format`](https://www.npmjs.com/package/pretty-format). +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`. ::: tip Beware that `plugins` field on this object will be ignored. diff --git a/docs/guide/migration.md b/docs/guide/migration.md index f164565a2..0f9280f87 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -273,6 +273,39 @@ export default defineConfig({ }) ``` +### Snapshots using custom elements print the shadow root + +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`. + +```js +// before Vite 4.0 +exports[`custom element with shadow root 1`] = ` +" +
+ +
+" +` + +// after Vite 4.0 +exports[`custom element with shadow root 1`] = ` +" +
+ + #shadow-root + + hello + + +
+" +` +``` + ### Deprecated APIs are Removed Vitest 4.0 removes some deprecated APIs, including: diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index 582164dec..e96cba860 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -444,6 +444,7 @@ export const DEFAULT_OPTIONS: Options = { plugins: [], printBasicPrototype: true, printFunctionName: true, + printShadowRoot: true, theme: DEFAULT_THEME, } satisfies Options @@ -519,6 +520,7 @@ function getConfig(options?: OptionsReceived): Config { plugins: options?.plugins ?? DEFAULT_OPTIONS.plugins, printBasicPrototype: options?.printBasicPrototype ?? true, printFunctionName: getPrintFunctionName(options), + printShadowRoot: options?.printShadowRoot ?? true, spacingInner: options?.min ? ' ' : '\n', spacingOuter: options?.min ? '' : '\n', } diff --git a/packages/pretty-format/src/plugins/DOMElement.ts b/packages/pretty-format/src/plugins/DOMElement.ts index 59caa49ff..eba3dfab9 100644 --- a/packages/pretty-format/src/plugins/DOMElement.ts +++ b/packages/pretty-format/src/plugins/DOMElement.ts @@ -12,6 +12,7 @@ import { printElement, printElementAsLeaf, printProps, + printShadowRoot, printText, } from './lib/markup' @@ -109,7 +110,10 @@ export const serialize: NewPlugin['serialize'] = ( refs, printer, ), - printChildren( + ((nodeIsFragment(node) || !node.shadowRoot) + ? '' + : printShadowRoot(Array.prototype.slice.call(node.shadowRoot.children), config, indentation + config.indent, depth, refs, printer)) + + printChildren( Array.prototype.slice.call(node.childNodes || node.children), config, indentation + config.indent, diff --git a/packages/pretty-format/src/plugins/lib/markup.ts b/packages/pretty-format/src/plugins/lib/markup.ts index 6fe155f10..5e34fd178 100644 --- a/packages/pretty-format/src/plugins/lib/markup.ts +++ b/packages/pretty-format/src/plugins/lib/markup.ts @@ -62,6 +62,23 @@ export function printChildren(children: Array, config: Config, indentat .join('') } +export function printShadowRoot(children: Array, config: Config, indentation: string, depth: number, refs: Refs, printer: Printer): string { + if (config.printShadowRoot === false) { + return '' + } + return [ + `${config.spacingOuter + indentation}#shadow-root`, + printChildren( + children, + config, + indentation + config.indent, + depth, + refs, + printer, + ), + ].join('') +} + export function printText(text: string, config: Config): string { const contentColor = config.colors.content return contentColor.open + escapeHTML(text) + contentColor.close diff --git a/packages/pretty-format/src/types.ts b/packages/pretty-format/src/types.ts index ad1b90898..eac12b794 100644 --- a/packages/pretty-format/src/types.ts +++ b/packages/pretty-format/src/types.ts @@ -48,6 +48,7 @@ export interface PrettyFormatOptions { min?: boolean printBasicPrototype?: boolean printFunctionName?: boolean + printShadowRoot?: boolean compareKeys?: CompareKeys plugins?: Plugins } @@ -67,6 +68,7 @@ export interface Config { plugins: Plugins printBasicPrototype: boolean printFunctionName: boolean + printShadowRoot: boolean spacingInner: string spacingOuter: string } diff --git a/test/browser/test/__snapshots__/utils.test.ts.snap b/test/browser/test/__snapshots__/utils.test.ts.snap index 60fb56d24..7a35862e0 100644 --- a/test/browser/test/__snapshots__/utils.test.ts.snap +++ b/test/browser/test/__snapshots__/utils.test.ts.snap @@ -36,6 +36,14 @@ exports[`prints the element with attributes 1`] = ` " `; +exports[`should be able to opt out of shadow DOM content 1`] = ` +" +
+ +
+" +`; + exports[`should handle DOM content bigger than maxLength 1`] = ` "
@@ -45,3 +53,20 @@ exports[`should handle DOM content bigger than maxLength 1`] = `
..." `; + +exports[`should handle shadow DOM content 1`] = ` +" +
+ + #shadow-root + + hello + + +
+" +`; diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts index a6920f46f..168e53066 100644 --- a/test/browser/test/utils.test.ts +++ b/test/browser/test/utils.test.ts @@ -52,3 +52,45 @@ test('should handle DOM content bigger than maxLength', async () => { document.body.appendChild(parentDiv) expect(await commands.stripVTControlCharacters(prettyDOM(undefined, maxContent))).toMatchSnapshot() }) + +test('should handle shadow DOM content', async () => { + class CustomElement extends HTMLElement { + connectedCallback() { + const shadowRoot = this.attachShadow({ mode: 'open' }) + const span = document.createElement('span') + span.classList.add('some-name') + span.setAttribute('data-test-id', '33') + span.setAttribute('id', '5') + span.textContent = 'hello' + shadowRoot.appendChild(span) + } + } + customElements.define('custom-element', CustomElement) + + const div = document.createElement('div') + div.innerHTML = '' + document.body.append(div) + + expect(await commands.stripVTControlCharacters(prettyDOM())).toMatchSnapshot() +}) + +test('should be able to opt out of shadow DOM content', async () => { + class CustomElement extends HTMLElement { + connectedCallback() { + const shadowRoot = this.attachShadow({ mode: 'open' }) + const span = document.createElement('span') + span.classList.add('some-name') + span.setAttribute('data-test-id', '33') + span.setAttribute('id', '5') + span.textContent = 'hello' + shadowRoot.appendChild(span) + } + } + customElements.define('no-shadow-root', CustomElement) + + const div = document.createElement('div') + div.innerHTML = '' + document.body.append(div) + + expect(await commands.stripVTControlCharacters(prettyDOM(undefined, undefined, { printShadowRoot: false }))).toMatchSnapshot() +})