Skip to content

Commit eba8d30

Browse files
authored
fix: Stub instance of the same component (#1979)
* fix: Stub instance of the same component * update test name * refactor "don't stub" handling
1 parent a96eb32 commit eba8d30

File tree

4 files changed

+56
-18
lines changed

4 files changed

+56
-18
lines changed

src/createInstance.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import {
3232
} from './utils/vueCompatSupport'
3333
import { createVNodeTransformer } from './vnodeTransformers/util'
3434
import {
35-
addToDoNotStubComponents,
36-
createStubComponentsTransformer
35+
createStubComponentsTransformer,
36+
CreateStubComponentsTransformerConfig
3737
} from './vnodeTransformers/stubComponentsTransformer'
3838
import { createStubDirectivesTransformer } from './vnodeTransformers/stubDirectivesTransformer'
3939

@@ -76,6 +76,9 @@ export function createInstance(
7676
let component: ConcreteComponent
7777
const instanceOptions = getInstanceOptions(options ?? {})
7878

79+
const rootComponents: CreateStubComponentsTransformerConfig['rootComponents'] =
80+
{}
81+
7982
if (
8083
isFunctionalComponent(originalComponent) ||
8184
isLegacyFunctionalComponent(originalComponent)
@@ -96,14 +99,14 @@ export function createInstance(
9699
h(originalComponent, { ...props, ...attrs }, slots),
97100
...instanceOptions
98101
})
99-
addToDoNotStubComponents(originalComponent)
102+
rootComponents.functional = originalComponent
100103
} else if (isObjectComponent(originalComponent)) {
101104
component = { ...originalComponent, ...instanceOptions }
102105
} else {
103106
component = originalComponent
104107
}
105108

106-
addToDoNotStubComponents(component)
109+
rootComponents.component = component
107110
// We've just replaced our component with its copy
108111
// Let's register it as a stub so user can find it
109112
registerStub({ source: originalComponent, stub: component })
@@ -217,11 +220,6 @@ export function createInstance(
217220

218221
// create the app
219222
const app = createApp(Parent)
220-
// the Parent type must not be stubbed
221-
// but we can't add it directly, as createApp creates a copy
222-
// and store it in app._component (since v3.2.32)
223-
// So we store this one instead
224-
addToDoNotStubComponents(app._component)
225223

226224
// add tracking for emitted events
227225
// this must be done after `createApp`: https://github.com/vuejs/test-utils/issues/436
@@ -329,6 +327,7 @@ export function createInstance(
329327
createVNodeTransformer({
330328
transformers: [
331329
createStubComponentsTransformer({
330+
rootComponents,
332331
stubs: getComponentsFromStubs(global.stubs),
333332
shallow: options?.shallow,
334333
renderStubDefaultSlot: global.renderStubDefaultSlot

src/vnodeTransformers/stubComponentsTransformer.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,6 @@ interface StubOptions {
3636
renderStubDefaultSlot?: boolean
3737
}
3838

39-
const doNotStubComponents: WeakSet<ConcreteComponent> = new WeakSet()
40-
const shouldNotStub = (type: ConcreteComponent) => doNotStubComponents.has(type)
41-
export const addToDoNotStubComponents = (type: ConcreteComponent) =>
42-
doNotStubComponents.add(type)
43-
4439
const normalizeStubProps = (props: ComponentPropsOptions) => {
4540
// props are always normalized to object syntax
4641
const $props = props as unknown as ComponentObjectPropsOptions
@@ -102,13 +97,20 @@ const resolveComponentStubByName = (
10297
}
10398
}
10499

105-
interface CreateStubComponentsTransformerConfig {
100+
export interface CreateStubComponentsTransformerConfig {
101+
rootComponents: {
102+
// Component which has been passed to mount. For functional components it contains a wrapper
103+
component?: Component
104+
// If component is functional then contains the original component otherwise empty
105+
functional?: Component
106+
}
106107
stubs?: Record<string, Component | boolean>
107108
shallow?: boolean
108109
renderStubDefaultSlot: boolean
109110
}
110111

111112
export function createStubComponentsTransformer({
113+
rootComponents,
112114
stubs = {},
113115
shallow = false,
114116
renderStubDefaultSlot = false
@@ -162,7 +164,14 @@ export function createStubComponentsTransformer({
162164
})
163165
}
164166

165-
if (shouldNotStub(type)) {
167+
if (
168+
// Don't stub VTU_ROOT component
169+
!instance ||
170+
// Don't stub mounted component on root level
171+
(rootComponents.component === type && !instance?.parent) ||
172+
// Don't stub component with compat wrapper
173+
(rootComponents.functional && rootComponents.functional === type)
174+
) {
166175
return type
167176
}
168177

@@ -218,7 +227,7 @@ export function createStubComponentsTransformer({
218227
// Set name when using shallow without stub
219228
const stubName = name || registeredName || componentName
220229

221-
const newStub =
230+
return (
222231
config.plugins.createStubs?.({
223232
name: stubName,
224233
component: type
@@ -228,7 +237,7 @@ export function createStubComponentsTransformer({
228237
type,
229238
renderStubDefaultSlot
230239
})
231-
return newStub
240+
)
232241
}
233242

234243
return type
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<template>
2+
<div>
3+
<Hello />
4+
<RecursiveComponent v-if="first" />
5+
</div>
6+
</template>
7+
8+
<script setup lang="ts">
9+
import Hello from './Hello.vue'
10+
11+
defineProps<{
12+
first?: boolean
13+
}>()
14+
</script>

tests/shallowMount.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { mount, shallowMount, VueWrapper } from '../src'
44
import ComponentWithChildren from './components/ComponentWithChildren.vue'
55
import ScriptSetupWithChildren from './components/ScriptSetupWithChildren.vue'
66
import DynamicComponentWithComputedProperty from './components/DynamicComponentWithComputedProperty.vue'
7+
import RecursiveComponent from './components/RecursiveComponent.vue'
78

89
describe('shallowMount', () => {
910
it('renders props for stubbed component in a snapshot', () => {
@@ -73,6 +74,21 @@ describe('shallowMount', () => {
7374
)
7475
})
7576

77+
it('stub instance of same component', () => {
78+
const wrapper = mount(RecursiveComponent, {
79+
shallow: true,
80+
props: {
81+
first: true
82+
}
83+
})
84+
expect(wrapper.html()).toEqual(
85+
'<div>\n' +
86+
' <hello-stub></hello-stub>\n' +
87+
' <recursive-component-stub first="false"></recursive-component-stub>\n' +
88+
'</div>'
89+
)
90+
})
91+
7692
it('correctly renders slot content', () => {
7793
const ComponentWithSlot = defineComponent({
7894
template: '<div><slot></slot></div>'

0 commit comments

Comments
 (0)