Skip to content

Commit e47c672

Browse files
authored
fix(stub): re-render of recursive component using wrong cached stub (#2057)
* Reproduction for #2039 * fix(stub): re-render of recursive component using wrong cached stub
1 parent 3139a1f commit e47c672

File tree

5 files changed

+62
-18
lines changed

5 files changed

+62
-18
lines changed

src/createInstance.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ export function createInstance(
307307
// stub out Transition and Transition Group by default.
308308
transformVNodeArgs(
309309
createVNodeTransformer({
310+
rootComponents,
310311
transformers: [
311312
createStubComponentsTransformer({
312313
rootComponents,

src/vnodeTransformers/stubComponentsTransformer.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { isKeepAlive, isTeleport, VTUVNodeTypeTransformer } from './util'
1+
import {
2+
isKeepAlive,
3+
isRootComponent,
4+
isTeleport,
5+
VTUVNodeTypeTransformer
6+
} from './util'
27
import {
38
Transition,
49
TransitionGroup,
@@ -177,14 +182,8 @@ export function createStubComponentsTransformer({
177182
})
178183
}
179184

180-
if (
181-
// Don't stub VTU_ROOT component
182-
!instance ||
183-
// Don't stub mounted component on root level
184-
(rootComponents.component === type && !instance?.parent) ||
185-
// Don't stub component with compat wrapper
186-
(rootComponents.functional && rootComponents.functional === type)
187-
) {
185+
// Don't stub root components
186+
if (isRootComponent(rootComponents, type, instance)) {
188187
return type
189188
}
190189

src/vnodeTransformers/util.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { isComponent } from '../utils'
22
import { registerStub } from '../stubs'
3-
import { ConcreteComponent, transformVNodeArgs } from 'vue'
3+
import { Component, ConcreteComponent, transformVNodeArgs } from 'vue'
44

55
type VNodeArgsTransformerFn = NonNullable<
66
Parameters<typeof transformVNodeArgs>[0]
@@ -23,9 +23,30 @@ export type VTUVNodeTypeTransformer = (
2323
export const isTeleport = (type: any): boolean => type.__isTeleport
2424
export const isKeepAlive = (type: any): boolean => type.__isKeepAlive
2525

26+
export interface RootComponents {
27+
// Component which has been passed to mount. For functional components it contains a wrapper
28+
component?: Component
29+
// If component is functional then contains the original component otherwise empty
30+
functional?: Component
31+
}
32+
export const isRootComponent = (
33+
rootComponents: RootComponents,
34+
type: VNodeTransformerInputComponentType,
35+
instance: InstanceArgsType
36+
): boolean =>
37+
!!(
38+
!instance ||
39+
// Don't stub mounted component on root level
40+
(rootComponents.component === type && !instance?.parent) ||
41+
// Don't stub component with compat wrapper
42+
(rootComponents.functional && rootComponents.functional === type)
43+
)
44+
2645
export const createVNodeTransformer = ({
46+
rootComponents,
2747
transformers
2848
}: {
49+
rootComponents: RootComponents
2950
transformers: VTUVNodeTypeTransformer[]
3051
}): VNodeArgsTransformerFn => {
3152
const transformationCache: WeakMap<
@@ -40,8 +61,14 @@ export const createVNodeTransformer = ({
4061
return [originalType, props, children, ...restVNodeArgs]
4162
}
4263

64+
const componentType: VNodeTransformerInputComponentType = originalType
65+
4366
const cachedTransformation = transformationCache.get(originalType)
44-
if (cachedTransformation) {
67+
if (
68+
cachedTransformation &&
69+
// Don't use cache for root component, as it could use stubbed recursive component
70+
!isRootComponent(rootComponents, componentType, instance)
71+
) {
4572
// https://github.com/vuejs/test-utils/issues/1829 & https://github.com/vuejs/test-utils/issues/1888
4673
// Teleport/KeepAlive should return child nodes as a function
4774
if (isTeleport(originalType) || isKeepAlive(originalType)) {
@@ -50,8 +77,6 @@ export const createVNodeTransformer = ({
5077
return [cachedTransformation, props, children, ...restVNodeArgs]
5178
}
5279

53-
const componentType: VNodeTransformerInputComponentType = originalType
54-
5580
const transformedType = transformers.reduce(
5681
(type, transformer) => transformer(type, instance),
5782
componentType
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
<template>
22
<div>
3+
<h2>{{ name }}</h2>
34
<Hello />
4-
<RecursiveComponent v-if="first" />
5+
<template
6+
v-for="item in items"
7+
:key="item"
8+
>
9+
<RecursiveComponent
10+
:name="item"
11+
/>
12+
</template>
513
</div>
614
</template>
715

816
<script setup lang="ts">
917
import Hello from './Hello.vue'
1018
1119
defineProps<{
12-
first?: boolean
20+
name: string
21+
items?: string[]
1322
}>()
1423
</script>

tests/shallowMount.spec.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,29 @@ describe('shallowMount', () => {
7474
)
7575
})
7676

77-
it('stub instance of same component', () => {
77+
it('stub instance of same component', async () => {
7878
const wrapper = mount(RecursiveComponent, {
7979
shallow: true,
8080
props: {
81-
first: true
81+
name: '1',
82+
items: ['2']
8283
}
8384
})
8485
expect(wrapper.html()).toEqual(
8586
'<div>\n' +
87+
' <h2>1</h2>\n' +
8688
' <hello-stub></hello-stub>\n' +
87-
' <recursive-component-stub first="false"></recursive-component-stub>\n' +
89+
' <recursive-component-stub name="2"></recursive-component-stub>\n' +
8890
'</div>'
8991
)
92+
93+
expect(wrapper.find('h2').text()).toBe('1')
94+
95+
await wrapper.setProps({
96+
name: '3'
97+
})
98+
99+
expect(wrapper.find('h2').text()).toBe('3')
90100
})
91101

92102
it('correctly renders slot content', () => {

0 commit comments

Comments
 (0)