Environment
- Operating System:
Linux
- Node Version:
v20.18.1
- Nuxt Version:
3.15.4
- CLI Version:
3.21.1
- Nitro Version:
2.10.4
- Package Manager:
bun@1.2.5
- Builder:
-
- User Config:
$production, compatibilityDate, telemetry, devtools, ssr, imports, runtimeConfig, vite, build, modules, css, app, auth, sourcemap
- Runtime Modules:
@nuxt/test-utils/module@3.17.2, ~/modules/vuetify.ts, ~/modules/DynamicCSSStyles.ts, nuxt-svgo@4.0.13, @sidebase/nuxt-auth@0.10.0, @nuxt/eslint@0.7.5, @sentry/nuxt/module@8.52.0
- Build Modules:
-
Reproduction
component
<template>
<v-menu v-model="menuShow">
<template #activator="{ props }">
<v-btn v-bind="props" />
</template>
<v-list v-model:selected="selected" selectable density="default" max-height="40vh" select-strategy="leaf">
<template v-for="item in items" :key="item.title">
<v-list-item>
{{ item.title }}
</v-list-item>
</template>
</v-list>
</v-menu>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
export default defineComponent({
name: 'AppTestComponent',
setup() {
const selected = ref([])
const menuShow = ref(false)
return {
menuShow,
selected,
}
},
computed: {
items() {
return [
{
title: 'My Title',
},
]
},
},
})
</script>
test
import { describe, expect, test } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import AppTestComponent from '~/components/AppTestComponent.vue'
import { mount } from '@vue/test-utils'
import { VList } from 'vuetify/components/VList'
describe('AppTestComponent', () => {
test('renders', async () => {
const component = mount(AppTestComponent)
await component.find('button').trigger('click')
const listContent = component.findComponent(VList).html()
expect(listContent).toContain('My Title')
})
test('will fail', async () => {
const component = await mountSuspended(AppTestComponent)
await component.find('button').trigger('click')
const listContent = component.findComponent(VList).html()
expect(listContent).toContain('My Title')
})
})
Describe the bug
When testing Vuetify components with mountSuspended from @nuxt/test-utils/runtime, I discovered that ref values are not properly unwrapped before being passed to components.
Specifically, when mounting Vuetify's VMenu or VList components, props like modelValue (for VMenu) or selected (for VList) receive the actual ref object instead of the ref's value. This behavior differs from Vue Test Utils' standard mount function, which correctly unwraps ref values.
Reproduction Steps
The issue occurs when:
- Using
mountSuspended from @nuxt/test-utils/runtime
- Testing components created with
defineComponent()
- Passing refs as props to Vuetify components from
setup()
Interestingly, components using <script setup> syntax work correctly.
Error Details
The error occurs in Vuetify's internal code when it tries to iterate over what it expects to be an array value but receives a ref object instead:
TypeError: (v || []) is not iterable
❯ Object.in node_modules/vuetify/src/composables/nested/selectStrategies.ts:56:25
❯ transformIn node_modules/vuetify/src/composables/nested/nested.ts:184:33
❯ ComputedRefImpl.get [as fn] node_modules/vuetify/src/composables/proxiedModel.ts:53:14
❯ refreshComputed node_modules/@vue/reactivity/dist/reactivity.cjs.js:384:28
❯ ComputedRefImpl.value node_modules/@vue/reactivity/dist/reactivity.cjs.js:1625:5
❯ ComputedRefImpl.fn node_modules/vuetify/src/composables/nested/nested.ts:356:53
❯ refreshComputed node_modules/@vue/reactivity/dist/reactivity.cjs.js:384:28
❯ ComputedRefImpl.value node_modules/@vue/reactivity/dist/reactivity.cjs.js:1625:5
❯ ComputedRefImpl.fn node_modules/vuetify/src/components/VList/VListItem.tsx:141:105
❯ refreshComputed node_modules/@vue/reactivity/dist/reactivity.cjs.js:384:28
Investigation Findings
When debugging the Vuetify components by placing breakpoints in their setup functions, I observed that:
- With
@vue/test-utils mount: Props contain the actual values of refs
- With
@nuxt/test-utils mountSuspended: Props contain the ref objects themselves
I suspect this is related to how mountSuspended handles the return value of setup() when using defineComponent(), failing to properly unwrap reactive references before passing them as props.
Additional context
No response
Logs
Environment
Linuxv20.18.13.15.43.21.12.10.4bun@1.2.5-$production,compatibilityDate,telemetry,devtools,ssr,imports,runtimeConfig,vite,build,modules,css,app,auth,sourcemap@nuxt/test-utils/module@3.17.2,~/modules/vuetify.ts,~/modules/DynamicCSSStyles.ts,nuxt-svgo@4.0.13,@sidebase/nuxt-auth@0.10.0,@nuxt/eslint@0.7.5,@sentry/nuxt/module@8.52.0-Reproduction
component
test
Describe the bug
When testing Vuetify components with
mountSuspendedfrom@nuxt/test-utils/runtime, I discovered that ref values are not properly unwrapped before being passed to components.Specifically, when mounting Vuetify's
VMenuorVListcomponents, props likemodelValue(for VMenu) orselected(for VList) receive the actual ref object instead of the ref's value. This behavior differs from Vue Test Utils' standardmountfunction, which correctly unwraps ref values.Reproduction Steps
The issue occurs when:
mountSuspendedfrom@nuxt/test-utils/runtimedefineComponent()setup()Interestingly, components using
<script setup>syntax work correctly.Error Details
The error occurs in Vuetify's internal code when it tries to iterate over what it expects to be an array value but receives a ref object instead:
Investigation Findings
When debugging the Vuetify components by placing breakpoints in their setup functions, I observed that:
@vue/test-utilsmount: Props contain the actual values of refs@nuxt/test-utilsmountSuspended: Props contain the ref objects themselvesI suspect this is related to how
mountSuspendedhandles the return value ofsetup()when usingdefineComponent(), failing to properly unwrap reactive references before passing them as props.Additional context
No response
Logs