Skip to content

Commit

Permalink
fix: Stub instance of the same component (#1979)
Browse files Browse the repository at this point in the history
* fix: Stub instance of the same component

* update test name

* refactor "don't stub" handling
  • Loading branch information
freakzlike authored Mar 9, 2023
1 parent a96eb32 commit eba8d30
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 18 deletions.
17 changes: 8 additions & 9 deletions src/createInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import {
} from './utils/vueCompatSupport'
import { createVNodeTransformer } from './vnodeTransformers/util'
import {
addToDoNotStubComponents,
createStubComponentsTransformer
createStubComponentsTransformer,
CreateStubComponentsTransformerConfig
} from './vnodeTransformers/stubComponentsTransformer'
import { createStubDirectivesTransformer } from './vnodeTransformers/stubDirectivesTransformer'

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

const rootComponents: CreateStubComponentsTransformerConfig['rootComponents'] =
{}

if (
isFunctionalComponent(originalComponent) ||
isLegacyFunctionalComponent(originalComponent)
Expand All @@ -96,14 +99,14 @@ export function createInstance(
h(originalComponent, { ...props, ...attrs }, slots),
...instanceOptions
})
addToDoNotStubComponents(originalComponent)
rootComponents.functional = originalComponent
} else if (isObjectComponent(originalComponent)) {
component = { ...originalComponent, ...instanceOptions }
} else {
component = originalComponent
}

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

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

// add tracking for emitted events
// this must be done after `createApp`: https://github.com/vuejs/test-utils/issues/436
Expand Down Expand Up @@ -329,6 +327,7 @@ export function createInstance(
createVNodeTransformer({
transformers: [
createStubComponentsTransformer({
rootComponents,
stubs: getComponentsFromStubs(global.stubs),
shallow: options?.shallow,
renderStubDefaultSlot: global.renderStubDefaultSlot
Expand Down
27 changes: 18 additions & 9 deletions src/vnodeTransformers/stubComponentsTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ interface StubOptions {
renderStubDefaultSlot?: boolean
}

const doNotStubComponents: WeakSet<ConcreteComponent> = new WeakSet()
const shouldNotStub = (type: ConcreteComponent) => doNotStubComponents.has(type)
export const addToDoNotStubComponents = (type: ConcreteComponent) =>
doNotStubComponents.add(type)

const normalizeStubProps = (props: ComponentPropsOptions) => {
// props are always normalized to object syntax
const $props = props as unknown as ComponentObjectPropsOptions
Expand Down Expand Up @@ -102,13 +97,20 @@ const resolveComponentStubByName = (
}
}

interface CreateStubComponentsTransformerConfig {
export interface CreateStubComponentsTransformerConfig {
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
}
stubs?: Record<string, Component | boolean>
shallow?: boolean
renderStubDefaultSlot: boolean
}

export function createStubComponentsTransformer({
rootComponents,
stubs = {},
shallow = false,
renderStubDefaultSlot = false
Expand Down Expand Up @@ -162,7 +164,14 @@ export function createStubComponentsTransformer({
})
}

if (shouldNotStub(type)) {
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)
) {
return type
}

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

const newStub =
return (
config.plugins.createStubs?.({
name: stubName,
component: type
Expand All @@ -228,7 +237,7 @@ export function createStubComponentsTransformer({
type,
renderStubDefaultSlot
})
return newStub
)
}

return type
Expand Down
14 changes: 14 additions & 0 deletions tests/components/RecursiveComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<div>
<Hello />
<RecursiveComponent v-if="first" />
</div>
</template>

<script setup lang="ts">
import Hello from './Hello.vue'
defineProps<{
first?: boolean
}>()
</script>
16 changes: 16 additions & 0 deletions tests/shallowMount.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mount, shallowMount, VueWrapper } from '../src'
import ComponentWithChildren from './components/ComponentWithChildren.vue'
import ScriptSetupWithChildren from './components/ScriptSetupWithChildren.vue'
import DynamicComponentWithComputedProperty from './components/DynamicComponentWithComputedProperty.vue'
import RecursiveComponent from './components/RecursiveComponent.vue'

describe('shallowMount', () => {
it('renders props for stubbed component in a snapshot', () => {
Expand Down Expand Up @@ -73,6 +74,21 @@ describe('shallowMount', () => {
)
})

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

it('correctly renders slot content', () => {
const ComponentWithSlot = defineComponent({
template: '<div><slot></slot></div>'
Expand Down

0 comments on commit eba8d30

Please sign in to comment.