diff --git a/src/vueWrapper.ts b/src/vueWrapper.ts index c00310405..27d5f9744 100644 --- a/src/vueWrapper.ts +++ b/src/vueWrapper.ts @@ -26,7 +26,11 @@ function createVMProxy( ): T { return new Proxy(vm, { get(vm, key, receiver) { - if (key in setupState) { + if (vm.$.exposed && vm.$.exposeProxy && key in vm.$.exposeProxy) { + // first if the key is exposed + return Reflect.get(vm.$.exposeProxy, key, receiver) + } else if (key in setupState) { + // second if the key is acccessible from the setupState return Reflect.get(setupState, key, receiver) } else { // vm.$.ctx is the internal context of the vm diff --git a/tests/components/DefineExpose.vue b/tests/components/DefineExpose.vue index d9319ee89..1beb34a99 100644 --- a/tests/components/DefineExpose.vue +++ b/tests/components/DefineExpose.vue @@ -1,7 +1,6 @@ @@ -12,11 +11,56 @@ export default defineComponent({ name: 'Hello', setup(props, { expose }) { - const other = ref('other') - expose({ other }) + /* ------ Common Test Case ------ */ + const exposedState1 = 'exposedState1' + const exposedState2 = 'exposedState2' + + const exposedState2Getter = () => { + return exposedState2; + } + + const exposedRef = ref('exposedRef') + const exposedRefGetter = () => { + return exposedRef.value; + } + + const exposedMethod1 = () => { + + return 'result of exposedMethod1'; + } + + const exposedMethod2 = () => { + return 'result of exposedMethod2'; + } + /* ------ Common Test Case End ------ */ + + const stateNonExposedAndNonReturned = 'stateNonExposedAndNonReturned' + const stateNonExposedAndNonReturnedGetter = () => { + return stateNonExposedAndNonReturned; + } + + const returnedState = ref('returnedState') + + expose({ + /* ------ Common Test Case ------ */ + exposeObjectLiteral: 'exposeObjectLiteral', + + exposedState1, + exposedState2Alias: exposedState2, + exposedState2Getter, + + exposedRef, + exposedRefGetter, + + exposedMethod1, + exposedMethod2Alias: exposedMethod2, + /* ------ Common Test Case End ------ */ + + stateNonExposedAndNonReturnedGetter, + }) + return { - msg: ref('Hello world'), - other + returnedState, } } }) diff --git a/tests/components/DefineExposeWithRenderFunction.vue b/tests/components/DefineExposeWithRenderFunction.vue index 1bbb2ca45..de161e2f2 100644 --- a/tests/components/DefineExposeWithRenderFunction.vue +++ b/tests/components/DefineExposeWithRenderFunction.vue @@ -5,10 +5,48 @@ 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)] + /* ------ Common Test Case ------ */ + const exposedState1 = 'exposedState1' + const exposedState2 = 'exposedState2' + + const exposedState2Getter = () => { + return exposedState2; + } + + const exposedRef = ref('exposedRef') + const exposedRefGetter = () => { + return exposedRef.value; + } + + const exposedMethod1 = () => { + + return 'result of exposedMethod1'; + } + + const exposedMethod2 = () => { + return 'result of exposedMethod2'; + } + /* ------ Common Test Case End ------ */ + + const refUseByRenderFnButNotExposed = ref('refUseByRenderFnButNotExposed') + + expose({ + /* ------ Common Test Case ------ */ + exposeObjectLiteral: 'exposeObjectLiteral', + + exposedState1, + exposedState2Alias: exposedState2, + exposedState2Getter, + + exposedRef, + exposedRefGetter, + + exposedMethod1, + exposedMethod2Alias: exposedMethod2, + /* ------ Common Test Case ------ */ + }) + + return () => [h('div', refUseByRenderFnButNotExposed.value)] } }) diff --git a/tests/components/ScriptSetup_Expose.vue b/tests/components/ScriptSetup_Expose.vue index bbe741de9..369d91138 100644 --- a/tests/components/ScriptSetup_Expose.vue +++ b/tests/components/ScriptSetup_Expose.vue @@ -3,21 +3,67 @@ import { ref } from 'vue' import Hello from './Hello.vue' +/* ------ Common Test Case ------ */ +const exposedState1 = 'exposedState1' +const exposedState2 = 'exposedState2' + +const exposedState2Getter = () => { + return exposedState2; +} + +const exposedRef = ref('exposedRef') +const exposedRefGetter = () => { + return exposedRef.value; +} + +const exposedMethod1 = () => { + + return 'result of exposedMethod1'; +} + +const exposedMethod2 = () => { + return 'result of exposedMethod2'; +} +/* ------ Common Test Case End ------ */ + +const refNonExposed = ref('refNonExposed') +const refNonExposedGetter = () => { + return refNonExposed.value; +} + const count = ref(0) const inc = () => { count.value++ } + const resetCount = () => { count.value = 0 } + defineExpose({ + /* ------ Common Test Case ------ */ + exposeObjectLiteral: 'exposeObjectLiteral', + + exposedState1, + exposedState2Alias: exposedState2, + exposedState2Getter, + + exposedRef, + exposedRefGetter, + + exposedMethod1, + exposedMethod2Alias: exposedMethod2, + /* ------ Common Test Case End ------ */ + count, - resetCount + resetCount, + refNonExposedGetter, }) diff --git a/tests/expose.spec.ts b/tests/expose.spec.ts index 357d4000b..6212ddec3 100644 --- a/tests/expose.spec.ts +++ b/tests/expose.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { nextTick } from 'vue' 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' @@ -9,43 +9,89 @@ import ScriptSetup from './components/ScriptSetup.vue' import ScriptSetupWithProps from './components/ScriptSetupWithProps.vue' describe('expose', () => { - it('access vm on simple components', async () => { - const wrapper = mount(Hello) + const commonTests = (vm: any) => { + // exposedState1 is exposed vie `expose` and aliased to `exposedState1` + expect(vm.exposedState1).toBe('exposedState1') + // exposedState2 is exposed vie `expose` and aliased to `exposedState2Alias` + expect(vm.exposedState2Alias).toBe('exposedState2') + + // exposed state can be changed but will not affect the original state + vm.exposedState2Alias = 'newExposedState2' + expect(vm.exposedState2Alias).toBe('newExposedState2') + expect(vm.exposedState2Getter()).toBe('exposedState2') + + // exposed ref can be changed and will affect the original ref + // @ts-ignore upstream issue, see https://github.com/vuejs/vue-next/issues/4397#issuecomment-957613874 + expect(vm.exposedRef).toBe('exposedRef') + vm.exposedRef = 'newExposedRef' + expect(vm.exposedRef).toBe('newExposedRef') + expect(vm.exposedRefGetter()).toBe('newExposedRef') - expect(wrapper.vm.msg).toBe('Hello world') - }) + // exposedMethod1 is exposed vie `expose` + expect(vm.exposedMethod1).not.toBe(undefined) + expect(vm.exposedMethod1()).toBe('result of exposedMethod1') + + // exposedMethod2 is exposed vie `expose` and aliased to `exposedMethod2Alias` + expect(vm.exposedMethod2Alias).not.toBe(undefined) + expect(vm.exposedMethod2Alias()).toBe('result of exposedMethod2') + } it('access vm on simple components with custom `expose`', async () => { const wrapper = mount(DefineExpose) + const vm = wrapper.vm + + commonTests(vm) + + // returned state shuold be accessible + expect(vm.returnedState).toBe('returnedState') - // other is exposed vie `expose` - expect(wrapper.vm.other).toBe('other') - // can access `msg` even if not exposed - expect(wrapper.vm.msg).toBe('Hello world') + // non-exposed and non-returned state should not be accessible + expect( + (vm as unknown as { stateNonExposedAndNonReturned: undefined }) + .stateNonExposedAndNonReturned + ).toBe(undefined) }) it('access vm on simple components with custom `expose` and a setup returning a render function', async () => { const wrapper = mount(DefineExposeWithRenderFunction) + const vm = wrapper.vm - // other is exposed vie `expose` - // @ts-ignore upstream issue, see https://github.com/vuejs/vue-next/issues/4397#issuecomment-957613874 - expect(wrapper.vm.other).toBe('other') - // can't access `msg` as it is not exposed + commonTests(vm) + + // can't access `refUseByRenderFnButNotExposed` as it is not exposed // and we are in a component with a setup returning a render function - expect((wrapper.vm as unknown as { msg: undefined }).msg).toBeUndefined() + expect( + (vm as unknown as { refUseByRenderFnButNotExposed: undefined }) + .refUseByRenderFnButNotExposed + ).toBeUndefined() }) it('access vm with