diff --git a/docs/.vitepress/config/index.mts b/docs/.vitepress/config/index.mts index 6965cc2eb..e3fe97898 100644 --- a/docs/.vitepress/config/index.mts +++ b/docs/.vitepress/config/index.mts @@ -1,5 +1,6 @@ import { enConfig } from './en.mts' import { frConfig } from './fr.mts' +import { zhConfig } from './zh.mts' import { sharedConfig } from './shared.mts' import { defineConfig } from 'vitepress' @@ -8,6 +9,7 @@ export default defineConfig({ locales: { root: { label: 'English', lang: 'en-US', link: '/', ...enConfig }, - fr: { label: 'Français', lang: 'fr-FR', link: '/fr/', ...frConfig }, + fr: { label: 'Français', lang: 'fr-FR', link: '/fr/', ...frConfig }, + zh: { label: '简体中文 (校对中)', lang: 'zh-CN', link: '/zh/', ...zhConfig } } }) diff --git a/docs/.vitepress/config/zh.mts b/docs/.vitepress/config/zh.mts new file mode 100644 index 000000000..3e3b0d194 --- /dev/null +++ b/docs/.vitepress/config/zh.mts @@ -0,0 +1,154 @@ +import type { DefaultTheme, LocaleSpecificConfig } from 'vitepress' + +export const META_URL = '' +export const META_TITLE = 'Vue Test Utils' +export const META_DESCRIPTION = 'Vue.js 3 官方测试工具集' + +export const zhConfig: LocaleSpecificConfig = { + description: META_DESCRIPTION, + head: [ + ['meta', { property: 'og:url', content: META_URL }], + ['meta', { property: 'og:title', content: META_TITLE }], + ['meta', { property: 'og:description', content: META_DESCRIPTION }], + ['meta', { property: 'twitter:url', content: META_URL }], + ['meta', { property: 'twitter:title', content: META_TITLE }], + ['meta', { property: 'twitter:description', content: META_DESCRIPTION }] + ], + + themeConfig: { + editLink: { + pattern: 'https://github.com/vuejs/test-utils/edit/main/docs/:path', + text: '改进此页面的内容' + }, + + nav: [ + { text: '指南', link: '/zh/guide/' }, + { text: 'API 参考', link: '/zh/api/' }, + { text: '从 Vue 2 迁移', link: '/zh/migration/' }, + { + text: '更新日志', + link: 'https://github.com/vuejs/test-utils/releases' + } + ], + + sidebar: { + '/zh': [ + { + text: '安装', + link: '/zh/installation/' + }, + { + text: '基础知识', + items: [ + { + text: '开始', + link: '/zh/guide/' + }, + { + text: '快速上手', + link: '/zh/guide/essentials/a-crash-course' + }, + { + text: '条件渲染', + link: '/zh/guide/essentials/conditional-rendering' + }, + { + text: '测试事件触发', + link: '/zh/guide/essentials/event-handling' + }, + { + text: '测试表单', + link: '/zh/guide/essentials/forms' + }, + { + text: '传递数据到组件', + link: '/zh/guide/essentials/passing-data' + }, + { + text: '编写易于测试的组件', + link: '/zh/guide/essentials/easy-to-test' + } + ] + }, + { + text: '深入学习 Vue Test Utils', + items: [ + { + text: '插槽', + link: '/zh/guide/advanced/slots' + }, + { + text: '异步行为', + link: '/zh/guide/advanced/async-suspense' + }, + { + text: '发起 HTTP 请求', + link: '/zh/guide/advanced/http-requests' + }, + { + text: '过渡效果', + link: '/zh/guide/advanced/transitions' + }, + { + text: '组件实例', + link: '/zh/guide/advanced/component-instance' + }, + { + text: '复用与组合', + link: '/zh/guide/advanced/reusability-composition' + }, + { + text: '测试 v-model', + link: '/zh/guide/advanced/v-model' + }, + { + text: '测试 Vuex', + link: '/zh/guide/advanced/vuex' + }, + { + text: '测试 Vue Router', + link: '/zh/guide/advanced/vue-router' + }, + { + text: '测试 Teleport', + link: '/zh/guide/advanced/teleport' + }, + { + text: 'Stubs 和浅挂载', + link: '/zh/guide/advanced/stubs-shallow-mount' + }, + { + text: '测试服务端渲染', + link: '/zh/guide/advanced/ssr' + } + ] + }, + { + text: '扩展 Vue Test Utils', + items: [ + { + text: '插件', + link: '/zh/guide/extending-vtu/plugins' + }, + { + text: '社区与学习资源', + link: '/zh/guide/extending-vtu/community-learning' + } + ] + }, + { + text: '常见问题', + link: '/zh/guide/faq/' + }, + { + text: '从 Vue 2 迁移', + link: '/zh/migration/' + }, + { + text: 'API 参考', + link: '/zh/api/' + } + ] + } + } +} diff --git a/docs/.vitepress/theme/index.mts b/docs/.vitepress/theme/index.mts index a7e01c7bb..30162d817 100644 --- a/docs/.vitepress/theme/index.mts +++ b/docs/.vitepress/theme/index.mts @@ -5,6 +5,7 @@ import status from '../translation-status.json' import './custom.css' const i18nLabels = { fr: 'La traduction est synchronisée avec les docs du ${date} dont le hash du commit est ${hash}.', + zh: '该翻译已同步到了 ${date} 的版本,其对应的 commit hash 是 ${hash}
同时该文档仍处于校对中,如有任何疑问或想参与校对工作,请移步这里了解更多。' } diff --git a/docs/.vitepress/translation-status.json b/docs/.vitepress/translation-status.json index 5a191a06c..b7a6d4c97 100644 --- a/docs/.vitepress/translation-status.json +++ b/docs/.vitepress/translation-status.json @@ -2,5 +2,9 @@ "fr": { "hash": "644917a", "date": "2024-05-28" + }, + "zh": { + "hash": "7c55128", + "date": "2024-11-28" } } \ No newline at end of file diff --git a/docs/zh/api/index.md b/docs/zh/api/index.md new file mode 100644 index 000000000..93337218a --- /dev/null +++ b/docs/zh/api/index.md @@ -0,0 +1,2037 @@ +--- +sidebar: auto +--- + +# API 参考 + +## mount + +创建一个包含已挂载和渲染的 Vue 组件的 Wrapper 以进行测试。 +请注意,当使用 Vitest 模拟日期/计时器时,必须在 `vi.setSystemTime` 之后调用此方法。 + +**签名:** + +```ts +interface MountingOptions { + attachTo?: Element | string + attrs?: Record + data?: () => {} extends Data ? any : Data extends object ? Partial : any + props?: (RawProps & Props) | ({} extends Props ? null : never) + slots?: { [key: string]: Slot } & { default?: Slot } + global?: GlobalMountOptions + shallow?: boolean +} + +function mount(Component, options?: MountingOptions): VueWrapper +``` + +**详细信息:** + +`mount` 是 Vue Test Utils 提供的主要方法。它创建一个 Vue 3 应用程序,该应用程序持有并渲染正在测试的组件。作为返回,它创建一个 Wrapper 以对组件进行操作和断言。 + +```js +import { mount } from '@vue/test-utils' + +const Component = { + template: '
Hello world
' +} + +test('mounts a component', () => { + const wrapper = mount(Component, {}) + + expect(wrapper.html()).toContain('Hello world') +}) +``` + +注意 `mount` 接受第二个参数以定义组件的状态配置。 + +**示例:使用组件属性和 Vue 应用插件进行挂载** + +```js +const wrapper = mount(Component, { + props: { + msg: 'world' + }, + global: { + plugins: [vuex] + } +}) +``` + +#### options.global + +组件状态中,你可以通过 [`MountingOptions.global` 配置属性](#global)配置上述 Vue 3 应用程序。这对于提供组件期望可用的模拟值非常有用。 + +::: tip +如果你发现自己需要为许多测试设置共同的应用配置,则可以使用导出的 [`config` 对象](#config)为整个测试套件设置配置。 +::: + +### attachTo + +指定要挂载组件的节点。当使用 `renderToString` 时,此选项不可用。 + +**签名:** + +```ts +attachTo?: Element | string +``` + +**详细信息:** + +可以是有效的 CSS 选择器,或者是连接到文档的 [`Element`](https://developer.mozilla.org/en-US/docs/Web/API/Element)。 + +注意,组件是附加到节点上的,并不会替换节点的整个内容。如果在多个测试中将组件挂载到同一个节点上,请确保在每个测试后调用 `wrapper.unmount()` 以卸载它,这将从节点中移除渲染的元素。 + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +document.body.innerHTML = ` +
+

Non Vue app

+
+
+` + +test('mounts on a specific element', () => { + const wrapper = mount(Component, { + attachTo: document.getElementById('app') + }) + + expect(document.body.innerHTML).toBe(` +
+

Non Vue app

+

Vue Component

+
+`) +}) +``` + +### attrs + +为组件设置 HTML 属性。 + +**签名:** + +```ts +attrs?: Record +``` + +**详细信息:** + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('attrs', () => { + const wrapper = mount(Component, { + attrs: { + id: 'hello', + disabled: true + } + }) + + expect(wrapper.attributes()).toEqual({ + disabled: 'true', + id: 'hello' + }) +}) +``` + +请注意,已定义的属性会覆盖 HTML 属性的设置: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('attribute is overridden by a prop with the same name', () => { + const wrapper = mount(Component, { + props: { + message: 'Hello World' + }, + attrs: { + message: 'this will get overridden' + } + }) + + expect(wrapper.props()).toEqual({ message: 'Hello World' }) + expect(wrapper.attributes()).toEqual({}) +}) +``` + +### data + +覆盖组件的默认 `data`。必须是一个函数。 + +**签名:** + +```ts +data?: () => {} extends Data ? any : Data extends object ? Partial : any +``` + +**详细信息:** + +`Component.vue` + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('data', () => { + const wrapper = mount(Component, { + data() { + return { + message: 'world' + } + } + }) + + expect(wrapper.html()).toContain('Hello world') +}) +``` + +### props + +在组件挂载时设置 props。 + +**签名:** + +```ts +props?: (RawProps & Props) | ({} extends Props ? null : never) +``` + +**详细信息:** + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('props', () => { + const wrapper = mount(Component, { + props: { + count: 5 + } + }) + + expect(wrapper.html()).toContain('Count: 5') +}) +``` + +### slots + +为组件的插槽设置值。 + +**签名:** + +```ts +type Slot = VNode | string | { render: Function } | Function | Component + +slots?: { [key: string]: Slot } & { default?: Slot } +``` + +**详细信息:** + +插槽可以是一个字符串或任何有效的组件定义,既可以从 `.vue` 文件中导入,也可以内联提供。 + +`Component.vue`: + +```vue + +``` + +`Bar.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { h } from 'vue' +import { mount } from '@vue/test-utils' +import Component from './Component.vue' +import Bar from './Bar.vue' + +test('renders slots content', () => { + const wrapper = mount(Component, { + slots: { + default: 'Default', + first: h('h1', {}, 'Named Slot'), + second: Bar + } + }) + + expect(wrapper.html()).toBe('

Named Slot

Default
Bar
') +}) +``` + +### global + +**签名:** + +```ts +type GlobalMountOptions = { + plugins?: (Plugin | [Plugin, ...any[]])[] + config?: Partial> + mixins?: ComponentOptions[] + mocks?: Record + provide?: Record + components?: Record + directives?: Record + stubs?: Stubs = Record | Array + renderStubDefaultSlot?: boolean +} +``` + +你可以在每个测试基础上以及整个测试套件中配置所有 `global` 选项。[请参见此处以了解如何配置项目范围的默认值](#config-global)。 + +#### global.components + +将组件全局注册到挂载的组件中。 + +**签名:** + +```ts +components?: Record +``` + +**详细信息:** + +`Component.vue`: + +```vue + + + +``` + +`GlobalComponent.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import GlobalComponent from '@/components/GlobalComponent' +import Component from './Component.vue' + +test('global.components', () => { + const wrapper = mount(Component, { + global: { + components: { + GlobalComponent + } + } + }) + + expect(wrapper.find('.global-component').exists()).toBe(true) +}) +``` + +#### global.config + +配置 [Vue 的应用程序全局配置](https://v3.vuejs.org/api/application-config.html#application-config)。 + +**签名:** + +```ts +config?: Partial> +``` + +#### global.directives + +将[指令](https://v3.vuejs.org/api/directives.html#directives)全局注册到挂载的组件中。 + +**签名:** + +```ts +directives?: Record +``` + +**详细信息:** + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' + +import Directive from '@/directives/Directive' + +const Component = { + template: '
Foo
' +} + +test('global.directives', () => { + const wrapper = mount(Component, { + global: { + directives: { + Bar: Directive // Bar matches v-bar + } + } + }) +}) +``` + +#### global.mixins + +将[混入](https://v3.vuejs.org/guide/mixins.html)全局注册到挂载的组件中。 + +**签名:** + +```ts +mixins?: ComponentOptions[] +``` + +**详细信息:** + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('global.mixins', () => { + const wrapper = mount(Component, { + global: { + mixins: [mixin] + } + }) +}) +``` + +#### global.mocks + +模拟全局实例属性。可用于模拟 `this.$store`、`this.$router` 等。 + +**签名:** + +```ts +mocks?: Record +``` + +**详细信息:** + +::: warning +此功能旨在模拟由第三方插件注入的变量,而不是 Vue 的原生属性,如 $root、$children 等。 +::: + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('global.mocks', async () => { + const $store = { + dispatch: jest.fn() + } + + const wrapper = mount(Component, { + global: { + mocks: { + $store + } + } + }) + + await wrapper.find('button').trigger('click') + + expect($store.dispatch).toHaveBeenCalledWith('click') +}) +``` + +#### global.plugins + +在挂载的组件上安装插件。 + +**签名:** + +```ts +plugins?: (Plugin | [Plugin, ...any[]])[] +``` + +**详细信息:** + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +import myPlugin from '@/plugins/myPlugin' + +test('global.plugins', () => { + mount(Component, { + global: { + plugins: [myPlugin] + } + }) +}) +``` + +要使用带选项的插件,可以传递选项数组。 + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('global.plugins with options', () => { + mount(Component, { + global: { + plugins: [Plugin, [PluginWithOptions, 'argument 1', 'another argument']] + } + }) +}) +``` + +#### global.provide + +提供数据,以便在 `setup` 函数中通过 `inject` 接收。 + +**签名:** + +```ts +provide?: Record +``` + +**详细信息:** + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('global.provide', () => { + const wrapper = mount(Component, { + global: { + provide: { + Theme: 'dark' + } + } + }) + + console.log(wrapper.html()) //=>
Theme is dark
+}) +``` + +如果你使用 ES6 `Symbol` 作为提供键,可以将其用作动态键: + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +const ThemeSymbol = Symbol() + +mount(Component, { + global: { + provide: { + [ThemeSymbol]: 'value' + } + } +}) +``` + +#### global.renderStubDefaultSlot + +即使在使用 `shallow` 或 `shallowMount` 时,也会渲染 `default` 插槽内容。 + +**签名:** + +```ts +renderStubDefaultSlot?: boolean +``` + +**详细信息:** + +默认为 **false**。 + +`Component.vue` + +```vue + + + +``` + +`AnotherComponent.vue` + +```vue + +``` + +`Component.spec.js` + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('global.renderStubDefaultSlot', () => { + const wrapper = mount(ComponentWithSlots, { + slots: { + default: '
My slot content
' + }, + shallow: true, + global: { + renderStubDefaultSlot: true + } + }) + + expect(wrapper.html()).toBe( + '
My slot content
' + ) +}) +``` + +由于技术限制,**此行为无法扩展到除默认插槽之外的其他插槽**。 + +#### global.stubs + +在挂载的组件上使用全局替代组件 (stub)。 + +**签名:** + +```ts +stubs?: Record +``` + +**详细信息:** + +默认情况下,`Transition` 和 `TransitionGroup` 组件会被自动 stub 掉。 + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('global.stubs using array syntax', () => { + const wrapper = mount(Component, { + global: { + stubs: ['Foo'] + } + }) + + expect(wrapper.html()).toEqual('
') +}) + +test('global.stubs using object syntax', () => { + const wrapper = mount(Component, { + global: { + stubs: { Foo: true } + } + }) + + expect(wrapper.html()).toEqual('
') +}) + +test('global.stubs using a custom component', () => { + const CustomStub = { + name: 'CustomStub', + template: '

custom stub content

' + } + + const wrapper = mount(Component, { + global: { + stubs: { Foo: CustomStub } + } + }) + + expect(wrapper.html()).toEqual('

custom stub content

') +}) +``` + +### shallow + +组件的所有子组件替换为 stub。 + +**签名:** + +```ts +shallow?: boolean +``` + +**详细信息:** + +默认为 **false**。 + +`Component.vue` + +```vue + + + +``` + +`Component.spec.js` + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('shallow', () => { + const wrapper = mount(Component, { shallow: true }) + + expect(wrapper.html()).toEqual( + `` + ) +}) +``` + +::: tip +`shallowMount()` 是使用 `shallow: true` 挂载组件的别名。 +::: + +## Wrapper methods + +当你使用 `mount` 时,会返回一个 `VueWrapper`,它包含了一些用于测试的有用方法。`VueWrapper` 是对你的组件实例的一个轻量级包装。 + +请注意,像 `find` 这样的函数返回一个 `DOMWrapper`,它是对你组件及其子组件中的 DOM 节点的一个轻量级包装。两者都实现了类似的 API。 + +### attributes + +返回 DOM 节点上的属性。 + +**签名:** + +```ts +attributes(): { [key: string]: string } +attributes(key: string): string +attributes(key?: string): { [key: string]: string } | string +``` + +**详细信息:** + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('attributes', () => { + const wrapper = mount(Component) + + expect(wrapper.attributes('id')).toBe('foo') + expect(wrapper.attributes('class')).toBe('bar') +}) +``` + +### classes + +**签名:** + +```ts +classes(): string[] +classes(className: string): boolean +classes(className?: string): string[] | boolean +``` + +**详细信息:** + +返回元素上的类名数组。 + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('classes', () => { + const wrapper = mount(Component) + + expect(wrapper.classes()).toContain('my-span') + expect(wrapper.classes('my-span')).toBe(true) + expect(wrapper.classes('not-existing')).toBe(false) +}) +``` + +### emitted + +返回组件发出的所有事件。 + +**签名:** + +```ts +emitted(): Record +emitted(eventName: string): undefined | T[] +emitted(eventName?: string): undefined | T[] | Record +``` + +**详细信息:** + +参数被存储在一个数组中,因此你可以验证每个事件发出时的参数。 + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('emitted', () => { + const wrapper = mount(Component) + + // wrapper.emitted() equals to { greet: [ ['hello'], ['goodbye'] ] } + + expect(wrapper.emitted()).toHaveProperty('greet') + expect(wrapper.emitted().greet).toHaveLength(2) + expect(wrapper.emitted().greet[0]).toEqual(['hello']) + expect(wrapper.emitted().greet[1]).toEqual(['goodbye']) +}) +``` + +### exists + +验证一个元素是否存在。 + +**签名:** + +```ts +exists(): boolean +``` + +**详细信息:** + +你可以使用与 `querySelector` 实现相同的语法。 + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('exists', () => { + const wrapper = mount(Component) + + expect(wrapper.find('span').exists()).toBe(true) + expect(wrapper.find('p').exists()).toBe(false) +}) +``` + +### find + +查找一个元素,如果找到则返回一个 `DOMWrapper`。 + +**签名:** + +```ts +find(selector: K): DOMWrapper +find(selector: K): DOMWrapper +find(selector: string): DOMWrapper +find(selector: string): DOMWrapper +find(selector: string | RefSelector): DOMWrapper; +``` + +**详细信息:** + +你可以使用与 `querySelector` 相同的语法。`find` 基本上是 `querySelector` 的别名。此外,你还可以搜索元素引用。 + +它与 `get` 类似,但如果未找到元素,`find` 将返回一个 ErrorWrapper,而 [`get`](#get) 会抛出一个错误。 + +根据经验,当你断言某个元素不存在时,请始终使用 `find`。如果你断言某个元素确实存在,请使用 [`get`](#get)。 + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('find', () => { + const wrapper = mount(Component) + + wrapper.find('span') //=> found; returns DOMWrapper + wrapper.find('[data-test="span"]') //=> found; returns DOMWrapper + wrapper.find({ ref: 'span' }) //=> found; returns DOMWrapper + wrapper.find('p') //=> nothing found; returns ErrorWrapper +}) +``` + +### findAll + +与 `find` 类似,但返回的是一个 `DOMWrapper` 数组。 + +**签名:** + +```ts +findAll(selector: K): DOMWrapper[] +findAll(selector: K): DOMWrapper[] +findAll(selector: string): DOMWrapper[] +findAll(selector: string): DOMWrapper[] +``` + +**详细信息:** + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import BaseTable from './BaseTable.vue' + +test('findAll', () => { + const wrapper = mount(BaseTable) + + // .findAll() returns an array of DOMWrappers + const thirdRow = wrapper.findAll('span')[2] +}) +``` + +### findComponent + +找到一个 Vue 组件实例并返回一个 `VueWrapper` (如果找到)。否则返回 `ErrorWrapper`。 + +**签名:** + +```ts +findComponent(selector: string): WrapperLike +findComponent(selector: T | Exclude): VueWrapper> +findComponent(selector: T | string): DOMWrapper +findComponent(selector: NameSelector | RefSelector): VueWrapper +findComponent(selector: T | FindComponentSelector): VueWrapper +findComponent(selector: FindComponentSelector): WrapperLike +``` + +**详细信息:** + +`findComponent` 支持几种语法: + +| 语法 | 示例 | 详情 | +| --------------- | ----------------------------- | ------------------------------------------ | +| querySelector | `findComponent('.component')` | 匹配标准查询选择器。 | +| 组件名称 | `findComponent({name: 'a'})` | 匹配 PascalCase、snake-case 和 camelCase。 | +| 组件引用 | `findComponent({ref: 'ref'})` | 仅可用于已挂载组件的直接引用子组件。 | +| 单文件组件(SFC) | `findComponent(Component)` | 直接传入导入的组件。 | + +`Foo.vue` + +```vue + + + +``` + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js` + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +import Foo from '@/Foo.vue' + +test('findComponent', () => { + const wrapper = mount(Component) + + // All the following queries would return a VueWrapper + + wrapper.findComponent('.foo') + wrapper.findComponent('[data-test="foo"]') + + wrapper.findComponent({ name: 'Foo' }) + + wrapper.findComponent({ ref: 'foo' }) + + wrapper.findComponent(Foo) +}) +``` + +:::warning +如果组件中的 `ref` 指向 HTML 元素,`findComponent` 将返回一个空的包装器。这是预期的行为。 +::: + +:::warning 使用 CSS 选择器时的注意事项 +使用 `findComponent` 和 CSS 选择器可能会导致混淆的行为。 + +考虑以下示例: + +```js +const ChildComponent = { + name: 'Child', + template: '
' +} +const RootComponent = { + name: 'Root', + components: { ChildComponent }, + template: '' +} +const wrapper = mount(RootComponent) +const rootByCss = wrapper.findComponent('.root') // => finds Root +expect(rootByCss.vm.$options.name).toBe('Root') +const childByCss = wrapper.findComponent('.child') +expect(childByCss.vm.$options.name).toBe('Root') // => still Root +``` + +这种行为的原因是 `RootComponent` 和 `ChildComponent` 共享相同的 DOM 节点,并且每个唯一的 DOM 节点只包含第一个匹配的组件。 +::: + +:::info 使用 CSS 选择器时的 WrapperLike 类型 +例如,当使用 `wrapper.findComponent('.foo')` 时,VTU 将返回 `WrapperLike` 类型。这是因为功能组件需要一个 `DOMWrapper`,否则返回的是 `VueWrapper`。你可以通过提供正确的组件类型来强制返回 `VueWrapper`: + +```typescript +wrapper.findComponent('.foo') // returns WrapperLike +wrapper.findComponent('.foo') // returns VueWrapper +wrapper.findComponent('.foo') // returns VueWrapper +``` + +::: + +### findAllComponents + +**签名:** + +```ts +findAllComponents(selector: string): WrapperLike[] +findAllComponents(selector: T | Exclude): VueWrapper>[] +findAllComponents(selector: string): DOMWrapper[] +findAllComponents(selector: T): DOMWrapper[] +findAllComponents(selector: NameSelector): VueWrapper[] +findAllComponents(selector: T | FindAllComponentsSelector): VueWrapper[] +findAllComponents(selector: FindAllComponentsSelector): WrapperLike[] +``` + +**详细信息:** + +与 `findComponent` 类似,但查找所有匹配查询的 Vue 组件实例。返回一个 `VueWrapper` 数组。 + +:::warning +`ref` 语法在 `findAllComponents` 中不支持。所有其他查询语法都是有效的。 +::: + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('findAllComponents', () => { + const wrapper = mount(Component) + + // Returns an array of VueWrapper + wrapper.findAllComponents('[data-test="number"]') +}) +``` + +:::warning 使用 CSS 选择器 +`findAllComponents` 在使用 CSS 选择器时具有与 [findComponent](#findcomponent) 相同的行为。 +::: + +### get + +获取一个元素,如果找到则返回一个 `DOMWrapper`,否则抛出错误。 + +**签名:** + +```ts +get(selector: K): Omit, 'exists'> +get(selector: K): Omit, 'exists'> +get(selector: string): Omit, 'exists'> +get(selector: string): Omit, 'exists'> +``` + +**详细信息:** + +它与 `find` 类似,但如果未找到元素,`get` 会抛出错误,而 [`find`](#find) 会返回一个 ErrorWrapper。 + +根据经验,除非你断言某个元素不存在 (使用 [`find`](#find)),否则请始终使用 `get`。 + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('get', () => { + const wrapper = mount(Component) + + wrapper.get('span') //=> found; returns DOMWrapper + + expect(() => wrapper.get('.not-there')).toThrowError() +}) +``` + +### getComponent + +获取 Vue 组件实例,如果找到则返回一个 `VueWrapper`,否则抛出错误。 + +**签名:** + +```ts +getComponent(selector: new () => T): Omit, 'exists'> +getComponent(selector: { name: string } | { ref: string } | string): Omit, 'exists'> +getComponent(selector: any): Omit, 'exists'> +``` + +**详细信息:** + +它与 `findComponent` 类似,但如果未找到 Vue 组件实例,`getComponent` 会抛出错误,而 [`findComponent`](#findComponent) 会返回一个 ErrorWrapper。 + +**支持的语法:** + +| 语法 | 示例 | 详细信息 | +| --------------- | ---------------------------- | ---------------------------------------- | +| querySelector | `getComponent('.component')` | 匹配标准查询选择器。 | +| 组件名称 | `getComponent({name: 'a'})` | 匹配 PascalCase、snake-case、camelCase。 | +| 组件引用 | `getComponent({ref: 'ref'})` | 仅可用于已挂载组件的直接引用子组件。 | +| 单文件组件(SFC) | `getComponent(Component)` | 直接传入已导入的组件。 | + +`Foo.vue` + +```vue + + + +``` + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js` + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +import Foo from '@/Foo.vue' + +test('getComponent', () => { + const wrapper = mount(Component) + + wrapper.getComponent({ name: 'foo' }) // returns a VueWrapper + wrapper.getComponent(Foo) // returns a VueWrapper + + expect(() => wrapper.getComponent('.not-there')).toThrowError() +}) +``` + +### html + +返回元素的 HTML 内容。 + +默认情况下,输出会使用 [`js-beautify`](https://github.com/beautify-web/js-beautify) 进行格式化,以使快照更易读。如果需要未格式化的 HTML 字符串,可以使用 `raw: true` 选项。 + +**签名:** + +```ts +html(): string +html(options?: { raw?: boolean }): string +``` + +**详细信息:** + +`Component.vue`: + +```vue + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('html', () => { + const wrapper = mount(Component) + + expect(wrapper.html()).toBe('
\n' + '

Hello world

\n' + '
') + + expect(wrapper.html({ raw: true })).toBe('

Hello world

') +}) +``` + +### isVisible + +验证一个元素是否可见。 + +**签名:** + +```ts +isVisible(): boolean +``` + +**详细信息:** + +::: warning +`isVisible()` 仅在使用 [`attachTo`](#attachTo) 将包装器附加到 DOM 时才能正确工作。 +::: + +```js +const Component = { + template: `
` +} + +test('isVisible', () => { + const wrapper = mount(Component, { + attachTo: document.body + }) + + expect(wrapper.find('span').isVisible()).toBe(false) +}) +``` + +### props + +返回传递给 Vue 组件的属性 (props)。 + +**签名:** + +```ts +props(): { [key: string]: any } +props(selector: string): any +props(selector?: string): { [key: string]: any } | any +``` + +**详细信息:** + +`Component.vue`: + +```js +export default { + name: 'Component', + props: { + truthy: Boolean, + object: Object, + string: String + } +} +``` + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('props', () => { + const wrapper = mount(Component, { + global: { stubs: ['Foo'] } + }) + + const foo = wrapper.getComponent({ name: 'Foo' }) + + expect(foo.props('truthy')).toBe(true) + expect(foo.props('object')).toEqual({}) + expect(foo.props('notExisting')).toEqual(undefined) + expect(foo.props()).toEqual({ + truthy: true, + object: {}, + string: 'string' + }) +}) +``` + +:::tip +根据经验,测试传递的属性的效果 (如 DOM 更新、触发的事件等)。这将使测试比仅仅断言一个属性被传递要更有效。 +::: + +### setData + +更新组件内部数据。 + +**签名:** + +```ts +setData(data: Record): Promise +``` + +**详细信息:** + +`setData` 不允许设置组件中未定义的新属性。 + +::: warning +请注意,`setData` 不会修改组合式 API 中 setup() 的数据。 +::: + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js`: + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('setData', async () => { + const wrapper = mount(Component) + expect(wrapper.html()).toContain('Count: 0') + + await wrapper.setData({ count: 1 }) + + expect(wrapper.html()).toContain('Count: 1') +}) +``` + +::: warning +在调用 `setData` 时,你应该使用 `await`,以确保 Vue 在你进行断言之前更新 DOM。 +::: + +### setProps + +更新组件的属性。 + +**签名:** + +```ts +setProps(props: Record): Promise +``` + +**详细信息:** + +`Component.vue`: + +```vue + + + +``` + +`Component.spec.js` + +```js +import { mount } from '@vue/test-utils' +import Component from './Component.vue' + +test('updates prop', async () => { + const wrapper = mount(Component, { + props: { + message: 'hello' + } + }) + + expect(wrapper.html()).toContain('hello') + + await wrapper.setProps({ message: 'goodbye' }) + + expect(wrapper.html()).toContain('goodbye') +}) +``` + +::: warning +在调用 `setProps` 时,你应该使用 `await`,以确保 Vue 在你进行断言之前更新 DOM。 +::: + +### setValue + +在 DOM 元素上设置一个值,包括: + +- `` + - 会检测 `type="checkbox"` 和 `type="radio"`,并将 `element.checked` 设置为相应的值。 +- `