Skip to content

Commit

Permalink
fix(stub): re-render of recursive component using wrong cached stub (#…
Browse files Browse the repository at this point in the history
…2057)

* Reproduction for #2039

* fix(stub): re-render of recursive component using wrong cached stub
  • Loading branch information
freakzlike authored May 21, 2023
1 parent 3139a1f commit e47c672
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/createInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ export function createInstance(
// stub out Transition and Transition Group by default.
transformVNodeArgs(
createVNodeTransformer({
rootComponents,
transformers: [
createStubComponentsTransformer({
rootComponents,
Expand Down
17 changes: 8 additions & 9 deletions src/vnodeTransformers/stubComponentsTransformer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { isKeepAlive, isTeleport, VTUVNodeTypeTransformer } from './util'
import {
isKeepAlive,
isRootComponent,
isTeleport,
VTUVNodeTypeTransformer
} from './util'
import {
Transition,
TransitionGroup,
Expand Down Expand Up @@ -177,14 +182,8 @@ export function createStubComponentsTransformer({
})
}

if (
// Don't stub VTU_ROOT component
!instance ||
// Don't stub mounted component on root level
(rootComponents.component === type && !instance?.parent) ||
// Don't stub component with compat wrapper
(rootComponents.functional && rootComponents.functional === type)
) {
// Don't stub root components
if (isRootComponent(rootComponents, type, instance)) {
return type
}

Expand Down
33 changes: 29 additions & 4 deletions src/vnodeTransformers/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isComponent } from '../utils'
import { registerStub } from '../stubs'
import { ConcreteComponent, transformVNodeArgs } from 'vue'
import { Component, ConcreteComponent, transformVNodeArgs } from 'vue'

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

export interface RootComponents {
// Component which has been passed to mount. For functional components it contains a wrapper
component?: Component
// If component is functional then contains the original component otherwise empty
functional?: Component
}
export const isRootComponent = (
rootComponents: RootComponents,
type: VNodeTransformerInputComponentType,
instance: InstanceArgsType
): boolean =>
!!(
!instance ||
// Don't stub mounted component on root level
(rootComponents.component === type && !instance?.parent) ||
// Don't stub component with compat wrapper
(rootComponents.functional && rootComponents.functional === type)
)

export const createVNodeTransformer = ({
rootComponents,
transformers
}: {
rootComponents: RootComponents
transformers: VTUVNodeTypeTransformer[]
}): VNodeArgsTransformerFn => {
const transformationCache: WeakMap<
Expand All @@ -40,8 +61,14 @@ export const createVNodeTransformer = ({
return [originalType, props, children, ...restVNodeArgs]
}

const componentType: VNodeTransformerInputComponentType = originalType

const cachedTransformation = transformationCache.get(originalType)
if (cachedTransformation) {
if (
cachedTransformation &&
// Don't use cache for root component, as it could use stubbed recursive component
!isRootComponent(rootComponents, componentType, instance)
) {
// https://github.com/vuejs/test-utils/issues/1829 & https://github.com/vuejs/test-utils/issues/1888
// Teleport/KeepAlive should return child nodes as a function
if (isTeleport(originalType) || isKeepAlive(originalType)) {
Expand All @@ -50,8 +77,6 @@ export const createVNodeTransformer = ({
return [cachedTransformation, props, children, ...restVNodeArgs]
}

const componentType: VNodeTransformerInputComponentType = originalType

const transformedType = transformers.reduce(
(type, transformer) => transformer(type, instance),
componentType
Expand Down
13 changes: 11 additions & 2 deletions tests/components/RecursiveComponent.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
<template>
<div>
<h2>{{ name }}</h2>
<Hello />
<RecursiveComponent v-if="first" />
<template
v-for="item in items"
:key="item"
>
<RecursiveComponent
:name="item"
/>
</template>
</div>
</template>

<script setup lang="ts">
import Hello from './Hello.vue'
defineProps<{
first?: boolean
name: string
items?: string[]
}>()
</script>
16 changes: 13 additions & 3 deletions tests/shallowMount.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,29 @@ describe('shallowMount', () => {
)
})

it('stub instance of same component', () => {
it('stub instance of same component', async () => {
const wrapper = mount(RecursiveComponent, {
shallow: true,
props: {
first: true
name: '1',
items: ['2']
}
})
expect(wrapper.html()).toEqual(
'<div>\n' +
' <h2>1</h2>\n' +
' <hello-stub></hello-stub>\n' +
' <recursive-component-stub first="false"></recursive-component-stub>\n' +
' <recursive-component-stub name="2"></recursive-component-stub>\n' +
'</div>'
)

expect(wrapper.find('h2').text()).toBe('1')

await wrapper.setProps({
name: '3'
})

expect(wrapper.find('h2').text()).toBe('3')
})

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

0 comments on commit e47c672

Please sign in to comment.