From 3b0f7f5a8d1a8de59b9ea68e4a4748292b7a6342 Mon Sep 17 00:00:00 2001 From: Illya Klymov Date: Wed, 1 Dec 2021 09:46:07 +0200 Subject: [PATCH] fix(html): ensure wrapper.html() works correctly for multi-root (#1120) --- src/baseWrapper.ts | 8 ++++++- src/domWrapper.ts | 7 ------ src/utils/getRootNodes.ts | 9 ++++++-- src/utils/stringifyNode.ts | 5 +++++ src/vueWrapper.ts | 9 -------- tests/html.spec.ts | 33 ++++++++++++++++++++++------- tests/mountingOptions/slots.spec.ts | 11 +++++----- 7 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 src/utils/stringifyNode.ts diff --git a/src/baseWrapper.ts b/src/baseWrapper.ts index 294e161b5..2bce96558 100644 --- a/src/baseWrapper.ts +++ b/src/baseWrapper.ts @@ -24,6 +24,8 @@ import { isElementVisible } from './utils/isElementVisible' import { isElement } from './utils/isElement' import type { DOMWrapper } from './domWrapper' import { createDOMWrapper, createVueWrapper } from './wrapperFactory' +import { stringifyNode } from './utils/stringifyNode' +import pretty from 'pretty' export default abstract class BaseWrapper implements WrapperLike @@ -191,7 +193,11 @@ export default abstract class BaseWrapper ) } abstract setValue(value?: any): Promise - abstract html(): string + html(): string { + return this.getRootNodes() + .map((node) => pretty(stringifyNode(node))) + .join('\n') + } classes(): string[] classes(className: string): boolean diff --git a/src/domWrapper.ts b/src/domWrapper.ts index f2a880482..cdb062cd1 100644 --- a/src/domWrapper.ts +++ b/src/domWrapper.ts @@ -1,7 +1,6 @@ import { config } from './config' import BaseWrapper from './baseWrapper' import WrapperLike from './interfaces/wrapperLike' -import { isElement } from './utils/isElement' import { registerFactory, WrapperType } from './wrapperFactory' import { RefSelector } from './types' import { isRefSelector } from './utils' @@ -22,12 +21,6 @@ export class DOMWrapper extends BaseWrapper { return this.element.__vueParentComponent } - html() { - return isElement(this.element) - ? this.element.outerHTML - : this.element.toString() - } - find(selector: string | RefSelector): DOMWrapper { const result = super.find(selector) if (result.exists() && isRefSelector(selector)) { diff --git a/src/utils/getRootNodes.ts b/src/utils/getRootNodes.ts index 17831f858..fb1c54316 100644 --- a/src/utils/getRootNodes.ts +++ b/src/utils/getRootNodes.ts @@ -8,7 +8,10 @@ export function getRootNodes(vnode: VNode): Node[] { } else if (vnode.shapeFlag & ShapeFlags.COMPONENT) { const { subTree } = vnode.component! return getRootNodes(subTree) - } else if (vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN) { + } else if ( + vnode.shapeFlag & + (ShapeFlags.TEXT_CHILDREN | ShapeFlags.TELEPORT) + ) { // static node optimization, subTree.children will be static string and will not help us const result = [vnode.el as Node] if (vnode.anchor) { @@ -30,6 +33,8 @@ export function getRootNodes(vnode: VNode): Node[] { } // Missing cases which do not need special handling: // ShapeFlags.SLOTS_CHILDREN comes with ShapeFlags.ELEMENT - // ShapeFlags.TELEPORT comes with ShapeFlags.TEXT_CHILDREN + + // Will hit this default when ShapeFlags is 0 + // This is the case for example for unresolved async component without loader return [] } diff --git a/src/utils/stringifyNode.ts b/src/utils/stringifyNode.ts new file mode 100644 index 000000000..57d930b90 --- /dev/null +++ b/src/utils/stringifyNode.ts @@ -0,0 +1,5 @@ +export function stringifyNode(node: Node): string { + return node instanceof Element + ? node.outerHTML + : new XMLSerializer().serializeToString(node) +} diff --git a/src/vueWrapper.ts b/src/vueWrapper.ts index 92b6b735e..49de59790 100644 --- a/src/vueWrapper.ts +++ b/src/vueWrapper.ts @@ -131,15 +131,6 @@ export class VueWrapper< return emitted(this.vm, eventName) } - html() { - // cover cases like , multiple root nodes. - if (this.parentElement['__vue_app__']) { - return pretty(this.parentElement.innerHTML) - } - - return pretty(this.element.outerHTML) - } - isVisible(): boolean { const domWrapper = createDOMWrapper(this.element) return domWrapper.isVisible() diff --git a/tests/html.spec.ts b/tests/html.spec.ts index 30420484a..5d8299a80 100644 --- a/tests/html.spec.ts +++ b/tests/html.spec.ts @@ -15,18 +15,35 @@ describe('html', () => { expect(wrapper.html()).toBe('
Text content
') }) - it('returns the html when mounting multiple root nodes', () => { + describe('multiple root components', () => { + const originalTemplate = [ + '
foo
', + '', + 'some text', + '
bar
', + '
baz
' + ] + const Component = defineComponent({ - render() { - return [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')] - } + name: 'TemplateComponent', + template: originalTemplate.join('') }) - const wrapper = mount(Component) + it('returns the html when mounting multiple root nodes', () => { + const wrapper = mount(Component) + expect(wrapper.html()).toBe(originalTemplate.join('\n')) + }) - expect(wrapper.html()).toBe( - '
foo
\n' + '
bar
\n' + '
baz
' - ) + it('returns the html when multiple root component is located inside other component', () => { + const ParentComponent = defineComponent({ + components: { MultipleRoots: Component }, + template: '
parent
' + }) + const wrapper = mount(ParentComponent) + expect(wrapper.findComponent(Component).html()).toBe( + originalTemplate.join('\n') + ) + }) }) it('returns the html when mounting a Suspense component', () => { diff --git a/tests/mountingOptions/slots.spec.ts b/tests/mountingOptions/slots.spec.ts index 604474d6f..63f777bae 100644 --- a/tests/mountingOptions/slots.spec.ts +++ b/tests/mountingOptions/slots.spec.ts @@ -95,12 +95,13 @@ describe('slots', () => { }) expect(wrapper.find('.named').html()).toBe( - '' + - '
' + - '
' + - '
Hello world
' + - '
' + + [ + '
', + '
', + '
Hello world
', + '
', '
' + ].join('\n') ) }) })