Skip to content

Commit

Permalink
fix(#2591): always assign exposed properties to the vm
Browse files Browse the repository at this point in the history
Should we do this
  • Loading branch information
renatodeleao committed Jan 10, 2025
1 parent a31afb7 commit 851032b
Show file tree
Hide file tree
Showing 11 changed files with 355 additions and 5 deletions.
30 changes: 27 additions & 3 deletions src/vueWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ import { ShapeFlags } from './utils/vueShared'
*/
function createVMProxy<T extends ComponentPublicInstance>(
vm: T,
setupState: Record<string, any>
setupState: Record<string, any>,
exposed: Record<string, any> | null
): T {
return new Proxy(vm, {
get(vm, key, receiver) {
if (vm.$.exposed && vm.$.exposeProxy && key in vm.$.exposeProxy) {
// first if the key is exposed
return Reflect.get(vm.$.exposeProxy, key, receiver)
} else if (exposed && key in exposed) {
// first if the key is exposed
return Reflect.get(exposed, key, receiver)
} else if (key in setupState) {
// second if the key is acccessible from the setupState
return Reflect.get(setupState, key, receiver)
Expand Down Expand Up @@ -107,11 +111,31 @@ export class VueWrapper<
// 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
// in both cases, we return `vm` directly instead.
//
// NOTE https://github.com/vuejs/test-utils/issues/2591
// I'm sry i'm not entirely sure why, but exposed properties — via expose/defineExpose
// are not assigned to the componentVM when the the `vm` argument provided
// to this constructor comes from `findComponent` — as in, not the original instance
// but already the proxied one. I suspect that is by design because according
// to the defineExpose docs, the exposed properties become "available for the
// parent component via templateRefs, which is the the case reported in the issue
// (in fact using templateRefs and doing .findComponent({ ref: 'refName' }) works
// as expected). But using "expose" via option or setup script does not keep
// this consistenticy and dependending on how setup return is done, the exposed
// might not be added to the vm even if exposed.
// So this can be considered highjacking the vuiedesign.
// It's up to the VTU to decide if it want to provide the convience of having
// a single interface without
// https://vuejs.org/api/sfc-script-setup.html#defineexpose
// https://vuejs.org/api/composition-api-setup.html#exposing-public-properties
// https://vuejs.org/api/options-state.html#expose
//
if (hasSetupState(vm)) {
this.componentVM = createVMProxy<T>(vm, vm.$.setupState)
this.componentVM = createVMProxy<T>(vm, vm.$.setupState, vm.$.exposed)
} else {
this.componentVM = vm
Object.assign(this.componentVM, vm.$.exposed)
}
this.__setProps = setProps

Expand Down
1 change: 1 addition & 0 deletions tests/components/DefineExpose.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default defineComponent({
})
return {
exposedMethod1,
returnedState,
}
}
Expand Down
81 changes: 81 additions & 0 deletions tests/components/DefineExposeBundled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
defineComponent,
ref,
openBlock,
createElementBlock,
Fragment,
createElementVNode,
toDisplayString
} from 'vue'
const exposedState1 = 'exposedState1'
const exposedState2 = 'exposedState2'
const _sfc_main = /* @__PURE__ */ defineComponent({
...{
name: 'Hello'
},
__name: 'DefineExposeScriptSetup',
setup(__props, { expose: __expose }) {
const exposedState2Getter = () => {
return exposedState2
}
const exposedRef = ref('exposedRef')
const exposedRefGetter = () => {
return exposedRef.value
}
const exposedMethod1 = () => {
return 'result of exposedMethod1'
}
const exposedMethod2 = () => {
return 'result of exposedMethod2'
}
const refNonExposed = ref('refNonExposed')
const refNonExposedGetter = () => {
return refNonExposed.value
}
const count = ref(0)
const inc = () => {
count.value++
}
const resetCount = () => {
count.value = 0
}
__expose({
exposeObjectLiteral: 'exposeObjectLiteral',
exposedState1,
exposedState2Alias: exposedState2,
exposedState2Getter,
exposedRef,
exposedRefGetter,
exposedMethod1,
exposedMethod2Alias: exposedMethod2,
count,
resetCount,
refNonExposedGetter
})
return (_ctx, _cache) => {

Check failure on line 55 in tests/components/DefineExposeBundled.ts

View workflow job for this annotation

GitHub Actions / build (18)

Parameter '_ctx' implicitly has an 'any' type.

Check failure on line 55 in tests/components/DefineExposeBundled.ts

View workflow job for this annotation

GitHub Actions / build (18)

Parameter '_cache' implicitly has an 'any' type.

Check failure on line 55 in tests/components/DefineExposeBundled.ts

View workflow job for this annotation

GitHub Actions / build (20)

Parameter '_ctx' implicitly has an 'any' type.

Check failure on line 55 in tests/components/DefineExposeBundled.ts

View workflow job for this annotation

GitHub Actions / build (20)

Parameter '_cache' implicitly has an 'any' type.

Check failure on line 55 in tests/components/DefineExposeBundled.ts

View workflow job for this annotation

GitHub Actions / build (22)

Parameter '_ctx' implicitly has an 'any' type.

Check failure on line 55 in tests/components/DefineExposeBundled.ts

View workflow job for this annotation

GitHub Actions / build (22)

Parameter '_cache' implicitly has an 'any' type.
return (
openBlock(),
createElementBlock(
Fragment,
null,
[
createElementVNode(
'button',
{ onClick: inc },
toDisplayString(count.value),
1
),
createElementVNode(
'div',
{ 'force-expose': exposedMethod1 },
toDisplayString(refNonExposed.value),
1
)
],
64
)
)
}
}
})
export default _sfc_main
5 changes: 3 additions & 2 deletions tests/components/DefineExposeWithRenderFunction.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default defineComponent({
expose({
/* ------ Common Test Case ------ */
exposeObjectLiteral: 'exposeObjectLiteral',
exposedState1,
exposedState2Alias: exposedState2,
exposedState2Getter,
Expand All @@ -46,7 +46,8 @@ export default defineComponent({
/* ------ Common Test Case ------ */
})
return () => [h('div', refUseByRenderFnButNotExposed.value)]
return () => [
h('div', refUseByRenderFnButNotExposed.value)]
}
})
</script>
21 changes: 21 additions & 0 deletions tests/components/FindComponentExposeRenderFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineComponent, h } from 'vue'

export default defineComponent({
name: 'FindComponentExposeRenderFunction',
props: {
someProp: String
},
setup(_, { expose }) {
const exposedFn = () => {
return 'exposedFnReturn'
}

expose({
exposedFn
})

return () => {
return h('div', 'Example')
}
}
})
17 changes: 17 additions & 0 deletions tests/components/FindComponentExposeScriptSetup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div>Example</div>
</template>

<script setup>
const props = defineProps({
someProp: String,
});
const exposedFn = () => {
return 'exposedFnReturn';
};
defineExpose({
exposedFn,
});
</script>
19 changes: 19 additions & 0 deletions tests/components/FindComponentExposeScriptSetupBundled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { openBlock, createElementBlock } from 'vue'
const _sfc_main = {
__name: 'FindComponentExposeScriptSetupBundled',
props: {
someProp: String
},
setup(__props, { expose: __expose }) {

Check failure on line 7 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (18)

Parameter '__props' implicitly has an 'any' type.

Check failure on line 7 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (18)

Binding element '__expose' implicitly has an 'any' type.

Check failure on line 7 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (20)

Parameter '__props' implicitly has an 'any' type.

Check failure on line 7 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (20)

Binding element '__expose' implicitly has an 'any' type.

Check failure on line 7 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (22)

Parameter '__props' implicitly has an 'any' type.

Check failure on line 7 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (22)

Binding element '__expose' implicitly has an 'any' type.
const exposedFn = () => {
return 'exposedFnReturn'
}
__expose({
exposedFn
})
return (_ctx, _cache) => {

Check failure on line 14 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (18)

Parameter '_ctx' implicitly has an 'any' type.

Check failure on line 14 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (18)

Parameter '_cache' implicitly has an 'any' type.

Check failure on line 14 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (20)

Parameter '_ctx' implicitly has an 'any' type.

Check failure on line 14 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (20)

Parameter '_cache' implicitly has an 'any' type.

Check failure on line 14 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (22)

Parameter '_ctx' implicitly has an 'any' type.

Check failure on line 14 in tests/components/FindComponentExposeScriptSetupBundled.ts

View workflow job for this annotation

GitHub Actions / build (22)

Parameter '_cache' implicitly has an 'any' type.
return openBlock(), createElementBlock('div', null, 'Example')
}
}
}
export default _sfc_main
28 changes: 28 additions & 0 deletions tests/components/FindComponentExposeTemplate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<div>Example</div>
</template>

<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'FindComponentExposeTemplate',
props: {
someProp: String,
},
setup(_, { expose }) {
const exposedFn = () => {
return 'exposedFnReturn';
};
expose({
exposedFn,
});
return {
oopsy: 1
};
}
})
</script>

4 changes: 4 additions & 0 deletions tests/components/ScriptSetup_Expose.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ defineExpose({
resetCount,
refNonExposedGetter,
})
defineOptions({
name: 'Hello',
})
</script>

<template>
Expand Down
Loading

0 comments on commit 851032b

Please sign in to comment.