From 6fbdfa18b1253ae212c65a7aa6687945157fe55c Mon Sep 17 00:00:00 2001 From: Andrea Basile <57828270+Evobaso-J@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:59:50 +0100 Subject: [PATCH] fix: allow access to nested computed values (fix #2196) (#2356) * fix: allow access to nested computed values * fix: remove useless access to vm --- src/createInstance.ts | 4 +-- src/types.ts | 12 ++++++- src/utils.ts | 17 ++++++++-- tests/components/WithDeepRef.vue | 55 ++++++++++++++++++++++++++++++++ tests/mount.spec.ts | 28 +++++++++++++++- 5 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 tests/components/WithDeepRef.vue diff --git a/src/createInstance.ts b/src/createInstance.ts index 7dbb4f4ee..34f307783 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 46677534b..8202d8d3d 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 f1119b4b8..97b417e84 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 000000000..d7a461777 --- /dev/null +++ b/tests/components/WithDeepRef.vue @@ -0,0 +1,55 @@ + + + diff --git a/tests/mount.spec.ts b/tests/mount.spec.ts index fa0916322..ad0b03fc2 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,29 @@ 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.get('#oneLayerCountObjectValue').text()).toBe('2') + expect(wrapper.get('#twoLayersCountObjectValue').text()).toBe('3') + expect(wrapper.get('#countArrayValue').text()).toBe('4') + expect(wrapper.get('#countMatrixValue').text()).toBe('5') + expect(wrapper.get('#oneLayerCountObjectArrayValue').text()).toBe('6') + expect(wrapper.get('#oneLayerCountArrayObjectValue').text()).toBe('7') + expect(wrapper.get('#oneLayerCountObjectMatrixValue').text()).toBe('8') + }) })