diff --git a/src/createInstance.ts b/src/createInstance.ts index 7dbb4f4eec..34f3077837 100644 --- a/src/createInstance.ts +++ b/src/createInstance.ts @@ -4,7 +4,6 @@ import { defineComponent, reactive, shallowReactive, - isRef, ref, AppConfig, ComponentOptions, @@ -17,6 +16,7 @@ import { MountingOptions, Slot } from './types' import { getComponentsFromStubs, getDirectivesFromStubs, + isDeepRef, isFunctionalComponent, isObject, isObjectComponent, @@ -174,7 +174,7 @@ export function createInstance( ...options?.props, ref: MOUNT_COMPONENT_REF }).forEach(([k, v]) => { - if (isRef(v)) { + if (isDeepRef(v)) { refs[k] = v } else { props[k] = v diff --git a/src/types.ts b/src/types.ts index 46677534b9..8202d8d3d3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,8 @@ import { VNode, VNodeProps, FunctionalComponent, - ComponentInternalInstance + ComponentInternalInstance, + Ref } from 'vue' export interface RefSelector { @@ -167,3 +168,12 @@ export type VueNode = T & { export type VueElement = VueNode export type DefinedComponent = new (...args: any[]) => any + +/** + * T is a DeepRef if: + * - It's a Ref itself + * - It's an array containing a ref at any level + * - It's an object containing a ref at any level + */ +export type DeepRef = + T extends Ref ? T : T extends object ? DeepRef : Ref diff --git a/src/utils.ts b/src/utils.ts index f1119b4b89..97b417e848 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,11 +1,12 @@ -import { GlobalMountOptions, RefSelector, Stub, Stubs } from './types' +import { DeepRef, GlobalMountOptions, RefSelector, Stub, Stubs } from './types' import { Component, ComponentOptions, ComponentPublicInstance, ConcreteComponent, Directive, - FunctionalComponent + FunctionalComponent, + isRef } from 'vue' import { config } from './config' @@ -252,3 +253,15 @@ export const getGlobalThis = (): any => { : {}) ) } + +/** + * Checks if the given value is a DeepRef. + * + * For both arrays and objects, it will recursively check + * if any of their values is a Ref. + * + * @param {DeepRef | unknown} r - The value to check. + * @returns {boolean} Returns true if the value is a DeepRef, false otherwise. + */ +export const isDeepRef = (r: DeepRef | unknown): r is DeepRef => + isRef(r) || (isObject(r) && Object.values(r).some(isDeepRef)) diff --git a/tests/components/WithDeepRef.vue b/tests/components/WithDeepRef.vue new file mode 100644 index 0000000000..d7a4617778 --- /dev/null +++ b/tests/components/WithDeepRef.vue @@ -0,0 +1,55 @@ + + + diff --git a/tests/mount.spec.ts b/tests/mount.spec.ts index fa09163229..d89c3d62f4 100644 --- a/tests/mount.spec.ts +++ b/tests/mount.spec.ts @@ -1,7 +1,8 @@ import { describe, expect, it, vi } from 'vitest' -import { defineComponent } from 'vue' +import { defineComponent, ref } from 'vue' import { mount } from '../src' import DefinePropsAndDefineEmits from './components/DefinePropsAndDefineEmits.vue' +import WithDeepRef from './components/WithDeepRef.vue' import HelloFromVitestPlayground from './components/HelloFromVitestPlayground.vue' describe('mount: general tests', () => { @@ -44,4 +45,37 @@ describe('mount: general tests', () => { expect(wrapper.get('div').text()).toContain('Hello') expect(wrapper.get('div').classes()).toContain('end') }) + it('allows access to nested computed values', async () => { + const wrapper = mount(WithDeepRef, { + props: { + count: ref(1), + oneLayerCountObject: { count: ref(2) }, + twoLayersCountObject: { oneLayerCountObject: { count: ref(3) } }, + + countArray: [ref(4)], + countMatrix: [[ref(5)]], + + oneLayerCountObjectArray: [{ count: ref(6) }], + oneLayerCountArrayObject: { count: [ref(7)] }, + oneLayerCountObjectMatrix: [[{ count: ref(8) }]] + } + }) + + expect(wrapper.get('#countValue').text()).toBe('1') + expect(wrapper.vm.countValue).toBe(1) + expect(wrapper.get('#oneLayerCountObjectValue').text()).toBe('2') + expect(wrapper.vm.oneLayerCountObjectValue).toBe(2) + expect(wrapper.get('#twoLayersCountObjectValue').text()).toBe('3') + expect(wrapper.vm.twoLayersCountObjectValue).toBe(3) + expect(wrapper.get('#countArrayValue').text()).toBe('4') + expect(wrapper.vm.countArrayValue).toBe(4) + expect(wrapper.get('#countMatrixValue').text()).toBe('5') + expect(wrapper.vm.countMatrixValue).toBe(5) + expect(wrapper.get('#oneLayerCountObjectArrayValue').text()).toBe('6') + expect(wrapper.vm.oneLayerCountObjectArrayValue).toBe(6) + expect(wrapper.get('#oneLayerCountArrayObjectValue').text()).toBe('7') + expect(wrapper.vm.oneLayerCountArrayObjectValue).toBe(7) + expect(wrapper.get('#oneLayerCountObjectMatrixValue').text()).toBe('8') + expect(wrapper.vm.oneLayerCountObjectMatrixValue).toBe(8) + }) })