ve3组合API单元测试无法更新modelValue



我是Vue的新手,目前正在为项目中使用的搜索组件编写单元测试。简单地说,当用户在输入字段中输入时,输入字段右侧会出现一个小X图标。单击X将把字段的值重置为空字符串。

组件正在使用组合API并按预期工作,我可以使用Vue开发工具观察发射和有效负载,但是我无法使用Vitest看到这些事件。大多数测试都失败了,我想知道我的逻辑哪里出错了。

对于这个问题,我用一些作用域样式重新创建了组件,以便在必要时易于安装。这里使用的是ve3 compp Api、TypeScript、Vite、Vitest和vue-test-utils。

组件:

<template>
<div class="searchBar">
<input
:value="modelValue"
class="searchInput"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
autocomplete="off"
data-test="searchInput"
/>
<button
v-if="modelValue"
@click="clear($event)"
class="clearIcon"
ariaLabel="Clear Search"
data-test="clearIcon"
>
<i class="fa fa-times"></i>
</button>
</div>
</template>
<script lang="ts">
import {
defineComponent,
watch,
} from "vue";
export default defineComponent({
name: "SOComponent",
props: {
modelValue: {
type: [String, Number],
},
},
emits: [
"update:modelValue",
"search",
"clear",
],
setup(props, { emit }) {
function clear(event: Event) {
emit("clear", event);
emit("update:modelValue", "");
}
watch(
() => props.modelValue,
(newValue, oldValue) => {
if (newValue !== oldValue) {
emit("search", newValue);
}
}
);
return {
clear,
};
},
});
</script>
<style scoped>
.searchBar {
display: flex;
justify-content: space-between;
align-items: center;
background-color: white;
border: 2px solid black;
border-radius: 1rem;
}
.searchInput {
border: none;
width: 100%;
outline: none;
color: black;
font-size: 1rem;
padding: 1rem;
background-color: transparent;
}
.clearIcon {
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
background-color: red;
border: none;
color: white;
border-radius: 1rem;
padding: 6.5px 9px;
font-size: 1rem;
}
.clearIcon:hover {
background-color: darkred;
}
</style>

下面是单元测试:

import { describe, it, expect, vi, afterEach } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import SOComponent from '../StackOverflowComponent.vue'
describe('SOComponent Component Tests', () => {
// Wrapper Factory
let wrapper: any
function createComponent() {
wrapper = shallowMount(SOComponent, {
attachTo: document.body
})
}
afterEach(() => {
wrapper.unmount()
})
// Helper Finder Functions
const searchInput = () => wrapper.find('[data-test="searchInput"]')
const clearIcon = () => wrapper.find('[data-test="clearIcon"]')
describe('component rendering', () => {
it('component renders as intended when created', () => {
createComponent()
expect(searchInput().exists()).toBe(true)
expect(clearIcon().exists()).toBe(false)
})
it('clear icon is displayed when input field has value', async () => {
createComponent()
await searchInput().setValue('render X')
expect(clearIcon().exists()).toBe(true)
})
it('clear icon is not displayed when input field has no value', async () => {
createComponent()
await searchInput().setValue('')
expect(clearIcon().exists()).toBe(false)
})
})
describe('component emits and methods', () => {
it('update:modelValue emits input value', async () => {
createComponent()
await searchInput().setValue('emit me')
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['emit me'])
})
it('clear icon click calls clear method', async () => {
createComponent()
await searchInput().setValue('call it')
const clearSpy = vi.spyOn(wrapper.vm, 'clear')
await clearIcon().trigger('click')
expect(clearSpy).toHaveBeenCalled()
})
it('clear icon click resets input field value', async () => {
createComponent()
await searchInput().setValue('clear me')
await clearIcon().trigger('click')
expect((searchInput().element as HTMLInputElement).value).toBe('')
})
it('search is emitted when input gains value', async () => {
createComponent()
await searchInput().setValue('emit me')
expect(wrapper.emitted('search')).toBeTruthy()
expect(wrapper.emitted('search')![0]).toEqual(['emit me'])
})
it('clear is emitted when clear icon is clicked', async () => {
createComponent()
await searchInput().setValue('emit me')
await clearIcon().trigger('click')
expect(wrapper.emitted('clear')).toBeTruthy()
})
it('update:modelValue is emitted when clear icon is clicked', async () => {
createComponent()
await searchInput().setValue('clear me')
await clearIcon().trigger('click')
expect(wrapper.emitted('update:modelValue')).toBeTruthy()
expect(wrapper.emitted('update:modelValue')![1]).toEqual([''])
})
})
})

在这一点上,我觉得我一定错过了一些基本的Vue3反应性,因为我无法测试附加到v-model的条件渲染。老实说,任何帮助,解决方案或建议将非常感激!

谢谢你:)

从我的理解来看,V-Model的双向绑定似乎不包括在vue-test-utils中。我发现的修复方法是在props中设置一个监视器来跟踪update:modelValue,这将更新modelValue prop。

function createComponent() {
wrapper = shallowMount(Component, {
attachTo: document.body,
props: {
'onUpdate:modelValue': async (modelValue: any) => await wrapper.setProps({ modelValue })
}
})
}

解决方案:https://github.com/vuejs/test-utils/discussions/279