如何在 Vue.js 项目中模拟 Vitest / vue-test-utils 中的 <select> 元素

Sho*_*zer 2 vue-test-utils vuejs3 vitest

我正在尝试模拟<select>在 Vitest 测试中的元素中选择一个选项。<select>模拟选择应设置绑定到s的 ref 的值v-model。正如您在下面看到的,我根据此处和互联网上其他地方的其他问题尝试了许多不同的方法,但我就是无法使其在我的情况下发挥作用。

我正在使用以下内容:

  • 版本 3.2.37
  • 维特4.0.4
  • 维斯特0.26.3
  • vue-测试-utils 2.2.7

我创建了一个非常基本的 Vue 组件,其中包括标题和元素<select>

<script setup lang="ts">
import { ref } from "vue";

const items = ["item 1", "item 2", "item 3", "item 4", "item 5"];
const selectedItem = ref<string>("");

// this is a placeholder for a more complex function
async function handleSubmit(): Promise<void> {
  console.log("in handleSubmit()");
  console.log(quotedString(selectedItem.value));
}

// this is just here for a prettier output
function quotedString(s: string): string {
  return s === undefined ? s : '"' + s + '"';
}
</script>

<template>
  <h1>Foo</h1>
  <p>
    <select v-model="selectedItem">
      <option disabled value="">Please Select</option>
      <option v-for="item in items" :value="item" :key="item">
        {{ item }}
      </option>
    </select>
  </p>
  <p>Selected {{ selectedItem }}</p>
  <p><button @click="handleSubmit">Submit</button></p>
</template>
Run Code Online (Sandbox Code Playgroud)

<select>绑定的selectedItem,当我在浏览器中打开该代码时,一切正常,这意味着选择一个元素后,该元素的名称将显示在页面上,单击提交按钮后,该元素的名称将写入控制台。

但是,当我尝试以下测试代码时,该语句console.log(quotedString(selectedItem.value));会生成未定义或空字符串,这意味着模拟选择不起作用。这是测试代码,包括几种不同的尝试。

import { describe, test } from "vitest";
import { shallowMount, VueWrapper } from "@vue/test-utils";
import FooView from "./FooView.vue";

describe("FooView", (): void => {
  test("can simulate select selection", async (): Promise<void> => {
    const fooViewWrapper = shallowMount(FooView);

    const selectFieldWrapper = fooViewWrapper.find("select");
    const options = selectFieldWrapper.findAll("option");

    // ATTEMP 0
    // This is the preferred way according to https://v1.test-utils.vuejs.org/api/wrapper/setvalue.html but still does not work
    // await selectFieldWrapper.setValue("item 2");
    // expect(selectFieldWrapper.element.value).toBe("item 2");
    // alternatively
    // selectFieldWrapper.element.value = "item 2";
    // selectFieldWrapper.trigger('change')
    // expect(selectFieldWrapper.element.value).toBe("item 2");

    // ATTEMPT 1
    // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    // await options.at(2)!.trigger("click");
    // --> output: empty string

    // ATTEMPT 2
    // await options.at(2)?.setSelected(); // setSelected() is private :(
    // --> output: empty string

    // ATTEMPT 3
    // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    // options.at(2)!.element.selected = true;
    // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    // await options.at(2)!.trigger("input");
    // --> output: empty string

    // ATTEMPT 4
    // await selectFieldWrapper.setValue("item 2");
    // await selectFieldWrapper.trigger("change");
    // --> output: undefined

    // ATTEMPT 5
    // await selectFieldWrapper.setValue("item 2");
    // await selectFieldWrapper.trigger("input");
    // --> output: undefined

    // ATTEMPT 6
    // await selectFieldWrapper.setValue("item 2");
    // await selectFieldWrapper.trigger("click");
    // --> output: undefined

    // ATTEMPT 7
    // selectFieldWrapper.element.selectedIndex = 2;
    // await selectFieldWrapper.trigger("change");
    // --> output: undefined

    // ATTEMPT 8
    // selectFieldWrapper.element.selectedIndex = 2;
    // await selectFieldWrapper.trigger("input");
    // --> output: empty string

    // ATTEMPT 9
    // selectFieldWrapper.element.selectedIndex = 2;
    // await selectFieldWrapper.trigger("click");
    // --> output: empty string

    // ATTEMPT 10
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    // await options.at(2)!.trigger("click");
    // --> output: empty string

    // ATTEMPT 10
    // await selectFieldWrapper.trigger("click");
    // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    // await options.at(2)!.trigger("click");
    // await selectFieldWrapper.trigger("input");
    // --> output: empty string

    // ATTEMPT 11
    // selectFieldWrapper.element.click();
    // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    // options.at(2)!.element.click();
    // await nextTick();
    // --> output: empty string

    // ATTEMPT 12
    // // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    // options.at(1)!.trigger('click');
    // await nextTick();
    // --> output: empty string

    // ATTEMPT 13
    // await selectFieldWrapper.setValue(2);
    // await selectFieldWrapper.trigger("click");
    // --> output: undefined

    // ATTEMPT 14
    // await selectFieldWrapper.setValue(2);
    // await selectFieldWrapper.trigger("change");
    // --> output: undefined

    // selectFieldWrapper.element.selectedIndex = 2;
    // await selectFieldWrapper.trigger("input");
    // console.log(options.at(2)?.element.selected);
    // console.log(selectFieldWrapper.element.selectedIndex);

    await clickSubmitButton(fooViewWrapper);
  });
});

async function clickSubmitButton(fooViewWrapper: VueWrapper): Promise<void> {
  const submitButtonWrapper = fooViewWrapper.find("button");
  submitButtonWrapper.element.click();
}
Run Code Online (Sandbox Code Playgroud)

我发现有趣的是我可以像这样设置所选选项:

options.at(2)!.element.selected = true;
console.log(options.at(2)?.element.selected); // prints *true*
Run Code Online (Sandbox Code Playgroud)

但即使这样我也没有得到想要的输出。

Sho*_*zer 5

回答我自己的问题:问题不在于测试本身,而在于 vitest 配置。我使用 happy-dom 作为它的环境,这显然在这种特殊情况下不起作用。将环境切换到jsdom就是解决方案。这样我的尝试 0 就成功了,所以这是有效的:

await selectFieldWrapper.setValue("item 2");
expect(selectFieldWrapper.element.value).toBe("item 2");
Run Code Online (Sandbox Code Playgroud)