Skip to content

Commit 25dfe7f

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 df6c234 commit 25dfe7f

File tree

7 files changed

+93
-23
lines changed

7 files changed

+93
-23
lines changed

src/mount.ts

Lines changed: 3 additions & 20 deletions
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

Lines changed: 6 additions & 1 deletion
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()

tests/components/DefineExpose.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<template>
2+
<div id="root">
3+
<div id="msg">{{ msg }}</div>
4+
<div>{{ other }}</div>
5+
</div>
6+
</template>
7+
8+
<script lang="ts">
9+
import { defineComponent, ref } from 'vue'
10+
11+
export default defineComponent({
12+
name: 'Hello',
13+
14+
setup(props, { expose }) {
15+
const other = ref('other')
16+
expose({ other })
17+
return {
18+
msg: ref('Hello world'),
19+
other
20+
}
21+
}
22+
})
23+
</script>
Lines changed: 19 additions & 0 deletions
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/expose.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { mount } from '../src'
2+
import Hello from './components/Hello.vue'
3+
import DefineExpose from './components/DefineExpose.vue'
4+
import ScriptSetupExpose from './components/ScriptSetup_Expose.vue'
5+
import ScriptSetup from './components/ScriptSetup.vue'
6+
7+
describe('expose', () => {
8+
it('access vm on simple components', async () => {
9+
const wrapper = mount(Hello)
10+
11+
expect(wrapper.vm.msg).toBe('Hello world')
12+
})
13+
14+
it('access vm on simple components with custom `expose`', async () => {
15+
const wrapper = mount(DefineExpose)
16+
17+
// other is exposed vie `expose`
18+
expect(wrapper.vm.other).toBe('other')
19+
// can access `msg` even if not exposed
20+
expect(wrapper.vm.msg).toBe('Hello world')
21+
})
22+
23+
it('access vm with <script setup> and defineExpose()', async () => {
24+
const wrapper = mount(ScriptSetupExpose)
25+
26+
await wrapper.find('button').trigger('click')
27+
expect(wrapper.html()).toContain('1')
28+
// can access `count` as it is exposed via `defineExpose()`
29+
expect(wrapper.vm.count).toBe(1)
30+
})
31+
32+
it('access vm with <script setup> even without defineExpose()', async () => {
33+
const wrapper = mount(ScriptSetup)
34+
35+
await wrapper.find('button').trigger('click')
36+
expect(wrapper.html()).toContain('1')
37+
// can access `count` even if it is _not_ exposed
38+
expect(wrapper.vm.count).toBe(1)
39+
})
40+
})

tests/vm.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { defineComponent, ref } from 'vue'
2-
32
import { mount } from '../src'
43

54
describe('vm', () => {

tsconfig.volar.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"compilerOptions": {
44
"lib": ["DOM", "ES2020"],
55
"skipLibCheck": true
6-
}
6+
},
7+
"exclude": ["tests/expose.spec.ts"]
78
}

0 commit comments

Comments
 (0)