Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(stub): re-render of recursive component using wrong cached stub #2057

Merged
merged 2 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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