Skip to content

Commit 962e607

Browse files
committed
Merge branch 'main' into regression/1476/reactive-update
2 parents f1d10dc + cc4f7bb commit 962e607

File tree

7 files changed

+530
-382
lines changed

7 files changed

+530
-382
lines changed

package.json

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@
55
"main": "dist/vue-test-utils.cjs.js",
66
"unpkg": "dist/vue-test-utils.browser.js",
77
"types": "dist/index.d.ts",
8-
"module": "dist/vue-test-utils.esm-bundler.js",
8+
"module": "dist/vue-test-utils.esm-bundler.mjs",
9+
"exports": {
10+
".": {
11+
"types": "./dist/index.d.ts",
12+
"import": "./dist/vue-test-utils.esm-bundler.mjs",
13+
"browser": "./dist/vue-test-utils.browser.js",
14+
"require": "./dist/vue-test-utils.cjs.js",
15+
"default": "./dist/vue-test-utils.cjs.js"
16+
}
17+
},
918
"files": [
1019
"dist",
1120
"README.md",
@@ -17,42 +26,42 @@
1726
"@babel/types": "^7.17.0",
1827
"@rollup/plugin-commonjs": "^22.0.0",
1928
"@rollup/plugin-json": "^4.1.0",
20-
"@rollup/plugin-node-resolve": "^13.2.1",
29+
"@rollup/plugin-node-resolve": "^13.3.0",
2130
"@rollup/plugin-replace": "^4.0.0",
22-
"@types/jest": "27.4.1",
23-
"@types/node": "17.0.30",
31+
"@types/jest": "27.5.0",
32+
"@types/node": "17.0.32",
2433
"@types/pretty": "^2.0.1",
25-
"@typescript-eslint/eslint-plugin": "^5.21.0",
26-
"@typescript-eslint/parser": "^5.21.0",
34+
"@typescript-eslint/eslint-plugin": "^5.23.0",
35+
"@typescript-eslint/parser": "^5.23.0",
2736
"@vue/babel-plugin-jsx": "^1.1.1",
2837
"@vue/compat": "3.2.33",
2938
"@vue/compiler-dom": "3.2.33",
3039
"@vue/compiler-sfc": "3.2.33",
3140
"@vue/vue3-jest": "27.0.0-alpha.4",
3241
"babel-jest": "27.5.1",
3342
"babel-preset-jest": "28.0.2",
34-
"eslint": "^8.14.0",
43+
"eslint": "^8.15.0",
3544
"eslint-config-prettier": "^8.5.0",
3645
"eslint-plugin-prettier": "^4.0.0",
37-
"husky": "^7.0.4",
46+
"husky": "^8.0.1",
3847
"jest": "27.5.1",
3948
"jsdom": "^19.0.0",
4049
"jsdom-global": "^3.0.2",
4150
"lint-staged": "^12.4.1",
4251
"prettier": "^2.6.2",
4352
"pretty": "^2.0.0",
4453
"reflect-metadata": "^0.1.13",
45-
"rollup": "^2.70.2",
54+
"rollup": "^2.73.0",
4655
"rollup-plugin-typescript2": "^0.31.2",
4756
"ts-jest": "27.1.4",
4857
"tslib": "2.4.0",
4958
"typescript": "4.6.4",
50-
"vitepress": "^0.22.3",
59+
"vitepress": "^0.22.4",
5160
"vue": "3.2.33",
5261
"vue-class-component": "^8.0.0-rc.1",
5362
"vue-jest": "^5.0.0-alpha.10",
54-
"vue-router": "^4.0.14",
55-
"vue-tsc": "0.34.11",
63+
"vue-router": "^4.0.15",
64+
"vue-tsc": "0.34.13",
5665
"vuex": "^4.0.2"
5766
},
5867
"peerDependencies": {

src/config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { GlobalMountOptions } from './types'
1+
import { GlobalMountOptions, Stub } from './types'
22
import { VueWrapper } from './vueWrapper'
33
import { DOMWrapper } from './domWrapper'
44
import { CustomCreateStub } from './stubs'
55

66
export interface GlobalConfigOptions {
7-
global: Required<GlobalMountOptions>
7+
global: Required<Omit<GlobalMountOptions, 'stubs'>> & {
8+
stubs: Record<string, Stub>
9+
}
810
plugins: {
911
VueWrapper: Pluggable<VueWrapper>
1012
DOMWrapper: Pluggable<DOMWrapper<Node>>

src/vueWrapper.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
registerFactory,
2222
WrapperType
2323
} from './wrapperFactory'
24+
import { VNode } from '@vue/runtime-core'
2425

2526
export class VueWrapper<
2627
T extends Omit<
@@ -66,8 +67,40 @@ export class VueWrapper<
6667
}
6768

6869
private get hasMultipleRoots(): boolean {
69-
// if the subtree is an array of children, we have multiple root nodes
70-
return this.vm.$.subTree.shapeFlag === ShapeFlags.ARRAY_CHILDREN
70+
// Recursive check subtree for nested root elements
71+
// <template>
72+
// <WithMultipleRoots />
73+
// </template>
74+
const checkTree = (subTree: VNode): boolean => {
75+
// if the subtree is an array of children, we have multiple root nodes
76+
if (subTree.shapeFlag === ShapeFlags.ARRAY_CHILDREN) return true
77+
78+
if (subTree.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
79+
// Component has multiple children or slot with multiple children
80+
if (
81+
subTree.shapeFlag & ShapeFlags.ARRAY_CHILDREN ||
82+
subTree.shapeFlag & ShapeFlags.SLOTS_CHILDREN
83+
) {
84+
return true
85+
}
86+
87+
if (subTree.component?.subTree) {
88+
return checkTree(subTree.component.subTree)
89+
}
90+
} else if (subTree.shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) {
91+
if (subTree.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
92+
return true
93+
}
94+
95+
if (subTree.component?.subTree) {
96+
return checkTree(subTree.component.subTree)
97+
}
98+
}
99+
100+
return false
101+
}
102+
103+
return checkTree(this.vm.$.subTree)
71104
}
72105

73106
protected getRootNodes(): VueNode[] {

test-dts/global.d-test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { config } from '../src'
2+
3+
config.global.stubs.RouterLink = true

tests/element.spec.ts

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import { defineComponent, h } from 'vue'
22

33
import { mount } from '../src'
44

5+
const MultiRootText = defineComponent({
6+
render: () => [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')]
7+
})
8+
const ReturnSlot = defineComponent({
9+
render() {
10+
return this.$slots.default!({})
11+
}
12+
})
13+
514
describe('element', () => {
615
it('returns element when mounting single root node', () => {
716
const Component = defineComponent({
@@ -16,14 +25,66 @@ describe('element', () => {
1625
})
1726

1827
it('returns the VTU root element when mounting multiple root nodes', () => {
19-
const Component = defineComponent({
20-
render() {
21-
return [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')]
22-
}
28+
const wrapper = mount(MultiRootText)
29+
30+
expect(wrapper.element.innerHTML).toBe(
31+
'<div>foo</div><div>bar</div><div>baz</div>'
32+
)
33+
})
34+
35+
it('returns correct element for root component with multiple roots', () => {
36+
const Parent = defineComponent({
37+
components: { MultiRootText },
38+
template: '<MultiRootText/>'
2339
})
2440

25-
const wrapper = mount(Component)
41+
const wrapper = mount(Parent)
42+
43+
expect(wrapper.findComponent(MultiRootText).text()).toBe('foobarbaz')
44+
expect(wrapper.text()).toBe('foobarbaz')
45+
})
46+
47+
it('returns correct element for root slot', () => {
48+
const Parent = defineComponent({
49+
components: { ReturnSlot },
50+
template: `
51+
<ReturnSlot>
52+
<div>foo</div>
53+
<div>bar</div>
54+
<div>baz</div>
55+
</ReturnSlot>`
56+
})
57+
58+
const wrapper = mount(Parent)
59+
expect(wrapper.element.innerHTML).toBe(
60+
'<div>foo</div><div>bar</div><div>baz</div>'
61+
)
62+
})
63+
64+
it('should return element for multi root functional component', () => {
65+
const Foo = () => h(MultiRootText)
66+
const wrapper = mount(Foo)
67+
68+
expect(wrapper.element.innerHTML).toBe(
69+
'<div>foo</div><div>bar</div><div>baz</div>'
70+
)
71+
})
72+
73+
it('returns correct element for root slot with functional component', () => {
74+
const wrapper = mount(() =>
75+
h(ReturnSlot, {}, () => [
76+
h('div', {}, 'foo'),
77+
h('div', {}, 'bar'),
78+
h('div', {}, 'baz')
79+
])
80+
)
81+
expect(wrapper.element.innerHTML).toBe(
82+
'<div>foo</div><div>bar</div><div>baz</div>'
83+
)
84+
})
2685

86+
it('returns correct element for root slot with nested component', () => {
87+
const wrapper = mount(() => h(ReturnSlot, {}, () => h(MultiRootText)))
2788
expect(wrapper.element.innerHTML).toBe(
2889
'<div>foo</div><div>bar</div><div>baz</div>'
2990
)

tests/text.spec.ts

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import { defineComponent, h } from 'vue'
22

33
import { mount } from '../src'
44

5+
const MultiRootText = defineComponent({
6+
render: () => [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')]
7+
})
8+
const ReturnSlot = defineComponent({
9+
render() {
10+
return this.$slots.default!({})
11+
}
12+
})
13+
514
describe('text', () => {
615
it('returns text when mounting single root node', () => {
716
const Component = defineComponent({
@@ -16,13 +25,7 @@ describe('text', () => {
1625
})
1726

1827
it('returns text when mounting multiple root nodes', () => {
19-
const Component = defineComponent({
20-
render() {
21-
return [h('div', {}, 'foo'), h('div', {}, 'bar'), h('div', {}, 'baz')]
22-
}
23-
})
24-
25-
const wrapper = mount(Component)
28+
const wrapper = mount(MultiRootText)
2629

2730
expect(wrapper.text()).toBe('foobarbaz')
2831
})
@@ -39,4 +42,54 @@ describe('text', () => {
3942

4043
expect(wrapper.text()).toBe('')
4144
})
45+
46+
it('returns correct text for root component with multiple roots', () => {
47+
const Parent = defineComponent({
48+
components: { MultiRootText },
49+
template: '<MultiRootText/>'
50+
})
51+
52+
const wrapper = mount(Parent)
53+
54+
expect(wrapper.findComponent(MultiRootText).text()).toBe('foobarbaz')
55+
expect(wrapper.text()).toBe('foobarbaz')
56+
})
57+
58+
it('returns correct text for root slot', () => {
59+
const Parent = defineComponent({
60+
components: { ReturnSlot },
61+
template: `
62+
<ReturnSlot>
63+
<div>foo</div>
64+
<div>bar</div>
65+
<div>baz</div>
66+
</ReturnSlot>`
67+
})
68+
69+
const wrapper = mount(Parent)
70+
expect(wrapper.text()).toBe('foobarbaz')
71+
})
72+
73+
it('should return text for multi root functional component', () => {
74+
const Foo = () => h(MultiRootText)
75+
const wrapper = mount(Foo)
76+
77+
expect(wrapper.text()).toBe('foobarbaz')
78+
})
79+
80+
it('returns correct text for root slot with functional component', () => {
81+
const wrapper = mount(() =>
82+
h(ReturnSlot, {}, () => [
83+
h('div', {}, 'foo'),
84+
h('div', {}, 'bar'),
85+
h('div', {}, 'baz')
86+
])
87+
)
88+
expect(wrapper.text()).toBe('foobarbaz')
89+
})
90+
91+
it('returns correct text for root slot with nested component', () => {
92+
const wrapper = mount(() => h(ReturnSlot, {}, () => h(MultiRootText)))
93+
expect(wrapper.text()).toBe('foobarbaz')
94+
})
4295
})

0 commit comments

Comments
 (0)