You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Vitest测试含q-select的Vue组件并触发下拉列表显示

问题描述

我有一个基于Quasar的Vue组件,实现了输入至少4个字符后触发自动补全的功能,实际使用时输入内容能正常触发下拉选项,但在Vitest单元测试中无法触发filterFn函数,下拉列表始终不显示。

组件代码

<template>
  <q-select
    clearable
    :disable='disable'
    emit-value
    :error='error'
    error-message='Invalid Equipment Number'
    fill-input
    hide-dropdown-icon
    hide-selected
    hint='Minimum 4 characters to trigger autocompletion'
    input-debounce='100'
    :label='label'
    :loading='loading'
    map-options
    :model-value='mValue'
    :options='options'
    outlined
    :readonly='readonly'
    use-input
    @clear='onClear'
    @filter='filterFn'
    @input-value='setValue'
  >

    <template v-slot:no-option>
      <q-item>
        <q-item-section class='text-grey'>No results</q-item-section>
      </q-item>
    </template>
  </q-select>
</template>

<script lang="ts">
import EquipmentApi from 'modules/Config/models/Equipment/EquipmentApi'
import { IEquipment } from 'modules/Config/models/Equipment/EquipmentInterface'
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'MyEquipment',
  props: {
    value: {
      required: false,
      type: Number
    },
    label: {
      required: false,
      default: 'Equipment',
      type: String
    },
    readonly: {
      type: Boolean,
      required: false
    },
    disable: {
      type: Boolean,
      required: false
    },
    error: {
      type: Boolean,
      default: false,
      required: false
    },
  },
  emits: {
    fill(equipment: IEquipment) {
      return equipment
    },
    'update:value'(val: number) {
      return val
    }
  },
  data() {
    return {
      mValue: null as number|null,
      options: [] as number[],
      loading: false
    }
  },
  computed: {
    isEquipment() {
      return (this.mValue)?.toString().length === 8
    }
  },
  watch: {
    value(val) {
      this.mValue = parseInt(val)
    }
  },
  methods: {
    filterFn(val: any, update: any, abort:any) {
      if (val.length < 4) {
        abort()
        return
      }
      update(() => {
        this.loading = true
        EquipmentApi.filter(val).then((response: { data: number[] }) => {
          this.options = response.data
          this.loading = false
        })
      })
    },
    setValue(val: string|number) {
      this.mValue = parseInt(val)
      this.$emit('update:value', this.mValue)
    },
    onClear() {
      this.mValue = null
    },
  },
})
</script>

单元测试代码

import { installQuasarPlugin } from '@quasar/quasar-app-extension-testing-unit-vitest'
import { mount } from '@vue/test-utils'
import EquipmentField from 'src/modules/Application/components/Form/EquipmentField.vue'
import { describe, expect, it } from 'vitest'

function sleep(milliseconds) {
  return new Promise((resolve) => {
    setTimeout(resolve, milliseconds)
  })
}

installQuasarPlugin()
let wrapper = null

describe('Equipment field unit test', async () => {
  beforeEach(() => {
    wrapper = mount(EquipmentField)
  })

  it('should have a list of equipment code after we fill the input, and the list\'s first row has text in it', async () => {
    wrapper.find('input').setValue('1234')

    sleep(2000)

    console.log(document.querySelectorAll('[role=option]').length) // length = 0. It means the dropdown is not created
  })
})
解决方案

1. 修复异步等待逻辑

你的sleep函数返回Promise,但测试中没有用await等待,导致代码还没等到防抖和接口请求完成就去查询DOM。修改测试:

it('should have a list of equipment code after we fill the input', async () => {
  wrapper.find('input').setValue('1234')
  
  await sleep(2000) // 加上await等待异步操作完成
  await wrapper.vm.$nextTick() // 等待DOM更新
  
  console.log(document.querySelectorAll('[role=option]').length)
})

更高效的方式是用Vitest的模拟计时器替代真实等待,避免拖慢测试:

it('should have a list of equipment code after we fill the input', async () => {
  vi.useFakeTimers()
  wrapper.find('input').setValue('1234')
  
  vi.runAllTimers() // 快进所有计时器,跳过防抖和接口延迟
  await wrapper.vm.$nextTick() // 等待DOM更新
  
  console.log(document.querySelectorAll('[role=option]').length)
  vi.useRealTimers() // 恢复真实计时器
})

2. 模拟接口请求

测试中需要模拟EquipmentApi.filter的返回值,避免真实接口调用的不确定性。在测试文件顶部添加:

import EquipmentApi from 'modules/Config/models/Equipment/EquipmentApi'
vi.mock('modules/Config/models/Equipment/EquipmentApi', () => ({
  default: {
    filter: vi.fn().mockResolvedValue({ data: [12345, 12346] })
  }
}))

这样filterFn会获取到模拟的选项数据,确保options被正确填充。

3. 手动触发filter事件

如果自动触发有问题,可以直接手动调用组件的filter事件:

it('should trigger filterFn and load options', async () => {
  await wrapper.findComponent({ name: 'QSelect' }).vm.$emit('filter', '1234')
  await wrapper.vm.$nextTick()
  
  expect(wrapper.vm.options).toHaveLength(2)
})

4. 确保输入框处于交互状态

Quasar的q-select在use-input模式下,需要输入框获得焦点才能触发输入逻辑:

it('should have a list of equipment code after we fill the input', async () => {
  const input = wrapper.find('input')
  await input.focus()
  await input.setValue('1234')
  
  vi.useFakeTimers()
  vi.runAllTimers()
  await wrapper.vm.$nextTick()
  
  console.log(document.querySelectorAll('[role=option]').length)
  vi.useRealTimers()
})

内容的提问来源于stack exchange,提问作者Dika

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 21:07:42