diff --git a/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts b/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts index fa894ef3f..0a602a149 100644 --- a/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts +++ b/examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts @@ -191,8 +191,14 @@ describe('mountSuspended', () => { it('can access exposed methods/refs from components mounted within nuxt suspense', async () => { const component = await mountSuspended(WrapperTests) expect(component.vm.testExpose?.()).toBe('expose was successful') - // @ts-expect-error FIXME: someRef is typed as unwrapped - expect(component.vm.someRef.value).toBe('thing') + expect(component.vm.someRef).toBe('thing') + }) + + it('can modify exposed refs from components', async () => { + const component = await mountSuspended(WrapperTests) + expect(component.vm.someRef).toBe('thing') + component.vm.someRef = 'modified thing' + expect(component.vm.someRef).toBe('modified thing') }) it('respects directives registered in nuxt plugins', async () => { diff --git a/src/runtime-utils/mount.ts b/src/runtime-utils/mount.ts index d6d746ba0..e3c2453a7 100644 --- a/src/runtime-utils/mount.ts +++ b/src/runtime-utils/mount.ts @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils' import type { ComponentMountingOptions } from '@vue/test-utils' -import { Suspense, h, isReadonly, nextTick, reactive, unref, getCurrentInstance } from 'vue' +import { Suspense, h, isReadonly, nextTick, reactive, unref, getCurrentInstance, isRef } from 'vue' import type { ComponentInternalInstance, DefineComponent, SetupContext } from 'vue' import { defu } from 'defu' import type { RouteLocationRaw } from 'vue-router' @@ -259,6 +259,10 @@ function wrappedMountedWrapper(wrapper: ReturnType> & { setup const component = target.findComponent({ name: 'MountSuspendedComponent' }) return component[prop] } + else if (prop === 'vm') { + const vm = Reflect.get(target, prop, receiver) + return createVMProxy(vm as unknown as ComponentPublicInstance, wrapper.setupState) + } else { return Reflect.get(target, prop, receiver) } @@ -276,3 +280,30 @@ function wrappedMountedWrapper(wrapper: ReturnType> & { setup return proxy } + +function createVMProxy(vm: T, setupState: Record): T { + return new Proxy(vm, { + get(target, key, receiver) { + const value = Reflect.get(target, key, receiver) + + if (setupState && typeof setupState === 'object' && key in setupState) { + return unref(setupState[key as keyof typeof setupState]) + } + + return value + }, + set(target, key, value, receiver) { + if (setupState && typeof setupState === 'object' && key in setupState) { + const setupValue = setupState[key as keyof typeof setupState] + if (setupValue && isRef(setupValue)) { + setupValue.value = value + return true + } + + return Reflect.set(setupState, key, value, receiver) + } + + return Reflect.set(target, key, value, receiver) + }, + }) +}