From 80e93907ad47e00a6532cce96411957581e6a604 Mon Sep 17 00:00:00 2001 From: cexbrayat Date: Fri, 12 Nov 2021 08:57:23 +0100 Subject: [PATCH] fix: expose with render function 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`. --- src/vueWrapper.ts | 14 +++++++++++--- .../components/DefineExposeWithRenderFunction.vue | 14 ++++++++++++++ tests/expose.spec.ts | 11 +++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 tests/components/DefineExposeWithRenderFunction.vue diff --git a/src/vueWrapper.ts b/src/vueWrapper.ts index 59be68aaed..88e640dde3 100644 --- a/src/vueWrapper.ts +++ b/src/vueWrapper.ts @@ -36,12 +36,20 @@ export class VueWrapper 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() diff --git a/tests/components/DefineExposeWithRenderFunction.vue b/tests/components/DefineExposeWithRenderFunction.vue new file mode 100644 index 0000000000..490fc6104f --- /dev/null +++ b/tests/components/DefineExposeWithRenderFunction.vue @@ -0,0 +1,14 @@ + diff --git a/tests/expose.spec.ts b/tests/expose.spec.ts index c4bcc40368..38f0991c17 100644 --- a/tests/expose.spec.ts +++ b/tests/expose.spec.ts @@ -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' @@ -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