-
Notifications
You must be signed in to change notification settings - Fork 112
Description
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
mountSuspendedfrom@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-utilsmount: Props contain the actual values of refs - With
@nuxt/test-utilsmountSuspended: 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