From b4b2fcd0b46507ff259c6d408065c8a0817332c9 Mon Sep 17 00:00:00 2001 From: felissi Date: Wed, 19 Feb 2025 12:10:33 +0800 Subject: [PATCH] fix(useForwardExpose): fix #1631 --- .../src/shared/useForwardExpose.test.ts | 165 ++++++++++++++++++ .../radix-vue/src/shared/useForwardExpose.ts | 4 +- 2 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 packages/radix-vue/src/shared/useForwardExpose.test.ts diff --git a/packages/radix-vue/src/shared/useForwardExpose.test.ts b/packages/radix-vue/src/shared/useForwardExpose.test.ts new file mode 100644 index 000000000..423cb7986 --- /dev/null +++ b/packages/radix-vue/src/shared/useForwardExpose.test.ts @@ -0,0 +1,165 @@ +import { flushPromises, mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' +import { Fragment, computed, defineComponent, h, ref } from 'vue' + +import { useForwardExpose } from './useForwardExpose' + +describe('useForwardRef', async () => { + it('should forward plain DOM element ref', async () => { + const comp = defineComponent({ + setup() { + const { forwardRef } = useForwardExpose() + return { forwardRef } + }, + template: ` +
+ inner element +
+ `, + }) + const wrapper = mount( + defineComponent(() => { + const el = ref() + const forwardedRef = computed(() => el.value?.$el) + return () => + h('div', { test: forwardedRef.value }, [h(comp, { ref: el })]) + }), + ) + await flushPromises() + expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]') + }) + it('should forward plain DOM element ref - 2', async () => { + const comp = defineComponent({ + setup() { + const { forwardRef } = useForwardExpose() + return { forwardRef } + }, + template: ` +
+ inner element +
+ `, + }) + const wrapper = mount( + defineComponent(() => { + const el = ref() + const forwardedRef = computed(() => el.value?.$el) + return () => + h('div', { test: forwardedRef.value }, [h(comp, { ref: el })]) + }), + ) + await flushPromises() + expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]') + }) + it('should forward plain DOM element ref - fragment', async () => { + const comp = defineComponent({ + setup() { + const { forwardRef } = useForwardExpose() + return { forwardRef } + }, + template: ` +
multiple node root
+
+ inner element +
+ `, + }) + const wrapper = mount( + defineComponent(() => { + const el = ref() + const forwardedRef = computed(() => el.value?.$el) + return () => + h('div', { test: forwardedRef.value }, [h(comp, { ref: el })]) + }), + ) + await flushPromises() + expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]') + }) + it('should forward plain DOM element ref - fragment - 2', async () => { + const Frag = defineComponent( + (props, { slots }) => + () => + h(Fragment, {}, [slots.default?.()]), + ) + const comp = defineComponent({ + components: { Frag }, + setup() { + const { forwardRef } = useForwardExpose() + return { forwardRef } + }, + template: ` + + +
+ inner element +
+
+
+ `, + }) + const wrapper = mount( + defineComponent(() => { + const el = ref() + const forwardedRef = computed(() => el.value?.$el) + return () => + h('div', { test: forwardedRef.value }, [h(comp, { ref: el })]) + }), + ) + await flushPromises() + expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]') + }) + it('should forward plain DOM element ref - fragment - 3', async () => { + const comp = defineComponent({ + setup() { + const { forwardRef } = useForwardExpose() + return { forwardRef } + }, + template: ` +
+ +
+ `, + }) + const wrapper = mount( + defineComponent(() => { + const el = ref() + const forwardedRef = computed(() => el.value?.$el) + return () => + h('div', { test: forwardedRef.value }, [h(comp, { ref: el })]) + }), + ) + await flushPromises() + expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]') + }) + it('should forward component instance for component', async () => { + const InnerComp = defineComponent(() => { + return () => h('span', {}, 'inner component') + }) + const comp = defineComponent({ + setup() { + const { forwardRef } = useForwardExpose() + return { forwardRef } + }, + components: { InnerComp }, + template: ` +
+ +
+ `, + }) + const wrapper = mount( + defineComponent(() => { + const el = ref() + const forwardedRef = computed(() => el.value?.$el) + return () => + h('div', { test: forwardedRef.value }, [h(comp, { ref: el })]) + }), + ) + await flushPromises() + expect(wrapper.attributes('test')).toBe('[object HTMLSpanElement]') + }) +}) diff --git a/packages/radix-vue/src/shared/useForwardExpose.ts b/packages/radix-vue/src/shared/useForwardExpose.ts index 44b14f563..e6cd48884 100644 --- a/packages/radix-vue/src/shared/useForwardExpose.ts +++ b/packages/radix-vue/src/shared/useForwardExpose.ts @@ -48,14 +48,14 @@ export function useForwardExpose() { function forwardRef(ref: Element | ComponentPublicInstance | null) { currentRef.value = ref - if (ref instanceof Element || !ref) + if (!ref) return // retrieve the forwarded element Object.defineProperty(ret, '$el', { enumerable: true, configurable: true, - get: () => ref.$el, + get: () => (ref instanceof Element ? ref : ref.$el), }) instance.exposed = ret