Skip to content

Commit

Permalink
fix: expose with render function
Browse files Browse the repository at this point in the history
Fixes #1065

When we are testing a component with a render function returned by the setup,
then its proxy will be empty.
The behavior difference can be seen in vue-next [here](https://github.com/vuejs/vue-next/blob/d19cfc0503dddbded5e599bef0965aba3d25ca28/packages/runtime-core/src/component.ts#L674).
In that case, `wrapper.vm` will be empty if we return `vm.$.proxy`.
To handle it, we return `vm` directly for such components,
allowing at least what is exposed publicly to be available on `wrapper.vm`.
  • Loading branch information
cexbrayat committed Nov 12, 2021
1 parent 389f921 commit 80e9390
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 3 deletions.
14 changes: 11 additions & 3 deletions src/vueWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,20 @@ export class VueWrapper<T extends ComponentPublicInstance>
this.__app = app
// root is null on functional components
this.rootVM = vm?.$root
// vm.$.proxy is what the template has access to
// `vm.$.proxy` is what the template has access to
// so even if the component is closed (as they are by default for `script setup`)
// a test will still be able to do something like
// `expect(wrapper.vm.count).toBe(1)`
// (note that vm can be null for functional components, hence the condition)
this.componentVM = vm ? (vm.$.proxy as T) : (vm as T)
// if we return it as `vm`
// This does not work for functional components though (as they have no vm)
// or for components with a setup that returns a render function (as they have an empty proxy)
// in both cases, we return `vm` directly instead
this.componentVM =
vm &&
// a component with a setup that returns a render function will have no `devtoolsRawSetupState`
(vm.$ as unknown as { devtoolsRawSetupState: any }).devtoolsRawSetupState
? ((vm.$ as any).proxy as T)
: (vm as T)
this.__setProps = setProps

this.attachNativeEventListener()
Expand Down
14 changes: 14 additions & 0 deletions tests/components/DefineExposeWithRenderFunction.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts">
import { defineComponent, h, ref } from 'vue'
export default defineComponent({
name: 'Hello',
setup(props, { expose }) {
const other = ref('other')
const msg = ref('Hello world');
expose({ other })
return () => [h('div', msg.value), h('div', other.value)]
}
})
</script>
11 changes: 11 additions & 0 deletions tests/expose.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mount } from '../src'
import Hello from './components/Hello.vue'
import DefineExpose from './components/DefineExpose.vue'
import DefineExposeWithRenderFunction from './components/DefineExposeWithRenderFunction.vue'
import ScriptSetupExpose from './components/ScriptSetup_Expose.vue'
import ScriptSetup from './components/ScriptSetup.vue'

Expand All @@ -20,6 +21,16 @@ describe('expose', () => {
expect(wrapper.vm.msg).toBe('Hello world')
})

it('access vm on simple components with custom `expose` and a setup returning a render function', async () => {
const wrapper = mount(DefineExposeWithRenderFunction)

// other is exposed vie `expose`
expect(wrapper.vm.other).toBe('other')
// can't access `msg` as it is not exposed
// and we are in a component with a setup returning a render function
expect(wrapper.vm.msg).toBeUndefined()
})

it('access vm with <script setup> and defineExpose()', async () => {
const wrapper = mount(ScriptSetupExpose)

Expand Down

0 comments on commit 80e9390

Please sign in to comment.