diff --git a/src/mount.ts b/src/mount.ts
index adf0273c1..1e63f49cd 100644
--- a/src/mount.ts
+++ b/src/mount.ts
@@ -3,8 +3,10 @@ import {
createApp,
VNode,
defineComponent,
- Plugin,
- ComponentOptions
+ VNodeNormalizedChildren,
+ VNodeProps,
+ ComponentOptions,
+ Plugin
} from 'vue'
import { VueWrapper, createWrapper } from './vue-wrapper'
@@ -41,15 +43,12 @@ export function mount
(
document.body.appendChild(el)
// handle any slots passed via mounting options
- const slots =
+ const slots: VNodeNormalizedChildren =
options?.slots &&
- Object.entries(options.slots).reduce VNode | string>>(
- (acc, [name, fn]) => {
- acc[name] = () => fn
- return acc
- },
- {}
- )
+ Object.entries(options.slots).reduce((acc, [name, fn]) => {
+ acc[name] = () => fn
+ return acc
+ }, {})
// override component data with mounting options data
if (options?.data) {
const dataMixin = createDataMixin(options.data())
@@ -57,10 +56,10 @@ export function mount(
}
// create the wrapper component
- const Parent = (props?: P) =>
+ const Parent = (props?: VNodeProps) =>
defineComponent({
render() {
- return h(component, props, slots)
+ return h(component, { ...props, ref: 'VTU_COMPONENT' }, slots)
}
})
@@ -90,7 +89,7 @@ export function mount
(
vm.mixin(emitMixin)
// mount the app!
- const app = vm.mount('#app')
+ const app = vm.mount(el)
return createWrapper(app, events)
}
diff --git a/src/vue-wrapper.ts b/src/vue-wrapper.ts
index 6855b86e5..3ba359f3a 100644
--- a/src/vue-wrapper.ts
+++ b/src/vue-wrapper.ts
@@ -1,24 +1,41 @@
import { ComponentPublicInstance } from 'vue'
+import { ShapeFlags } from '@vue/shared'
import { DOMWrapper } from './dom-wrapper'
import { WrapperAPI } from './types'
import { ErrorWrapper } from './error-wrapper'
export class VueWrapper implements WrapperAPI {
- vm: ComponentPublicInstance
+ rootVM: ComponentPublicInstance
+ componentVM: ComponentPublicInstance
__emitted: Record = {}
constructor(vm: ComponentPublicInstance, events: Record) {
- this.vm = vm
+ this.rootVM = vm
+ this.componentVM = this.rootVM.$refs[
+ 'VTU_COMPONENT'
+ ] as ComponentPublicInstance
this.__emitted = events
}
+ private get hasMultipleRoots(): boolean {
+ // if the subtree is an array of children, we have multiple root nodes
+ return this.componentVM.$.subTree.shapeFlag === ShapeFlags.ARRAY_CHILDREN
+ }
+
+ get element() {
+ return this.hasMultipleRoots
+ ? // get the parent element of the current component
+ this.componentVM.$el.parentElement
+ : this.componentVM.$el
+ }
+
classes(className?) {
- return new DOMWrapper(this.vm.$el).classes(className)
+ return new DOMWrapper(this.element).classes(className)
}
attributes(key?: string) {
- return new DOMWrapper(this.vm.$el).attributes(key)
+ return new DOMWrapper(this.element).attributes(key)
}
exists() {
@@ -30,15 +47,17 @@ export class VueWrapper implements WrapperAPI {
}
html() {
- return this.vm.$el.outerHTML
+ return this.hasMultipleRoots
+ ? this.element.innerHTML
+ : this.element.outerHTML
}
text() {
- return this.vm.$el.textContent?.trim()
+ return this.element.textContent?.trim()
}
find(selector: string): DOMWrapper | ErrorWrapper {
- const result = this.vm.$el.querySelector(selector) as T
+ const result = this.element.querySelector(selector) as T
if (result) {
return new DOMWrapper(result)
}
@@ -47,16 +66,16 @@ export class VueWrapper implements WrapperAPI {
}
findAll(selector: string): DOMWrapper[] {
- const results = (this.vm.$el as Element).querySelectorAll(selector)
+ const results = (this.element as Element).querySelectorAll(selector)
return Array.from(results).map((x) => new DOMWrapper(x))
}
async setChecked(checked: boolean = true) {
- return new DOMWrapper(this.vm.$el).setChecked(checked)
+ return new DOMWrapper(this.element).setChecked(checked)
}
trigger(eventString: string) {
- const rootElementWrapper = new DOMWrapper(this.vm.$el)
+ const rootElementWrapper = new DOMWrapper(this.element)
return rootElementWrapper.trigger(eventString)
}
}
diff --git a/tests/find.spec.ts b/tests/find.spec.ts
index 8557f5f72..b25df56c3 100644
--- a/tests/find.spec.ts
+++ b/tests/find.spec.ts
@@ -2,27 +2,56 @@ import { defineComponent, h } from 'vue'
import { mount } from '../src'
-test('find', () => {
- const Component = defineComponent({
- render() {
- return h('div', {}, [h('span', { id: 'my-span' })])
- }
+describe('find', () => {
+ test('find using single root node', () => {
+ const Component = defineComponent({
+ render() {
+ return h('div', {}, [h('span', { id: 'my-span' })])
+ }
+ })
+
+ const wrapper = mount(Component)
+ expect(wrapper.find('#my-span')).toBeTruthy()
})
- const wrapper = mount(Component)
- expect(wrapper.find('#my-span')).toBeTruthy()
+ it('find using multiple root nodes', () => {
+ const Component = defineComponent({
+ render() {
+ return [h('div', 'text'), h('span', { id: 'my-span' })]
+ }
+ })
+
+ const wrapper = mount(Component)
+ expect(wrapper.find('#my-span')).toBeTruthy()
+ })
})
-test('findAll', () => {
- const Component = defineComponent({
- render() {
- return h('div', {}, [
- h('span', { className: 'span' }),
- h('span', { className: 'span' })
- ])
- }
+describe('findAll', () => {
+ test('findAll using single root node', () => {
+ const Component = defineComponent({
+ render() {
+ return h('div', {}, [
+ h('span', { className: 'span' }),
+ h('span', { className: 'span' })
+ ])
+ }
+ })
+
+ const wrapper = mount(Component)
+ expect(wrapper.findAll('.span')).toHaveLength(2)
})
- const wrapper = mount(Component)
- expect(wrapper.findAll('.span')).toHaveLength(2)
-})
\ No newline at end of file
+ test('findAll using multiple root nodes', () => {
+ const Component = defineComponent({
+ render() {
+ return [
+ h('span', { className: 'span' }),
+ h('span', { className: 'span' })
+ ]
+ }
+ })
+
+ const wrapper = mount(Component)
+ expect(wrapper.findAll('.span')).toHaveLength(2)
+ })
+})
diff --git a/tests/html-text.spec.ts b/tests/html-text.spec.ts
index edd0edc7f..ab8990654 100644
--- a/tests/html-text.spec.ts
+++ b/tests/html-text.spec.ts
@@ -2,15 +2,28 @@ import { defineComponent, h } from 'vue'
import { mount } from '../src'
-test('html, text', () => {
- const Component = defineComponent({
- render() {
- return h('div', {}, 'Text content')
- }
+describe('html', () => {
+ it('returns html when mounting single root node', () => {
+ const Component = defineComponent({
+ render() {
+ return h('div', {}, 'Text content')
+ }
+ })
+
+ const wrapper = mount(Component)
+
+ expect(wrapper.html()).toBe('Text content
')
})
- const wrapper = mount(Component)
+ it('returns the html when mounting multiple root nodes', () => {
+ const Component = defineComponent({
+ render() {
+ return [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')]
+ }
+ })
- expect(wrapper.html()).toBe('Text content
')
- expect(wrapper.text()).toBe('Text content')
-})
\ No newline at end of file
+ const wrapper = mount(Component)
+
+ expect(wrapper.html()).toBe('foo
bar
baz
')
+ })
+})
diff --git a/tests/setChecked.spec.ts b/tests/setChecked.spec.ts
index f40550c44..4146820ea 100644
--- a/tests/setChecked.spec.ts
+++ b/tests/setChecked.spec.ts
@@ -13,7 +13,7 @@ describe('setChecked', () => {
const wrapper = mount(Comp)
await wrapper.setChecked()
- expect(wrapper.vm.$el.checked).toBe(true)
+ expect(wrapper.componentVM.$el.checked).toBe(true)
})
it('sets element checked true with no option passed', async () => {
@@ -64,11 +64,12 @@ describe('setChecked', () => {
const listener = jest.fn()
const Comp = defineComponent({
setup() {
- return () => h('input', {
- onChange: listener,
- type: 'checkbox',
- checked: true
- })
+ return () =>
+ h('input', {
+ onChange: listener,
+ type: 'checkbox',
+ checked: true
+ })
}
})