Skip to content

Commit

Permalink
refactor: use a proxy for wrapper.vm
Browse files Browse the repository at this point in the history
  • Loading branch information
cexbrayat committed Nov 11, 2022
1 parent cc2c2b3 commit 87190c3
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 5 deletions.
46 changes: 41 additions & 5 deletions src/vueWrapper.ts
Expand Up @@ -21,6 +21,40 @@ import {
import { VNode } from '@vue/runtime-core'
import { ShapeFlags } from './utils/vueShared'

/**
* Creates a proxy around the VM instance.
* This proxy returns the value from the setupState if there is one, or the one from the VM if not.
* See https://github.com/vuejs/core/issues/7103
*/
function createVMProxy<T extends ComponentPublicInstance>(
vm: T,
setupState: Record<string, any>
): T {
return new Proxy(vm, {
get(vm, key, receiver) {
if (key in setupState) {
return Reflect.get(setupState, key, receiver)
} else {
return (vm as any)[key]
}
},
set(vm, key, value, receiver) {
if (key in setupState) {
return Reflect.set(setupState, key, value, receiver)
} else {
return Reflect.set(vm, key, value, receiver)
}
},
getOwnPropertyDescriptor(vm, property) {
if (property in setupState) {
return Reflect.getOwnPropertyDescriptor(setupState, property)
} else {
return Reflect.getOwnPropertyDescriptor(vm, property)
}
}
})
}

export class VueWrapper<
T extends Omit<
ComponentPublicInstance,
Expand All @@ -46,20 +80,22 @@ 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.$.setupState` 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)`
// 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 =
if (
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.componentVM = createVMProxy<T>(vm, (vm.$ as any).setupState)
} else {
this.componentVM = vm
}
this.__setProps = setProps

this.attachNativeEventListener()
Expand Down
6 changes: 6 additions & 0 deletions tests/expose.spec.ts
@@ -1,4 +1,5 @@
import { describe, expect, it } from 'vitest'
import { nextTick } from 'vue'
import { mount } from '../src'
import Hello from './components/Hello.vue'
import DefineExpose from './components/DefineExpose.vue'
Expand Down Expand Up @@ -54,5 +55,10 @@ describe('expose', () => {
// can access `count` even if it is _not_ exposed
// @ts-ignore we need better types here, see https://github.com/vuejs/test-utils/issues/972
expect(wrapper.vm.count).toBe(1)

// @ts-ignore we need better types here, see https://github.com/vuejs/test-utils/issues/972
wrapper.vm.count = 2
await nextTick()
expect(wrapper.html()).toContain('2')
})
})

0 comments on commit 87190c3

Please sign in to comment.