From 2b9e811355b4d73e4994deeabca13b400d24a663 Mon Sep 17 00:00:00 2001 From: freakzlike Date: Fri, 6 May 2022 03:49:37 +0200 Subject: [PATCH] fix: wrapper.element with nested multiple roots (#1463) * Failing test cases with nested multi root elements * fix: multiple root elements with nested components or slots * fix: wrapper.element with nested multiple roots --- src/vueWrapper.ts | 37 ++++++++++++++++++++-- tests/element.spec.ts | 71 ++++++++++++++++++++++++++++++++++++++++--- tests/text.spec.ts | 67 +++++++++++++++++++++++++++++++++++----- 3 files changed, 161 insertions(+), 14 deletions(-) diff --git a/src/vueWrapper.ts b/src/vueWrapper.ts index f45c7f604..b6a6d86e5 100644 --- a/src/vueWrapper.ts +++ b/src/vueWrapper.ts @@ -21,6 +21,7 @@ import { registerFactory, WrapperType } from './wrapperFactory' +import { VNode } from '@vue/runtime-core' export class VueWrapper< T extends Omit< @@ -66,8 +67,40 @@ export class VueWrapper< } private get hasMultipleRoots(): boolean { - // if the subtree is an array of children, we have multiple root nodes - return this.vm.$.subTree.shapeFlag === ShapeFlags.ARRAY_CHILDREN + // Recursive check subtree for nested root elements + // + const checkTree = (subTree: VNode): boolean => { + // if the subtree is an array of children, we have multiple root nodes + if (subTree.shapeFlag === ShapeFlags.ARRAY_CHILDREN) return true + + if (subTree.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { + // Component has multiple children or slot with multiple children + if ( + subTree.shapeFlag & ShapeFlags.ARRAY_CHILDREN || + subTree.shapeFlag & ShapeFlags.SLOTS_CHILDREN + ) { + return true + } + + if (subTree.component?.subTree) { + return checkTree(subTree.component.subTree) + } + } else if (subTree.shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) { + if (subTree.shapeFlag & ShapeFlags.ARRAY_CHILDREN) { + return true + } + + if (subTree.component?.subTree) { + return checkTree(subTree.component.subTree) + } + } + + return false + } + + return checkTree(this.vm.$.subTree) } protected getRootNodes(): VueNode[] { diff --git a/tests/element.spec.ts b/tests/element.spec.ts index 6cd625c04..e1c6e71b2 100644 --- a/tests/element.spec.ts +++ b/tests/element.spec.ts @@ -2,6 +2,15 @@ import { defineComponent, h } from 'vue' import { mount } from '../src' +const MultiRootText = defineComponent({ + render: () => [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')] +}) +const ReturnSlot = defineComponent({ + render() { + return this.$slots.default!({}) + } +}) + describe('element', () => { it('returns element when mounting single root node', () => { const Component = defineComponent({ @@ -16,14 +25,66 @@ describe('element', () => { }) it('returns the VTU root element when mounting multiple root nodes', () => { - const Component = defineComponent({ - render() { - return [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')] - } + const wrapper = mount(MultiRootText) + + expect(wrapper.element.innerHTML).toBe( + '
foo
bar
baz
' + ) + }) + + it('returns correct element for root component with multiple roots', () => { + const Parent = defineComponent({ + components: { MultiRootText }, + template: '' }) - const wrapper = mount(Component) + const wrapper = mount(Parent) + + expect(wrapper.findComponent(MultiRootText).text()).toBe('foobarbaz') + expect(wrapper.text()).toBe('foobarbaz') + }) + + it('returns correct element for root slot', () => { + const Parent = defineComponent({ + components: { ReturnSlot }, + template: ` + +
foo
+
bar
+
baz
+
` + }) + + const wrapper = mount(Parent) + expect(wrapper.element.innerHTML).toBe( + '
foo
bar
baz
' + ) + }) + + it('should return element for multi root functional component', () => { + const Foo = () => h(MultiRootText) + const wrapper = mount(Foo) + + expect(wrapper.element.innerHTML).toBe( + '
foo
bar
baz
' + ) + }) + + it('returns correct element for root slot with functional component', () => { + const wrapper = mount(() => + h(ReturnSlot, {}, () => [ + h('div', {}, 'foo'), + h('div', {}, 'bar'), + h('div', {}, 'baz') + ]) + ) + expect(wrapper.element.innerHTML).toBe( + '
foo
bar
baz
' + ) + }) + it('returns correct element for root slot with nested component', () => { + const wrapper = mount(() => h(ReturnSlot, {}, () => h(MultiRootText))) expect(wrapper.element.innerHTML).toBe( '
foo
bar
baz
' ) diff --git a/tests/text.spec.ts b/tests/text.spec.ts index 296140aeb..9075df5e1 100644 --- a/tests/text.spec.ts +++ b/tests/text.spec.ts @@ -2,6 +2,15 @@ import { defineComponent, h } from 'vue' import { mount } from '../src' +const MultiRootText = defineComponent({ + render: () => [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')] +}) +const ReturnSlot = defineComponent({ + render() { + return this.$slots.default!({}) + } +}) + describe('text', () => { it('returns text when mounting single root node', () => { const Component = defineComponent({ @@ -16,13 +25,7 @@ describe('text', () => { }) it('returns text when mounting multiple root nodes', () => { - const Component = defineComponent({ - render() { - return [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')] - } - }) - - const wrapper = mount(Component) + const wrapper = mount(MultiRootText) expect(wrapper.text()).toBe('foobarbaz') }) @@ -39,4 +42,54 @@ describe('text', () => { expect(wrapper.text()).toBe('') }) + + it('returns correct text for root component with multiple roots', () => { + const Parent = defineComponent({ + components: { MultiRootText }, + template: '' + }) + + const wrapper = mount(Parent) + + expect(wrapper.findComponent(MultiRootText).text()).toBe('foobarbaz') + expect(wrapper.text()).toBe('foobarbaz') + }) + + it('returns correct text for root slot', () => { + const Parent = defineComponent({ + components: { ReturnSlot }, + template: ` + +
foo
+
bar
+
baz
+
` + }) + + const wrapper = mount(Parent) + expect(wrapper.text()).toBe('foobarbaz') + }) + + it('should return text for multi root functional component', () => { + const Foo = () => h(MultiRootText) + const wrapper = mount(Foo) + + expect(wrapper.text()).toBe('foobarbaz') + }) + + it('returns correct text for root slot with functional component', () => { + const wrapper = mount(() => + h(ReturnSlot, {}, () => [ + h('div', {}, 'foo'), + h('div', {}, 'bar'), + h('div', {}, 'baz') + ]) + ) + expect(wrapper.text()).toBe('foobarbaz') + }) + + it('returns correct text for root slot with nested component', () => { + const wrapper = mount(() => h(ReturnSlot, {}, () => h(MultiRootText))) + expect(wrapper.text()).toBe('foobarbaz') + }) })