import { describe, expect, it } from 'vitest'; import type { UseForwardExposeReturn } from '.'; import { defineComponent, nextTick, ref } from 'vue'; import { mount } from '@vue/test-utils'; import { useForwardExpose } from '.'; describe(useForwardExpose, () => { it('returns forwardRef, currentRef, and currentElement', () => { let result!: UseForwardExposeReturn; const Component = defineComponent({ setup() { result = useForwardExpose(); return {}; }, template: `
test
`, }); mount(Component); expect(result.forwardRef).toBeTypeOf('function'); expect(result.currentRef).toBeDefined(); expect(result.currentElement).toBeDefined(); }); it('exposes parent props on instance.exposed', () => { const Component = defineComponent({ props: { label: { type: String, default: 'hello' }, }, setup() { useForwardExpose(); return {}; }, template: `
{{ label }}
`, }); const wrapper = mount(Component, { props: { label: 'world' } }); expect(wrapper.vm.$.exposed).toBeDefined(); expect(wrapper.vm.$.exposed!.label).toBe('world'); }); it('exposes $el on instance.exposed', () => { const Component = defineComponent({ setup() { useForwardExpose(); return {}; }, template: `
content
`, }); const wrapper = mount(Component); expect(wrapper.vm.$.exposed).toBeDefined(); expect(wrapper.vm.$.exposed!.$el).toBeInstanceOf(HTMLDivElement); expect(wrapper.vm.$.exposed!.$el.classList.contains('root')).toBeTruthy(); }); it('forwardRef with a DOM element updates $el', async () => { let result!: UseForwardExposeReturn; const Component = defineComponent({ setup() { result = useForwardExpose(); return {}; }, template: `
inner
`, }); const wrapper = mount(Component); const innerSpan = wrapper.find('.inner').element; result.forwardRef(innerSpan as Element); await nextTick(); expect(result.currentRef.value).toBe(innerSpan); expect(wrapper.vm.$.exposed!.$el).toBe(innerSpan); }); it('forwardRef with a child component instance copies child exposed', async () => { const Child = defineComponent({ setup(_, { expose }) { const childValue = ref('from-child'); expose({ childValue }); return { childValue }; }, template: `child`, }); let result!: UseForwardExposeReturn; const Parent = defineComponent({ components: { Child }, setup() { result = useForwardExpose(); return { forwardRef: result.forwardRef }; }, template: ``, }); const wrapper = mount(Parent); await nextTick(); // The parent's exposed should contain the child's exposed ref expect(wrapper.vm.$.exposed).toBeDefined(); expect(wrapper.vm.$.exposed!.childValue).toEqual(ref('from-child')); }); it('forwardRef with null clears currentRef without error', async () => { let result!: UseForwardExposeReturn; const Component = defineComponent({ setup() { result = useForwardExpose(); return {}; }, template: `
test
`, }); mount(Component); expect(() => result.forwardRef(null)).not.toThrow(); expect(result.currentRef.value).toBeNull(); }); it('merges prior expose bindings', () => { const Component = defineComponent({ setup(_, { expose }) { const custom = ref(42); expose({ custom }); useForwardExpose(); return {}; }, template: `
test
`, }); const wrapper = mount(Component); expect(wrapper.vm.$.exposed).toBeDefined(); expect(wrapper.vm.$.exposed!.custom).toEqual(ref(42)); expect(wrapper.vm.$.exposed!.$el).toBeDefined(); }); it('currentElement resolves to HTMLElement for element ref', async () => { let result!: UseForwardExposeReturn; const Component = defineComponent({ setup() { result = useForwardExpose(); return {}; }, template: `
content
`, }); const wrapper = mount(Component); const el = wrapper.find('.resolved').element; result.forwardRef(el as Element); await nextTick(); expect(result.currentElement.value).toBe(el); }); it('switching child components updates exposed correctly', async () => { const ChildA = defineComponent({ setup(_, { expose }) { const a = ref('value-a'); expose({ a }); return { a }; }, template: `A`, }); const ChildB = defineComponent({ setup(_, { expose }) { const b = ref('value-b'); expose({ b }); return { b }; }, template: `B`, }); let result!: UseForwardExposeReturn; const Parent = defineComponent({ components: { ChildA, ChildB }, setup() { const showA = ref(true); result = useForwardExpose(); return { showA, forwardRef: result.forwardRef }; }, template: ` `, }); const wrapper = mount(Parent); await nextTick(); // Initially ChildA is rendered expect(wrapper.vm.$.exposed!.a).toEqual(ref('value-a')); // Switch to ChildB wrapper.vm.showA = false; await nextTick(); // ChildB's exposed should be available expect(wrapper.vm.$.exposed!.b).toEqual(ref('value-b')); }); it('$el remains correct after switching from component to element', async () => { const Child = defineComponent({ setup(_, { expose }) { expose({ test: ref(1) }); return {}; }, template: `child`, }); let result!: UseForwardExposeReturn; const Parent = defineComponent({ components: { Child }, setup() { result = useForwardExpose(); return { forwardRef: result.forwardRef }; }, template: ``, }); const wrapper = mount(Parent); await nextTick(); expect(wrapper.vm.$.exposed!.test).toEqual(ref(1)); // Now forward to a plain DOM element — $el must update on instance.exposed const div = document.createElement('div'); result.forwardRef(div); await nextTick(); expect(wrapper.vm.$.exposed!.$el).toBe(div); }); it('parent props remain accessible after child forwarding', async () => { const Child = defineComponent({ setup(_, { expose }) { expose({ childProp: ref('child') }); return {}; }, template: `child`, }); let result!: UseForwardExposeReturn; const Parent = defineComponent({ components: { Child }, props: { parentLabel: { type: String, default: 'parent' }, }, setup() { result = useForwardExpose(); return { forwardRef: result.forwardRef }; }, template: ``, }); const wrapper = mount(Parent, { props: { parentLabel: 'test' } }); await nextTick(); // Both parent props and child exposed should be accessible expect(wrapper.vm.$.exposed!.parentLabel).toBe('test'); expect(wrapper.vm.$.exposed!.childProp).toEqual(ref('child')); }); });