Skip to content

Commit 77cbbf6

Browse files
committed
feat: expose everything on wrapper.vm
This commit changes `wrapper.vm` to actually point to `vm.$.proxy`. This shouldn't change the behaviour of existing tests, but allows components written with `script setup` to be tested as well, without the need to expose everything just for testing purposes. For example a component like: ```vue <script setup lang="ts"> import { ref } from 'vue' const count = ref(0) </script> ``` can now be tested like `expect(wrapper.vm.count).toBe(0)`, whereas you previously had to add `defineExpose({ count })` for this to work. The downside is that you now can't test that something is _not_ exposed by a component, but I don't think this is a problem. This also removes the previous hacks for script setup, as it looks like they are no longer necessary.
1 parent e74751f commit 77cbbf6

File tree

4 files changed

+46
-22
lines changed

4 files changed

+46
-22
lines changed

src/mount.ts

+3-20
Original file line numberDiff line numberDiff line change
@@ -337,17 +337,6 @@ export function mount(
337337
const Parent = defineComponent({
338338
name: 'VTU_ROOT',
339339
render() {
340-
// https://github.com/vuejs/vue-test-utils-next/issues/651
341-
// script setup components include an empty `expose` array as part of the
342-
// code generated by the SFC compiler. Eg a component might look like
343-
// { expose: [], setup: [Function], render: [Function] }
344-
// not sure why (yet), but the empty expose array causes events to not be
345-
// correctly captured.
346-
// TODO: figure out why this is happening and understand the implications of
347-
// the expose rfc for Test Utils.
348-
if (isObjectComponent(component)) {
349-
delete component.expose
350-
}
351340
return h(component, props, slots)
352341
}
353342
})
@@ -457,19 +446,13 @@ export function mount(
457446
const warnSave = console.warn
458447
console.warn = () => {}
459448

460-
// get `vm`.
461-
// for some unknown reason, getting the `vm` for components using `<script setup>`
462-
// as of Vue 3.0.3 works differently.
463-
// if `appRef` has keys, use that (vm always has keys like $el, $props etc).
464-
// if not, use the return value from app.mount.
465449
const appRef = vm.$refs[MOUNT_COMPONENT_REF] as ComponentPublicInstance
466-
const $vm = Reflect.ownKeys(appRef).length ? appRef : vm
467450
// we add `hasOwnProperty` so jest can spy on the proxied vm without throwing
468-
$vm.hasOwnProperty = (property) => {
469-
return Reflect.has($vm, property)
451+
appRef.hasOwnProperty = (property) => {
452+
return Reflect.has(appRef, property)
470453
}
471454
console.warn = warnSave
472-
return createWrapper(app, $vm, setProps)
455+
return createWrapper(app, appRef, setProps)
473456
}
474457

475458
export const shallowMount: typeof mount = (component: any, options?: any) => {

src/vueWrapper.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ export class VueWrapper<T extends ComponentPublicInstance>
3636
this.__app = app
3737
// root is null on functional components
3838
this.rootVM = vm?.$root
39-
this.componentVM = vm as T
39+
// vm.$.proxy is what the template has access to
40+
// so even if the component is closed (as they are by default for `script setup`)
41+
// a test will still be able to do something like
42+
// `expect(wrapper.vm.count).toBe(1)`
43+
// (note that vm can be null for functional components, hence the condition)
44+
this.componentVM = vm ? (vm.$.proxy as T) : (vm as T)
4045
this.__setProps = setProps
4146

4247
this.attachNativeEventListener()
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script setup lang="ts">
2+
// imported components are also directly usable in template
3+
import { ref } from 'vue'
4+
import Hello from './Hello.vue'
5+
6+
const count = ref(0)
7+
const inc = () => {
8+
count.value++
9+
}
10+
11+
defineExpose({
12+
count
13+
})
14+
</script>
15+
16+
<template>
17+
<button @click="inc">{{ count }}</button>
18+
<Hello />
19+
</template>

tests/vm.spec.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { defineComponent, ref } from 'vue'
2-
32
import { mount } from '../src'
3+
import ScriptSetup from './components/ScriptSetup.vue'
4+
import ScriptSetupExpose from './components/ScriptSetup_Expose.vue'
45

56
describe('vm', () => {
67
it('returns the component vm', () => {
@@ -45,4 +46,20 @@ describe('vm', () => {
4546

4647
expect(wrapper.vm.toggle).toHaveBeenCalled()
4748
})
49+
50+
it('access vm with <script setup> with defineExpose()', async () => {
51+
const wrapper = mount(ScriptSetupExpose)
52+
53+
await wrapper.find('button').trigger('click')
54+
expect(wrapper.html()).toContain('1')
55+
expect(wrapper.vm.count).toBe(1)
56+
})
57+
58+
it('access vm with <script setup> even without defineExpose()', async () => {
59+
const wrapper = mount(ScriptSetup)
60+
61+
await wrapper.find('button').trigger('click')
62+
expect(wrapper.html()).toContain('1')
63+
expect(wrapper.vm.count).toBe(1)
64+
})
4865
})

0 commit comments

Comments
 (0)