Skip to content

Commit 851032b

Browse files
committed
fix(vuejs#2591): always assign exposed properties to the vm
Should we do this
1 parent a31afb7 commit 851032b

11 files changed

+355
-5
lines changed

src/vueWrapper.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ import { ShapeFlags } from './utils/vueShared'
2222
*/
2323
function createVMProxy<T extends ComponentPublicInstance>(
2424
vm: T,
25-
setupState: Record<string, any>
25+
setupState: Record<string, any>,
26+
exposed: Record<string, any> | null
2627
): T {
2728
return new Proxy(vm, {
2829
get(vm, key, receiver) {
2930
if (vm.$.exposed && vm.$.exposeProxy && key in vm.$.exposeProxy) {
3031
// first if the key is exposed
3132
return Reflect.get(vm.$.exposeProxy, key, receiver)
33+
} else if (exposed && key in exposed) {
34+
// first if the key is exposed
35+
return Reflect.get(exposed, key, receiver)
3236
} else if (key in setupState) {
3337
// second if the key is acccessible from the setupState
3438
return Reflect.get(setupState, key, receiver)
@@ -107,11 +111,31 @@ export class VueWrapper<
107111
// if we return it as `vm`
108112
// This does not work for functional components though (as they have no vm)
109113
// or for components with a setup that returns a render function (as they have an empty proxy)
110-
// in both cases, we return `vm` directly instead
114+
// in both cases, we return `vm` directly instead.
115+
//
116+
// NOTE https://github.com/vuejs/test-utils/issues/2591
117+
// I'm sry i'm not entirely sure why, but exposed properties — via expose/defineExpose
118+
// are not assigned to the componentVM when the the `vm` argument provided
119+
// to this constructor comes from `findComponent` — as in, not the original instance
120+
// but already the proxied one. I suspect that is by design because according
121+
// to the defineExpose docs, the exposed properties become "available for the
122+
// parent component via templateRefs, which is the the case reported in the issue
123+
// (in fact using templateRefs and doing .findComponent({ ref: 'refName' }) works
124+
// as expected). But using "expose" via option or setup script does not keep
125+
// this consistenticy and dependending on how setup return is done, the exposed
126+
// might not be added to the vm even if exposed.
127+
// So this can be considered highjacking the vuiedesign.
128+
// It's up to the VTU to decide if it want to provide the convience of having
129+
// a single interface without
130+
// https://vuejs.org/api/sfc-script-setup.html#defineexpose
131+
// https://vuejs.org/api/composition-api-setup.html#exposing-public-properties
132+
// https://vuejs.org/api/options-state.html#expose
133+
//
111134
if (hasSetupState(vm)) {
112-
this.componentVM = createVMProxy<T>(vm, vm.$.setupState)
135+
this.componentVM = createVMProxy<T>(vm, vm.$.setupState, vm.$.exposed)
113136
} else {
114137
this.componentVM = vm
138+
Object.assign(this.componentVM, vm.$.exposed)
115139
}
116140
this.__setProps = setProps
117141

tests/components/DefineExpose.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export default defineComponent({
6060
})
6161
6262
return {
63+
exposedMethod1,
6364
returnedState,
6465
}
6566
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {
2+
defineComponent,
3+
ref,
4+
openBlock,
5+
createElementBlock,
6+
Fragment,
7+
createElementVNode,
8+
toDisplayString
9+
} from 'vue'
10+
const exposedState1 = 'exposedState1'
11+
const exposedState2 = 'exposedState2'
12+
const _sfc_main = /* @__PURE__ */ defineComponent({
13+
...{
14+
name: 'Hello'
15+
},
16+
__name: 'DefineExposeScriptSetup',
17+
setup(__props, { expose: __expose }) {
18+
const exposedState2Getter = () => {
19+
return exposedState2
20+
}
21+
const exposedRef = ref('exposedRef')
22+
const exposedRefGetter = () => {
23+
return exposedRef.value
24+
}
25+
const exposedMethod1 = () => {
26+
return 'result of exposedMethod1'
27+
}
28+
const exposedMethod2 = () => {
29+
return 'result of exposedMethod2'
30+
}
31+
const refNonExposed = ref('refNonExposed')
32+
const refNonExposedGetter = () => {
33+
return refNonExposed.value
34+
}
35+
const count = ref(0)
36+
const inc = () => {
37+
count.value++
38+
}
39+
const resetCount = () => {
40+
count.value = 0
41+
}
42+
__expose({
43+
exposeObjectLiteral: 'exposeObjectLiteral',
44+
exposedState1,
45+
exposedState2Alias: exposedState2,
46+
exposedState2Getter,
47+
exposedRef,
48+
exposedRefGetter,
49+
exposedMethod1,
50+
exposedMethod2Alias: exposedMethod2,
51+
count,
52+
resetCount,
53+
refNonExposedGetter
54+
})
55+
return (_ctx, _cache) => {
56+
return (
57+
openBlock(),
58+
createElementBlock(
59+
Fragment,
60+
null,
61+
[
62+
createElementVNode(
63+
'button',
64+
{ onClick: inc },
65+
toDisplayString(count.value),
66+
1
67+
),
68+
createElementVNode(
69+
'div',
70+
{ 'force-expose': exposedMethod1 },
71+
toDisplayString(refNonExposed.value),
72+
1
73+
)
74+
],
75+
64
76+
)
77+
)
78+
}
79+
}
80+
})
81+
export default _sfc_main

tests/components/DefineExposeWithRenderFunction.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default defineComponent({
3333
expose({
3434
/* ------ Common Test Case ------ */
3535
exposeObjectLiteral: 'exposeObjectLiteral',
36-
36+
3737
exposedState1,
3838
exposedState2Alias: exposedState2,
3939
exposedState2Getter,
@@ -46,7 +46,8 @@ export default defineComponent({
4646
/* ------ Common Test Case ------ */
4747
})
4848
49-
return () => [h('div', refUseByRenderFnButNotExposed.value)]
49+
return () => [
50+
h('div', refUseByRenderFnButNotExposed.value)]
5051
}
5152
})
5253
</script>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { defineComponent, h } from 'vue'
2+
3+
export default defineComponent({
4+
name: 'FindComponentExposeRenderFunction',
5+
props: {
6+
someProp: String
7+
},
8+
setup(_, { expose }) {
9+
const exposedFn = () => {
10+
return 'exposedFnReturn'
11+
}
12+
13+
expose({
14+
exposedFn
15+
})
16+
17+
return () => {
18+
return h('div', 'Example')
19+
}
20+
}
21+
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<div>Example</div>
3+
</template>
4+
5+
<script setup>
6+
const props = defineProps({
7+
someProp: String,
8+
});
9+
10+
const exposedFn = () => {
11+
return 'exposedFnReturn';
12+
};
13+
14+
defineExpose({
15+
exposedFn,
16+
});
17+
</script>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { openBlock, createElementBlock } from 'vue'
2+
const _sfc_main = {
3+
__name: 'FindComponentExposeScriptSetupBundled',
4+
props: {
5+
someProp: String
6+
},
7+
setup(__props, { expose: __expose }) {
8+
const exposedFn = () => {
9+
return 'exposedFnReturn'
10+
}
11+
__expose({
12+
exposedFn
13+
})
14+
return (_ctx, _cache) => {
15+
return openBlock(), createElementBlock('div', null, 'Example')
16+
}
17+
}
18+
}
19+
export default _sfc_main
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<div>Example</div>
3+
</template>
4+
5+
<script>
6+
import { defineComponent } from 'vue';
7+
8+
export default defineComponent({
9+
name: 'FindComponentExposeTemplate',
10+
props: {
11+
someProp: String,
12+
},
13+
setup(_, { expose }) {
14+
const exposedFn = () => {
15+
return 'exposedFnReturn';
16+
};
17+
18+
expose({
19+
exposedFn,
20+
});
21+
22+
return {
23+
oopsy: 1
24+
};
25+
}
26+
})
27+
</script>
28+

tests/components/ScriptSetup_Expose.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ defineExpose({
6060
resetCount,
6161
refNonExposedGetter,
6262
})
63+
64+
defineOptions({
65+
name: 'Hello',
66+
})
6367
</script>
6468

6569
<template>

0 commit comments

Comments
 (0)