Skip to content

Commit e235e3a

Browse files
authored
fix: modify createVMProxy (#2125)
resolves #2116
1 parent a304f59 commit e235e3a

File tree

5 files changed

+209
-31
lines changed

5 files changed

+209
-31
lines changed

src/vueWrapper.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ function createVMProxy<T extends ComponentPublicInstance>(
2626
): T {
2727
return new Proxy(vm, {
2828
get(vm, key, receiver) {
29-
if (key in setupState) {
29+
if (vm.$.exposed && vm.$.exposeProxy && key in vm.$.exposeProxy) {
30+
// first if the key is exposed
31+
return Reflect.get(vm.$.exposeProxy, key, receiver)
32+
} else if (key in setupState) {
33+
// second if the key is acccessible from the setupState
3034
return Reflect.get(setupState, key, receiver)
3135
} else {
3236
// vm.$.ctx is the internal context of the vm

tests/components/DefineExpose.vue

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<template>
22
<div id="root">
3-
<div id="msg">{{ msg }}</div>
4-
<div>{{ other }}</div>
3+
<div id="msg">{{ returnedState }}</div>
54
</div>
65
</template>
76

@@ -12,11 +11,56 @@ export default defineComponent({
1211
name: 'Hello',
1312
1413
setup(props, { expose }) {
15-
const other = ref('other')
16-
expose({ other })
14+
/* ------ Common Test Case ------ */
15+
const exposedState1 = 'exposedState1'
16+
const exposedState2 = 'exposedState2'
17+
18+
const exposedState2Getter = () => {
19+
return exposedState2;
20+
}
21+
22+
const exposedRef = ref('exposedRef')
23+
const exposedRefGetter = () => {
24+
return exposedRef.value;
25+
}
26+
27+
const exposedMethod1 = () => {
28+
29+
return 'result of exposedMethod1';
30+
}
31+
32+
const exposedMethod2 = () => {
33+
return 'result of exposedMethod2';
34+
}
35+
/* ------ Common Test Case End ------ */
36+
37+
const stateNonExposedAndNonReturned = 'stateNonExposedAndNonReturned'
38+
const stateNonExposedAndNonReturnedGetter = () => {
39+
return stateNonExposedAndNonReturned;
40+
}
41+
42+
const returnedState = ref('returnedState')
43+
44+
expose({
45+
/* ------ Common Test Case ------ */
46+
exposeObjectLiteral: 'exposeObjectLiteral',
47+
48+
exposedState1,
49+
exposedState2Alias: exposedState2,
50+
exposedState2Getter,
51+
52+
exposedRef,
53+
exposedRefGetter,
54+
55+
exposedMethod1,
56+
exposedMethod2Alias: exposedMethod2,
57+
/* ------ Common Test Case End ------ */
58+
59+
stateNonExposedAndNonReturnedGetter,
60+
})
61+
1762
return {
18-
msg: ref('Hello world'),
19-
other
63+
returnedState,
2064
}
2165
}
2266
})

tests/components/DefineExposeWithRenderFunction.vue

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,48 @@ export default defineComponent({
55
name: 'Hello',
66
77
setup(props, { expose }) {
8-
const other = ref('other')
9-
const msg = ref('Hello world')
10-
expose({ other })
11-
return () => [h('div', msg.value), h('div', other.value)]
8+
/* ------ Common Test Case ------ */
9+
const exposedState1 = 'exposedState1'
10+
const exposedState2 = 'exposedState2'
11+
12+
const exposedState2Getter = () => {
13+
return exposedState2;
14+
}
15+
16+
const exposedRef = ref('exposedRef')
17+
const exposedRefGetter = () => {
18+
return exposedRef.value;
19+
}
20+
21+
const exposedMethod1 = () => {
22+
23+
return 'result of exposedMethod1';
24+
}
25+
26+
const exposedMethod2 = () => {
27+
return 'result of exposedMethod2';
28+
}
29+
/* ------ Common Test Case End ------ */
30+
31+
const refUseByRenderFnButNotExposed = ref('refUseByRenderFnButNotExposed')
32+
33+
expose({
34+
/* ------ Common Test Case ------ */
35+
exposeObjectLiteral: 'exposeObjectLiteral',
36+
37+
exposedState1,
38+
exposedState2Alias: exposedState2,
39+
exposedState2Getter,
40+
41+
exposedRef,
42+
exposedRefGetter,
43+
44+
exposedMethod1,
45+
exposedMethod2Alias: exposedMethod2,
46+
/* ------ Common Test Case ------ */
47+
})
48+
49+
return () => [h('div', refUseByRenderFnButNotExposed.value)]
1250
}
1351
})
1452
</script>

tests/components/ScriptSetup_Expose.vue

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,67 @@
33
import { ref } from 'vue'
44
import Hello from './Hello.vue'
55
6+
/* ------ Common Test Case ------ */
7+
const exposedState1 = 'exposedState1'
8+
const exposedState2 = 'exposedState2'
9+
10+
const exposedState2Getter = () => {
11+
return exposedState2;
12+
}
13+
14+
const exposedRef = ref('exposedRef')
15+
const exposedRefGetter = () => {
16+
return exposedRef.value;
17+
}
18+
19+
const exposedMethod1 = () => {
20+
21+
return 'result of exposedMethod1';
22+
}
23+
24+
const exposedMethod2 = () => {
25+
return 'result of exposedMethod2';
26+
}
27+
/* ------ Common Test Case End ------ */
28+
29+
const refNonExposed = ref('refNonExposed')
30+
const refNonExposedGetter = () => {
31+
return refNonExposed.value;
32+
}
33+
634
const count = ref(0)
735
const inc = () => {
836
count.value++
937
}
38+
1039
const resetCount = () => {
1140
count.value = 0
1241
}
1342
43+
1444
defineExpose({
45+
/* ------ Common Test Case ------ */
46+
exposeObjectLiteral: 'exposeObjectLiteral',
47+
48+
exposedState1,
49+
exposedState2Alias: exposedState2,
50+
exposedState2Getter,
51+
52+
exposedRef,
53+
exposedRefGetter,
54+
55+
exposedMethod1,
56+
exposedMethod2Alias: exposedMethod2,
57+
/* ------ Common Test Case End ------ */
58+
1559
count,
16-
resetCount
60+
resetCount,
61+
refNonExposedGetter,
1762
})
1863
</script>
1964

2065
<template>
2166
<button @click="inc">{{ count }}</button>
67+
<div>{{ refNonExposed }}</div>
2268
<Hello />
2369
</template>

tests/expose.spec.ts

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,97 @@
11
import { describe, expect, it, vi } from 'vitest'
22
import { nextTick } from 'vue'
33
import { mount } from '../src'
4-
import Hello from './components/Hello.vue'
4+
55
import DefineExpose from './components/DefineExpose.vue'
66
import DefineExposeWithRenderFunction from './components/DefineExposeWithRenderFunction.vue'
77
import ScriptSetupExpose from './components/ScriptSetup_Expose.vue'
88
import ScriptSetup from './components/ScriptSetup.vue'
99
import ScriptSetupWithProps from './components/ScriptSetupWithProps.vue'
1010

1111
describe('expose', () => {
12-
it('access vm on simple components', async () => {
13-
const wrapper = mount(Hello)
12+
const commonTests = (vm: any) => {
13+
// exposedState1 is exposed vie `expose` and aliased to `exposedState1`
14+
expect(vm.exposedState1).toBe('exposedState1')
15+
// exposedState2 is exposed vie `expose` and aliased to `exposedState2Alias`
16+
expect(vm.exposedState2Alias).toBe('exposedState2')
17+
18+
// exposed state can be changed but will not affect the original state
19+
vm.exposedState2Alias = 'newExposedState2'
20+
expect(vm.exposedState2Alias).toBe('newExposedState2')
21+
expect(vm.exposedState2Getter()).toBe('exposedState2')
22+
23+
// exposed ref can be changed and will affect the original ref
24+
// @ts-ignore upstream issue, see https://github.com/vuejs/vue-next/issues/4397#issuecomment-957613874
25+
expect(vm.exposedRef).toBe('exposedRef')
26+
vm.exposedRef = 'newExposedRef'
27+
expect(vm.exposedRef).toBe('newExposedRef')
28+
expect(vm.exposedRefGetter()).toBe('newExposedRef')
1429

15-
expect(wrapper.vm.msg).toBe('Hello world')
16-
})
30+
// exposedMethod1 is exposed vie `expose`
31+
expect(vm.exposedMethod1).not.toBe(undefined)
32+
expect(vm.exposedMethod1()).toBe('result of exposedMethod1')
33+
34+
// exposedMethod2 is exposed vie `expose` and aliased to `exposedMethod2Alias`
35+
expect(vm.exposedMethod2Alias).not.toBe(undefined)
36+
expect(vm.exposedMethod2Alias()).toBe('result of exposedMethod2')
37+
}
1738

1839
it('access vm on simple components with custom `expose`', async () => {
1940
const wrapper = mount(DefineExpose)
41+
const vm = wrapper.vm
42+
43+
commonTests(vm)
44+
45+
// returned state shuold be accessible
46+
expect(vm.returnedState).toBe('returnedState')
2047

21-
// other is exposed vie `expose`
22-
expect(wrapper.vm.other).toBe('other')
23-
// can access `msg` even if not exposed
24-
expect(wrapper.vm.msg).toBe('Hello world')
48+
// non-exposed and non-returned state should not be accessible
49+
expect(
50+
(vm as unknown as { stateNonExposedAndNonReturned: undefined })
51+
.stateNonExposedAndNonReturned
52+
).toBe(undefined)
2553
})
2654

2755
it('access vm on simple components with custom `expose` and a setup returning a render function', async () => {
2856
const wrapper = mount(DefineExposeWithRenderFunction)
57+
const vm = wrapper.vm
2958

30-
// other is exposed vie `expose`
31-
// @ts-ignore upstream issue, see https://github.com/vuejs/vue-next/issues/4397#issuecomment-957613874
32-
expect(wrapper.vm.other).toBe('other')
33-
// can't access `msg` as it is not exposed
59+
commonTests(vm)
60+
61+
// can't access `refUseByRenderFnButNotExposed` as it is not exposed
3462
// and we are in a component with a setup returning a render function
35-
expect((wrapper.vm as unknown as { msg: undefined }).msg).toBeUndefined()
63+
expect(
64+
(vm as unknown as { refUseByRenderFnButNotExposed: undefined })
65+
.refUseByRenderFnButNotExposed
66+
).toBeUndefined()
3667
})
3768

3869
it('access vm with <script setup> and defineExpose()', async () => {
3970
const wrapper = mount(ScriptSetupExpose)
71+
const vm = wrapper.vm as unknown as {
72+
inc: () => void
73+
resetCount: () => void
74+
count: number
75+
refNonExposed: string
76+
refNonExposedGetter: () => string
77+
}
78+
79+
commonTests(vm)
4080

4181
await wrapper.find('button').trigger('click')
4282
expect(wrapper.html()).toContain('1')
43-
// can access `count` as it is exposed via `defineExpose()`
44-
expect(wrapper.vm.count).toBe(1)
45-
46-
wrapper.vm.resetCount()
4783

48-
expect(wrapper.vm.count).toBe(0)
84+
// can access state/method/ref as it is exposed via `defineExpose()`
85+
expect(vm.count).toBe(1)
86+
vm.resetCount()
87+
expect(vm.count).toBe(0)
88+
89+
// non-exposed state/method/ref should be accessible
90+
vm.inc()
91+
expect(vm.count).toBe(1)
92+
expect(vm.refNonExposed).toBe('refNonExposed')
93+
vm.refNonExposed = 'newRefNonExposed'
94+
expect(vm.refNonExposedGetter()).toBe('newRefNonExposed')
4995
})
5096

5197
it('access vm with <script setup> even without defineExpose()', async () => {

0 commit comments

Comments
 (0)