diff --git a/.vitepress/components.d.ts b/.vitepress/components.d.ts index b509b572..1d997988 100644 --- a/.vitepress/components.d.ts +++ b/.vitepress/components.d.ts @@ -1,10 +1,10 @@ /* eslint-disable */ +/* prettier-ignore */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 export {} -/* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { Contributors: typeof import('./components/Contributors.vue')['default'] diff --git a/.vitepress/components/FeaturesList.vue b/.vitepress/components/FeaturesList.vue index c6a4359b..fadf20b1 100644 --- a/.vitepress/components/FeaturesList.vue +++ b/.vitepress/components/FeaturesList.vue @@ -86,7 +86,6 @@ >expect-type 进行类型测试 - 支持分片 diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 0fdf8e4e..1cf751b5 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -32,13 +32,13 @@ export default () => { en: { label: 'English', lang: 'en', - link: 'https://vitest.dev/', + link: 'https://v1.vitest.dev/', }, }, head: [ ['meta', { name: 'theme-color', content: '#729b1a' }], - ['link', { rel: 'icon', href: '/favicon.ico', sizes: '48x48' }], - ['link', { rel: 'icon', href: '/logo.svg', sizes: 'any', type: 'image/svg+xml' }], + ['link', { rel: 'icon', href: '/favicon.ico', sizes: 'any' }], + ['link', { rel: 'icon', href: '/logo.svg', type: 'image/svg+xml' }], ['meta', { name: 'author', content: `${teamMembers.map(c => c.name).join(', ')} and ${vitestName} contributors` }], ['meta', { name: 'keywords', content: 'vitest, vite, test, coverage, snapshot, react, vue, preact, svelte, solid, lit, marko, ruby, cypress, puppeteer, jsdom, happy-dom, test-runner, jest, typescript, esm, tinypool, tinyspy, node' }], ['meta', { property: 'og:title', content: vitestName }], @@ -69,7 +69,7 @@ export default () => { logo: '/logo.svg', editLink: { - pattern: 'https://github.com/vitest-dev/docs-cn/tree/dev/:path', + pattern: 'https://github.com/vitest-dev/docs-cn/tree/v1/:path', text: '为此页提供修改建议', }, @@ -123,7 +123,7 @@ export default () => { text: '社区指南', link: contributing, }, - ] + ], }, { items: [ @@ -135,10 +135,6 @@ export default () => { text: 'v0.x', link: 'https://v0.vitest.dev/', }, - { - text: 'v1.x', - link: 'https://v1.vitest.dev/', - }, ], }, ], @@ -173,12 +169,12 @@ export default () => { ], }, ], - '/': [ + '/guide/': [ { text: '指南', items: [ { - text: '简介', + text: '为什么是 Vitest?', link: '/guide/why', }, { @@ -222,45 +218,12 @@ export default () => { link: '/guide/testing-types', }, { - text: 'Vitest UI', + text: 'UI 模式', link: '/guide/ui', }, { text: '浏览器模式', - link: '/guide/browser/', - collapsed: false, - items: [ - { - text: 'Assertion API', - link: '/guide/browser/assertion-api', - docFooterText: 'Assertion API | Browser Mode', - }, - { - text: 'Retry-ability', - link: '/guide/browser/retry-ability', - docFooterText: 'Retry-ability | Browser Mode', - }, - { - text: 'Context', - link: '/guide/browser/context', - docFooterText: 'Context | Browser Mode', - }, - { - text: 'Interactivity API', - link: '/guide/browser/interactivity-api', - docFooterText: 'Interactivity API | Browser Mode', - }, - { - text: 'Commands', - link: '/guide/browser/commands', - docFooterText: 'Commands | Browser Mode', - }, - { - text: 'Examples', - link: '/guide/browser/examples', - docFooterText: 'Examples | Browser Mode', - }, - ], + link: '/guide/browser', }, { text: '源码内联测试', @@ -287,7 +250,7 @@ export default () => { link: '/guide/debugging', }, { - text: '与其他测试框架对比', + text: '测试框架比较', link: '/guide/comparisons', }, { @@ -304,6 +267,8 @@ export default () => { }, ], }, + ], + '/api/': [ { text: 'API', items: [ @@ -337,6 +302,8 @@ export default () => { }, ], }, + ], + '/config/': [ { text: '配置', items: [ diff --git a/advanced/api.md b/advanced/api.md index f743f3e4..62a6f408 100644 --- a/advanced/api.md +++ b/advanced/api.md @@ -82,7 +82,6 @@ Vitest 实例需要当前的测试模式。它可以是以下之一: 你可以使用 `start` 方法运行测试或者基准测试。你还可以传递一个字符串数组以筛选测试文件。 - ### `provide` Vitest 暴露了`provide`方法,它是`vitest.getCoreWorkspaceProject().provide`的简写。使用该方法,您可以从主线程向测试传递值。所有值在存储前都会通过 `structuredClone`进行检查,但值本身不会被克隆。 diff --git a/advanced/runner.md b/advanced/runner.md index 8340ff7f..2f04b456 100644 --- a/advanced/runner.md +++ b/advanced/runner.md @@ -31,7 +31,7 @@ export interface VitestRunner { * 这是在实际运行测试函数之前被调用的。 * 此时已经有了带有 "state" 和 "startTime" 属性的 "result" 对象。 */ - onBeforeTryTask?: (test: TaskPopulated, options: { retry: number, repeats: number }) => unknown + onBeforeTryTask?: (test: TaskPopulated, options: { retry: number; repeats: number }) => unknown /** * 这是在结果和状态都被设置之后被调用的。 */ @@ -40,7 +40,7 @@ export interface VitestRunner { * 这是在运行测试函数后立即被调用的。此时还没有新的状态。 * 如果测试函数抛出异常,将不会调用此方法。 */ - onAfterTryTask?: (test: TaskPopulated, options: { retry: number, repeats: number }) => unknown + onAfterTryTask?: (test: TaskPopulated, options: { retry: number; repeats: number }) => unknown /** * 这是在运行单个测试套件之前被调用的,此时还没有测试结果。 @@ -80,7 +80,7 @@ export interface VitestRunner { /** * 这个方法被用于 "test" 和 "custom" 处理程序。 * 你可以在 "setupFiles" 中使用 "beforeAll" 来定义自定义上下文,而不是使用 runner。 - * 更多信息请参考:https://vitest.dev/advanced/runner.html#your-task-function + * 更多信息请参考:https://v1.vitest.dev/advanced/runner.html#your-task-function */ extendTaskContext?: (context: TaskContext) => TaskContext /** diff --git a/api/expect-typeof.md b/api/expect-typeof.md index 1a8e1ae9..4b5630a5 100644 --- a/api/expect-typeof.md +++ b/api/expect-typeof.md @@ -50,7 +50,7 @@ expectTypeOf().not.toMatchTypeOf() ```ts twoslash import { expectTypeOf } from 'vitest' -type ResponsiveProp = T | T[] | { xs?: T, sm?: T, md?: T } +type ResponsiveProp = T | T[] | { xs?: T; sm?: T; md?: T } interface CSSProperties { margin?: string @@ -66,10 +66,10 @@ const cssProperties: CSSProperties = { margin: '1px', padding: '2px' } expectTypeOf(getResponsiveProp(cssProperties)) .extract<{ xs?: any }>() // extracts the last type from a union .toEqualTypeOf<{ - xs?: CSSProperties - sm?: CSSProperties - md?: CSSProperties - }>() + xs?: CSSProperties + sm?: CSSProperties + md?: CSSProperties +}>() expectTypeOf(getResponsiveProp(cssProperties)) .extract() // extracts an array from a union @@ -93,17 +93,16 @@ type ResponsiveProp = T | T[] | { xs?: T; sm?: T; md?: T } interface CSSProperties { margin?: string; padding?: string } -function getResponsiveProp(\_props: T): ResponsiveProp { -return {} +function getResponsiveProp(_props: T): ResponsiveProp { + return {} } const cssProperties: CSSProperties = { margin: '1px', padding: '2px' } expectTypeOf(getResponsiveProp(cssProperties)) -.exclude() -.exclude<{ xs?: unknown }>() // or just .exclude() -.toEqualTypeOf() - + .exclude() + .exclude<{ xs?: unknown }>() // or just .exclude() + .toEqualTypeOf() ``` ::: warning diff --git a/api/expect.md b/api/expect.md index 087db3e0..aaabe31c 100644 --- a/api/expect.md +++ b/api/expect.md @@ -24,7 +24,7 @@ expect(input).toBe(2) // jest API 此外,`expect` 可以静态地使用来访问匹配器函数,稍后将会介绍。 ::: warning -如果表达式没有类型错误,则 `expect` 对测试类型没有影响。 如果你想使用 Vitest 作为[类型检查器](/guide/testing-types),请使用 [`expectTypeOf`](/api/expect-typeof) 或 [`assertType`](/api/assert-type) 。 +如果表达式没有类型错误,则 `expect` 对测试类型没有影响。 如果你想使用 Vitest 作为 [类型检查器](/guide/testing-types) ,请使用 [`expectTypeOf`](/api/expect-typeof) 或 [`assertType`](/api/assert-type) 。 ::: ## soft @@ -1020,7 +1020,6 @@ test('spy function returns bananas on second call', () => { - **类型**: `() => Awaitable` - 此断言检查函数是否至少成功解析过一次值(即未reject)。需要将 spy 函数传递给 `expect`。 如果函数返回了一个promise,但尚未resolved,则将会失败。 @@ -1028,12 +1027,12 @@ test('spy function returns bananas on second call', () => { ```ts twoslash // @filename: db/apples.js /** @type {any} */ -const db = {} -export default db // @filename: test.ts // ---cut--- import { expect, test, vi } from 'vitest' import db from './db/apples.js' +const db = {} +export default db async function getApplesPrice(amount: number) { return amount * await db.get('price') @@ -1057,7 +1056,6 @@ test('spy function resolved a value', async () => { 这只会计算已resolved的promises。如果函数返回了一个promise,但尚未resolved,则不会计算在内。 - ```ts twoslash import { expect, test, vi } from 'vitest' @@ -1075,13 +1073,10 @@ test('spy function resolved a value two times', async () => { - **类型**: `(returnValue: any) => Awaitable` - - 您可以调用此断言来检查函数是否至少成功解析过一次某个值。需要将 spy 函数传递给`expect`。 如果函数返回了一个promise,但尚未resolved,则将会失败。 - ```ts twoslash import { expect, test, vi } from 'vitest' diff --git a/api/index.md b/api/index.md index 6cbb27ea..266d84cd 100644 --- a/api/index.md +++ b/api/index.md @@ -2,7 +2,7 @@ outline: deep --- -# Test API 索引 +# Test API 索引 {#test-api-reference} 下面的类型签名中使用了以下类型: @@ -32,14 +32,23 @@ interface TestOptions { } ``` +自 Vitest 1.3.0 版本起弃用将配置选项作为最后一位参数的写法。在 2.0.0 版本完全移除该语法前,你会看到弃用警告。如需传递配置选项,请使用测试函数的第二个参数: + +```js +import { test } from 'vitest' + +test('flaky test', () => {}, { retry: 3 }) // [!code --] +test('flaky test', { retry: 3 }, () => {}) // [!code ++] +``` + 当测试函数返回承诺时,运行程序将等待它被解析以收集异步期望。 如果承诺被拒绝,测试就会失败。 ::: tip -在 Jest 中,`TestFunction` 也可以是 `(done: DoneCallback) => void` 类型。如果使用这种形式,测试将在调用 `done` 之前不会结束。也可以使用 `async` 函数来实现相同的效果,请参阅[迁移指南中的回调完成部分](/guide/migration#回调完成)。 +在 Jest 中,`TestFunction` 也可以是 `(done: DoneCallback) => void` 类型。如果使用这种形式,测试将在调用 `done` 之前不会结束。也可以使用 `async` 函数来实现相同的效果,请参阅 [迁移指南中的回调完成部分](/guide/migration#done-callback)。 ::: -大多数选项都支持点语法和对象语法,允许您使用您喜欢的任何样式。 +自 Vitest 1.3.0 起,大多数选项都支持点语法和对象语法,允许您使用您喜欢的任何样式。 :::code-group @@ -51,7 +60,7 @@ test.skip('skipped test', () => { }) ``` -```ts [object-syntax] twoslash +```ts [object-syntax 1.3.0] twoslash import { test } from 'vitest' test('skipped test', { skip: true }, () => { @@ -78,12 +87,12 @@ test('should work as expected', () => { }) ``` -### test.extend {#test-extended} +### test.extend 0.32.3 {#test-extended} - **类型:** `>(fixtures: Fixtures): TestAPI` - **别名:** `it.extend` -使用 `test.extend` 来使用自定义的 fixtures 扩展测试上下文。这将返回一个新的 `test`,它也是可扩展的,因此可以根据需要扩展更多的 fixtures 或覆盖现有的 fixtures。有关更多信息,请参阅[扩展测试上下文](/guide/test-context.html#test-extend)。 +使用 `test.extend` 来使用自定义的 fixtures 扩展测试上下文。这将返回一个新的 `test`,它也是可扩展的,因此可以根据需要扩展更多的 fixtures 或覆盖现有的 fixtures。有关更多信息,请参阅 [扩展测试上下文](/guide/test-context.html#test-extend)。 ```ts import { expect, test } from 'vitest' @@ -119,7 +128,7 @@ myTest('add item', ({ todos }) => { import { assert, test } from 'vitest' test.skip('skipped test', () => { - // Test skipped, no error + // 测试被跳过,没有错误 assert.equal(Math.sqrt(4), 3) }) ``` @@ -131,7 +140,7 @@ import { assert, test } from 'vitest' test('skipped test', (context) => { context.skip() - // Test skipped, no error + // 测试被跳过,没有错误 assert.equal(Math.sqrt(4), 3) }) ``` @@ -149,12 +158,12 @@ import { assert, test } from 'vitest' const isDev = process.env.NODE_ENV === 'development' test.skipIf(isDev)('prod only test', () => { - // this test only runs in production + // 此测试仅在生产环境中运行 }) ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.runIf @@ -170,12 +179,12 @@ import { assert, test } from 'vitest' const isDev = process.env.NODE_ENV === 'development' test.runIf(isDev)('dev only test', () => { - // this test only runs in development + // 此测试仅在开发环境中运行 }) ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.only @@ -191,7 +200,7 @@ test.runIf(isDev)('dev only test', () => { import { assert, test } from 'vitest' test.only('test', () => { - // Only this test (and others marked with only) are run + // 只有此测试(以及其他标记为 `only` 的测试)会被运行。 assert.equal(Math.sqrt(4), 2) }) ``` @@ -214,17 +223,11 @@ test.only('test', () => { ```ts twoslash import { describe, test } from 'vitest' -// The two tests marked with concurrent will be run in parallel +// 标记为 `concurrent` 的两个测试将并行运行。 describe('suite', () => { - test('serial test', async () => { - /* ... */ - }) - test.concurrent('concurrent test 1', async () => { - /* ... */ - }) - test.concurrent('concurrent test 2', async () => { - /* ... */ - }) + test('serial test', async () => { /* ... */ }) + test.concurrent('concurrent test 1', async () => { /* ... */ }) + test.concurrent('concurrent test 2', async () => { /* ... */ }) }) ``` @@ -237,7 +240,7 @@ test.only.concurrent(/* ... */) // or test.concurrent.only(/* ... */) test.todo.concurrent(/* ... */) // or test.concurrent.todo(/* ... */) ``` -运行并发测试时,快照和断言必须使用本地[测试上下文](/guide/test-context.md)中的 `expect`,以确保检测到正确的测试。 +运行并发测试时,快照和断言必须使用本地 [测试上下文](/guide/test-context) 中的 `expect`,以确保检测到正确的测试。 ```ts test.concurrent('test 1', async ({ expect }) => { @@ -249,7 +252,7 @@ test.concurrent('test 2', async ({ expect }) => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.sequential @@ -262,36 +265,20 @@ test.concurrent('test 2', async ({ expect }) => { import { describe, test } from 'vitest' // ---cut--- -// with config option { sequence: { concurrent: true } } -test('concurrent test 1', async () => { - /* ... */ -}) -test('concurrent test 2', async () => { - /* ... */ -}) +// 使用配置选项 `{ sequence: { concurrent: true } }` +test('concurrent test 1', async () => { /* ... */ }) +test('concurrent test 2', async () => { /* ... */ }) -test.sequential('sequential test 1', async () => { - /* ... */ -}) -test.sequential('sequential test 2', async () => { - /* ... */ -}) +test.sequential('sequential test 1', async () => { /* ... */ }) +test.sequential('sequential test 2', async () => { /* ... */ }) -// within concurrent suite +// 在并发套件中 describe.concurrent('suite', () => { - test('concurrent test 1', async () => { - /* ... */ - }) - test('concurrent test 2', async () => { - /* ... */ - }) + test('concurrent test 1', async () => { /* ... */ }) + test('concurrent test 2', async () => { /* ... */ }) - test.sequential('sequential test 1', async () => { - /* ... */ - }) - test.sequential('sequential test 2', async () => { - /* ... */ - }) + test.sequential('sequential test 1', async () => { /* ... */ }) + test.sequential('sequential test 2', async () => { /* ... */ }) }) ``` @@ -303,7 +290,7 @@ describe.concurrent('suite', () => { 使用 `test.todo` 来存根测试,以便稍后实施。测试报告中将显示一个条目,以便知道还有多少测试需要执行。 ```ts -// An entry will be shown in the report for this test +// 此测试将在报告中显示一个条目 test.todo('unimplemented test') ``` @@ -326,7 +313,7 @@ test.fails('fail test', async () => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### test.each @@ -334,10 +321,6 @@ test.fails('fail test', async () => { - **类型:** `(cases: ReadonlyArray, ...args: any[]) => void` - **别名:** `it.each` -::: tip - `test.each` 是为了与 Jest 兼容而提供的,Vitest 还提供了 [`test.for`](#test-for),并集成了 [`TestContext`](/guide/test-context)。 -::: - 当需要使用不同变量运行同一测试时,请使用 `test.each`。 我们可以按照测试功能参数的顺序,在测试名称中注入带有 [printf formatting](https://nodejs.org/api/util.html#util_util_format_format_args) 的参数。 @@ -362,7 +345,7 @@ test.each([ expect(a + b).toBe(expected) }) -// this will return +// 这将返回 // ✓ add(1, 1) -> 2 // ✓ add(1, 2) -> 3 // ✓ add(2, 1) -> 3 @@ -379,7 +362,7 @@ test.each([ expect(a + b).toBe(expected) }) -// this will return +// 这将返回 // ✓ add(1, 1) -> 2 // ✓ add(1, 2) -> 3 // ✓ add(2, 1) -> 3 @@ -400,7 +383,7 @@ test.each` expect(a.val + b).toBe(expected) }) -// this will return +// 这将返回 // ✓ add(1, b) -> 1b // ✓ add(2, b) -> 2b // ✓ add(3, b) -> 3b @@ -425,55 +408,16 @@ test.each` }) ``` +如果你想访问 `TestContext` ,请在单个测试中使用 `describe.each` 。 + ::: tip Vitest 使用 chai `format` 方法处理 `$values`。如果数值太短,可以在配置文件中增加 [chaiConfig.truncateThreshold](/config/#chaiconfig-truncatethreshold)。 ::: ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: -### test.for - -- **Alias:** `it.for` - -作为 `test.each` 的替代,提供 [`TestContext`](/guide/test-context)。 - -与 `test.each` 的区别在于如何在参数中提供数组情况。 -其他非数组情况(包括模板字符串的使用)完全相同。 - -```ts -// `each` spreads array case -test.each([ - [1, 1, 2], - [1, 2, 3], - [2, 1, 3], -])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --] - expect(a + b).toBe(expected) -}) - -// `for` doesn't spread array case -test.for([ - [1, 1, 2], - [1, 2, 3], - [2, 1, 3], -])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++] - expect(a + b).toBe(expected) -}) -``` - -第二个参数是 [`TestContext`](/guide/test-context),可用于并发快照等 - -```ts -test.concurrent.for([ - [1, 1], - [1, 2], - [2, 1], -])('add(%i, %i)', ([a, b], { expect }) => { - expect(a + b).matchSnapshot() -}) -``` - ## bench - **类型:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` @@ -549,7 +493,7 @@ export interface Options { - **类型:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` -可以使用 "bench.skip "语法跳过运行某些基准。 +可以使用 `bench.skip` 语法跳过运行某些基准。 ```ts twoslash import { bench } from 'vitest' @@ -597,7 +541,7 @@ bench.todo('unimplemented test') ```ts twoslash // basic.spec.ts -// organizing tests +// 组织测试 import { describe, expect, test } from 'vitest' @@ -623,7 +567,7 @@ describe('person', () => { ```ts twoslash // basic.bench.ts -// organizing benchmarks +// 组织基准测试 import { bench, describe } from 'vitest' @@ -650,9 +594,8 @@ describe('sort', () => { import { describe, expect, test } from 'vitest' function numberToCurrency(value: number | string) { - if (typeof value !== 'number') { - throw new TypeError('Value must be a number') - } + if (typeof value !== 'number') + throw new Error('Value must be a number') return value .toFixed(2) @@ -686,7 +629,7 @@ import { assert, describe, test } from 'vitest' describe.skip('skipped suite', () => { test('sqrt', () => { - // Suite skipped, no error + // 跳过测试套件,不报错 assert.equal(Math.sqrt(4), 3) }) }) @@ -704,12 +647,12 @@ import { describe, test } from 'vitest' const isDev = process.env.NODE_ENV === 'development' describe.skipIf(isDev)('prod only test suite', () => { - // this test suite only runs in production + // 此测试套件仅在生产环境中运行 }) ``` ::: warning -将 Vitest 用作[类型检查器](/guide/testing-types)时,你不能使用此语法。 +将 Vitest 用作 [类型检查器](/guide/testing-types) 时,你不能使用此语法。 ::: ### describe.runIf @@ -724,12 +667,12 @@ import { assert, describe, test } from 'vitest' const isDev = process.env.NODE_ENV === 'development' describe.runIf(isDev)('dev only test suite', () => { - // this test suite only runs in development + // 此测试套件仅在开发环境中运行 }) ``` ::: warning -将 Vitest 用作[类型检查器](/guide/testing-types)时,你不能使用此语法。 +将 Vitest 用作 [类型检查器](/guide/testing-types) 时,你不能使用此语法。 ::: ### describe.only @@ -741,7 +684,7 @@ describe.runIf(isDev)('dev only test suite', () => { ```ts twoslash import { assert, describe, test } from 'vitest' // ---cut--- -// Only this suite (and others marked with only) are run +// 只有此测试套件(以及其他标记为 `only` 的测试套件)会被运行。 describe.only('suite', () => { test('sqrt', () => { assert.equal(Math.sqrt(4), 3) @@ -749,12 +692,10 @@ describe.only('suite', () => { }) describe('other suite', () => { - // ... will be skipped + // 将被跳过... }) ``` -In order to do that run `vitest` with specific file containing the tests in question. - 有时,只运行某个文件中的测试套件,而忽略整个测试套件中的所有其他测试是非常有用的,因为这些测试会污染输出。 要做到这一点,请在包含相关测试的特定文件中运行 `vitest`。 @@ -772,22 +713,14 @@ In order to do that run `vitest` with specific file containing the tests in ques ```ts twoslash import { describe, test } from 'vitest' // ---cut--- -// All suites and tests within this suite will be run in parallel +// 此测试套件中的所有测试套件和测试将并行运行。 describe.concurrent('suite', () => { - test('concurrent test 1', async () => { - /* ... */ - }) + test('concurrent test 1', async () => { /* ... */ }) describe('concurrent suite 2', async () => { - test('concurrent test inner 1', async () => { - /* ... */ - }) - test('concurrent test inner 2', async () => { - /* ... */ - }) - }) - test.concurrent('concurrent test 3', async () => { - /* ... */ + test('concurrent test inner 1', async () => { /* ... */ }) + test('concurrent test inner 2', async () => { /* ... */ }) }) + test.concurrent('concurrent test 3', async () => { /* ... */ }) }) ``` @@ -800,7 +733,7 @@ describe.only.concurrent(/* ... */) // or describe.concurrent.only(/* ... */) describe.todo.concurrent(/* ... */) // or describe.concurrent.todo(/* ... */) ``` -运行并发测试时,快照和断言必须使用本地[测试上下文](/guide/test-context.md)中的 `expect` ,以确保检测到正确的测试。 +运行并发测试时,快照和断言必须使用本地 [测试上下文](/guide/test-context) 中的 `expect` ,以确保检测到正确的测试。 ```ts describe.concurrent('suite', () => { @@ -814,7 +747,7 @@ describe.concurrent('suite', () => { ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### describe.sequential @@ -827,20 +760,12 @@ describe.concurrent('suite', () => { import { describe, test } from 'vitest' // ---cut--- describe.concurrent('suite', () => { - test('concurrent test 1', async () => { - /* ... */ - }) - test('concurrent test 2', async () => { - /* ... */ - }) + test('concurrent test 1', async () => { /* ... */ }) + test('concurrent test 2', async () => { /* ... */ }) describe.sequential('', () => { - test('sequential test 1', async () => { - /* ... */ - }) - test('sequential test 2', async () => { - /* ... */ - }) + test('sequential test 1', async () => { /* ... */ }) + test('sequential test 2', async () => { /* ... */ }) }) }) ``` @@ -855,23 +780,17 @@ Vitest 通过 CLI 标志 [`--sequence.shuffle`](/guide/cli) 或配置选项 [`se import { describe, test } from 'vitest' // ---cut--- describe.shuffle('suite', () => { - test('random test 1', async () => { - /* ... */ - }) - test('random test 2', async () => { - /* ... */ - }) - test('random test 3', async () => { - /* ... */ - }) + test('random test 1', async () => { /* ... */ }) + test('random test 2', async () => { /* ... */ }) + test('random test 3', async () => { /* ... */ }) }) -// order depends on sequence.seed option in config (Date.now() by default) +// 顺序取决于配置中的 `sequence.seed` 选项(默认为 `Date.now()`) ``` `.skip`、`.only`和`.todo`适用于随机测试套件。 ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ### describe.todo @@ -881,7 +800,7 @@ describe.shuffle('suite', () => { 使用 `describe.todo` 来暂存待以后实施的套件。测试报告中会显示一个条目,这样就能知道还有多少测试需要执行。 ```ts -// An entry will be shown in the report for this suite +// 此测试套件将在报告中显示一个条目 describe.todo('unimplemented suite') ``` @@ -936,7 +855,7 @@ describe.each` ``` ::: warning -在将 Vitest 用作[类型检查器](/guide/testing-types)时,不能使用此语法。 +在将 Vitest 用作 [类型检查器](/guide/testing-types) 时,不能使用此语法。 ::: ## Setup and Teardown @@ -957,7 +876,7 @@ describe.each` import { beforeEach } from 'vitest' beforeEach(async () => { - // Clear mocks and add some testing data after before each test run + // 每次执行测试前,先重置所有 mock,然后准备好需要用到的测试数据 await stopMocking() await addUser({ name: 'John' }) }) @@ -965,16 +884,16 @@ beforeEach(async () => { 这里, `beforeEach` 确保每次测试都会添加用户。 -`beforeEach` 还接受一个可选的清理函数(相当于 `afterEach`)。 +自 Vitest v0.10.0 起,`beforeEach` 还接受一个可选的清理函数(相当于 `afterEach`)。 ```ts import { beforeEach } from 'vitest' beforeEach(async () => { - // called once before each test run + // 在每个测试运行之前调用一次 await prepareSomething() - // clean up function, called once after each test run + // 清理函数,在每个测试运行之后调用一次 return async () => { await resetSomething() } @@ -994,14 +913,14 @@ beforeEach(async () => { import { afterEach } from 'vitest' afterEach(async () => { - await clearTestingData() // clear testing data after each test run + await clearTestingData() // 在每个测试运行之后清除测试数据 }) ``` 在这里,`afterEach` 可确保在每次测试运行后清除测试数据。 ::: tip -Vitest 在 1.3.0 新增 [`onTestFinished`](#ontestfinished)。你可以在测试执行过程中调用它,以便在测试运行结束后清理任何状态。 +Vitest 在 1.3.0 起,新增 [`onTestFinished`](#ontestfinished)。你可以在测试执行过程中调用它,以便在测试运行结束后清理任何状态。 ::: ### beforeAll @@ -1017,22 +936,22 @@ Vitest 在 1.3.0 新增 [`onTestFinished`](#ontestfinished)。你可以在测试 import { beforeAll } from 'vitest' beforeAll(async () => { - await startMocking() // called once before all tests run + await startMocking() // 在所有测试运行之前调用一次 }) ``` 这里的 `beforeAll` 确保在测试运行前设置好模拟数据。 -`beforeAll` 还接受一个可选的清理函数(相当于 `afterAll`)。 +自 Vitest v0.10.0 起,`beforeAll` 还接受一个可选的清理函数(相当于 `afterAll`)。 ```ts import { beforeAll } from 'vitest' beforeAll(async () => { - // called once before all tests run + // 在所有测试运行之前调用一次 await startMocking() - // clean up function, called once after all tests run + // 清理函数,在所有测试运行之后调用一次 return async () => { await stopMocking() } @@ -1052,7 +971,7 @@ beforeAll(async () => { import { afterAll } from 'vitest' afterAll(async () => { - await stopMocking() // this method is called after all tests run + await stopMocking() // 此方法在所有测试运行之后被调用 }) ``` @@ -1066,7 +985,7 @@ Vitest 提供了一些 hooks,你可以在 _测试执行期间_ 调用这些钩 如果在测试体之外调用这些 hooks ,则会出错。 ::: -### onTestFinished {#ontestfinished} +### onTestFinished 1.3.0 {#ontestfinished} 这个 hook 总是在测试运行结束后调用。它在 `afterEach` 之后被调用,因为它们会影响测试结果。它接收一个包含当前测试结果的 `TaskResult` 。 diff --git a/api/mock.md b/api/mock.md index 6d63955b..6c0d9993 100644 --- a/api/mock.md +++ b/api/mock.md @@ -42,7 +42,7 @@ getApplesSpy.mock.calls.length === 1 清除每次调用的所有信息。调用该方法后,`.mock` 上的所有属性都将返回空状态。此方法不会重置实现。如果需要在不同断言之间清理 mock,该方法非常有用。 -如果我们希望在每次测试前自动调用该方法,可以在配置中启用 [`clearMocks``](/config/#clearmocks)设置。 +如果我们希望在每次测试前自动调用该方法,可以在配置中启用 [`clearMocks`](/config/#clearmocks) 设置。 ## mockName @@ -60,7 +60,7 @@ getApplesSpy.mock.calls.length === 1 import { vi } from 'vitest' // ---cut--- const mockFn = vi.fn().mockImplementation((apples: number) => apples + 1) -// or: vi.fn(apples => apples + 1); +// 或:vi.fn(apples => apples + 1); const NelliesBucket = mockFn(0) const BobsBucket = mockFn(1) @@ -116,12 +116,9 @@ import { vi } from 'vitest' // ---cut--- const myMockFn = vi.fn(() => 'original') -myMockFn.withImplementation( - () => 'temp', - () => { - myMockFn() // 'temp' - } -) +myMockFn.withImplementation(() => 'temp', () => { + myMockFn() // 'temp' +}) myMockFn() // 'original' ``` @@ -132,7 +129,7 @@ myMockFn() // 'original' test('async callback', () => { const myMockFn = vi.fn(() => 'original') - // We await this call since the callback is async + // 由于回调是异步的,我们要等待这个调用 await myMockFn.withImplementation( () => 'temp', async () => { @@ -157,7 +154,7 @@ import { vi } from 'vitest' // ---cut--- const asyncMock = vi.fn().mockRejectedValue(new Error('Async error')) -await asyncMock() // throws "Async error" +await asyncMock() // 抛出 "Async error" ``` ## mockRejectedValueOnce @@ -174,8 +171,8 @@ const asyncMock = vi .mockResolvedValueOnce('first call') .mockRejectedValueOnce(new Error('Async error')) -await asyncMock() // first call -await asyncMock() // throws "Async error" +await asyncMock() // 'first call' +await asyncMock() // 抛出 "Async error" ``` ## mockReset @@ -290,8 +287,7 @@ const fn = vi.fn() fn('arg1', 'arg2') fn('arg3') -fn.mock.calls -=== [ +fn.mock.calls === [ ['arg1', 'arg2'], // first call ['arg3'], // second call ] @@ -308,7 +304,6 @@ fn.mock.calls - `'return'` - 函数返回时没有抛出。 - `'throw'` - 函数抛出了一个值。 - `value` 属性包含返回值或抛出的错误。如果函数返回一个 `Promise`,那么即使Promise rejected,`result` 也将始终为 `'return'`。 ```js @@ -319,21 +314,20 @@ const fn = vi throw new Error('thrown error') }) -const result = fn() // returned 'result' +const result = fn() // 返回 'result' try { - fn() // threw Error + fn() // 抛出错误 } catch {} -fn.mock.results -=== [ - // first result +fn.mock.results === [ + // 首次调用结果 { type: 'return', value: 'result', }, - // last result + // 最后调用结果 { type: 'throw', value: Error, @@ -341,29 +335,6 @@ fn.mock.results ] ``` -## mock.settledResults - -包含函数中`resolved` 或 `rejected` 的所有值的数组。 - -如果函数从未`resolved` 或 `rejected` ,则此数组将为空。 - -```js -const fn = vi.fn().mockResolvedValueOnce('result') - -const result = fn() - -fn.mock.settledResults === [] - -await result - -fn.mock.settledResults === [ - { - type: 'fulfilled', - value: 'result', - }, -] -``` - ## mock.invocationCallOrder 此属性返回 mock 函数的执行顺序。它是一个数字数组,由所有定义的 mock 共享。 @@ -380,21 +351,6 @@ fn1.mock.invocationCallOrder === [1, 3] fn2.mock.invocationCallOrder === [2] ``` -## mock.contexts - -此属性是每次调用模拟函数时使用的 `this` 值的数组。 - -```js -const fn = vi.fn() -const context = {} - -fn.apply(context) -fn.call(context) - -fn.mock.contexts[0] === context -fn.mock.contexts[1] === context -``` - ## mock.instances 此属性是一个数组,其中包含使用 `new` 关键字调用模拟时创建的所有实例。请注意,这是函数的实际上下文(`this`),而不是返回值。 diff --git a/api/vi.md b/api/vi.md index db5ed558..8b62fa14 100644 --- a/api/vi.md +++ b/api/vi.md @@ -10,14 +10,13 @@ Vitest 通过其 `vi` 辅助工具提供实用功能来帮助你。可以全局 import { vi } from 'vitest' ``` -## Mock Modules +## 模拟模块 {#mock-modules} 本节介绍在 [模拟模块](/guide/mocking#modules) 时可以使用的 API。请注意,Vitest 不支持模拟使用 `require()` 导入的模块。 ### vi.mock - **类型**: `(path: string, factory?: (importOriginal: () => unknown) => unknown) => void` -- **类型**: `(path: Promise, factory?: (importOriginal: () => T) => unknown) => void` 用另一个模块替换提供的 `path` 中的所有导入模块。我们可以在路径内使用配置的 Vite 别名。对 `vi.mock` 的调用是悬挂式的,因此在何处调用并不重要。它总是在所有导入之前执行。如果需要在其作用域之外引用某些变量,可以在 [`vi.hoisted`](/api/vi#vi-hoisted)中定义它们,并在 `vi.mock` 中引用它们。 @@ -29,6 +28,9 @@ import { vi } from 'vitest' Vitest 不会模拟 [setup file](/config/#setupfiles) 中导入的模块,因为这些模块在运行测试文件时已被缓存。我们可以在 [`vi.hoisted`](#vi-hoisted) 中调用 [`vi.resetModules()`](#vi-resetmodules) ,在运行测试文件前清除所有模块缓存。 ::: +::: warning +[浏览器模式](/guide/browser) 目前不支持模拟模块。可以在这个 [issue](https://github.com/vitest-dev/vitest/issues/3046) 中持续关注此功能。 +::: 如果定义了 `factory`,所有导入都将返回其结果。Vitest 只调用一次 factory,并缓存所有后续导入的结果,直到 [`vi.unmock`](#vii-unmock) 或 [`vi.doUnmock`](#vii-dounmock) 被调用。 @@ -62,21 +64,6 @@ vi.mock('./path/to/module.js', async (importOriginal) => { }) ``` -Vitest 支持模块 promise,而不是 `vi.mock` 方法中的字符串,以获得更好的 IDE 支持(当文件被移动时,路径将被更新,`importOriginal` 也会自动继承类型)。 - -```ts -vi.mock(import('./path/to/module.js'), async (importOriginal) => { - const mod = await importOriginal() // type is inferred - return { - ...mod, - // replace some exports - namedExport: vi.fn(), - } -}) -``` - -在此钩子下,Vitest 仍然对字符串而不是模块对象进行操作。 - ::: warning `vi.mock` 被提升(换句话说,_移动_)到**文件的顶部**。这意味着无论何时写入它(无论是在 `beforeEach` 还是 `test`),它都会在此之前被调用。 @@ -117,7 +104,7 @@ vi.mock('./path/to/module.js', () => { return { default: { myDefaultKey: vi.fn() }, namedExport: vi.fn(), - // etc... + // 更多 ... } }) ``` @@ -141,13 +128,13 @@ vi.mock('./path/to/module.js', () => { 如果在没有提供工厂的测试文件中调用 `vi.mock` ,它会在 `__mocks__` 文件夹中找到一个文件作为模块使用: ```ts +// axios 是 `__mocks__/axios.js` 默认导出项 +import axios from 'axios' + // increment.test.js import { vi } from 'vitest' -// axios is a default export from `__mocks__/axios.js` -import axios from 'axios' - -// increment is a named export from `src/__mocks__/increment.js` +// increment 是 `src/__mocks__/increment.js` 具名导出 import { increment } from '../increment.js' vi.mock('axios') @@ -161,20 +148,19 @@ axios.get(`/apples/${increment(1)}`) 请注意,如果不调用 `vi.mock` ,模块**不会**被自动模拟。要复制 Jest 的自动锁定行为,可以在 [`setupFiles`](/config/#setupfiles) 中为每个所需的模块调用 `vi.mock` 。 ::: -如果没有提供 `__mocks__` 文件夹或工厂,Vitest 将导入原始模块并自动模拟其所有输出。有关应用的规则,请参阅[模块](/guide/mocking#%E6%A8%A1%E5%9D%97)。 +如果没有提供 `__mocks__` 文件夹或工厂,Vitest 将导入原始模块并自动模拟其所有输出。有关应用的规则,请参阅 [算法](/guide/mocking#automocking-algorithm)。 ### vi.doMock - **类型**: `(path: string, factory?: (importOriginal: () => unknown) => unknown) => void` -与 [`vi.mock`](#vi-mock) 相同,但它不会被移动到文件顶部,因此我们可以引用全局文件作用域中的变量。模块的下一个 [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) 将被模拟。 +与 [`vi.mock`](#vi-mock) 相同,但它不会被移动到文件顶部,因此我们可以引用全局文件作用域中的变量。模块的下一个 [动态导入](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) 将被模拟。 ::: warning 这将不会模拟在调用此调用之前导入的模块。不要忘记,ESM 中的所有静态导入都是 [hoaded](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#hoisting),因此在静态导入前调用此调用不会强制在导入前调用: ```ts -// this will be called _after_ the import statement - +// 这将在 import 语句之后被调用 import { increment } from './increment.js' vi.doMock('./increment.js') ``` @@ -192,21 +178,21 @@ export function increment(number) { import { beforeEach, test } from 'vitest' import { increment } from './increment.js' -// the module is not mocked, because vi.doMock is not called yet +// 该模块尚未被模拟,因为从未调用过 vi.doMock increment(1) === 2 let mockedIncrement = 100 beforeEach(() => { - // you can access variables inside a factory + // 你可以在工厂函数内部访问变量 vi.doMock('./increment.js', () => ({ increment: () => ++mockedIncrement })) }) test('importing the next module imports mocked one', async () => { - // original import WAS NOT MOCKED, because vi.doMock is evaluated AFTER imports + // 原始模块并未模拟,是因为 vi.doMock 是在导入语句之后执行的 expect(increment(1)).toBe(2) const { increment: mockedIncrement } = await import('./increment.js') - // new dynamic import returns mocked module + // 新的动态导入,返回模拟模块 expect(mockedIncrement(1)).toBe(101) expect(mockedIncrement(1)).toBe(102) expect(mockedIncrement(1)).toBe(103) @@ -218,7 +204,7 @@ test('importing the next module imports mocked one', async () => { - **类型**: `(obj: T, deep?: boolean) => MaybeMockedDeep` - **类型**: `(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep` -TypeScript 的类型助手。只返回传入的对象。 +TypeScript 的类型工具函数。只返回传入的对象。 当 `partial` 为 `true` 时,它将期望一个 `Partial` 作为返回值。默认情况下,这只会让 TypeScript 认为第一层的值是模拟的。我们可以将 `{ deep: true }` 作为第二个参数传递给 TypeScript,告诉它整个对象都是模拟的(如果实际上是的话)。 @@ -251,7 +237,7 @@ vi.mock('./example.js', async () => { - **类型**: `(path: string) => Promise>` -导入模块并模拟其所有属性(包括嵌套属性)。遵循与 [`vi.mock`](#vi-mock) 相同的规则。有关应用的规则,请参阅[模块](/guide/mocking#%E6%A8%A1%E5%9D%97)。 +导入模块并模拟其所有属性(包括嵌套属性)。遵循与 [`vi.mock`](#vi-mock) 相同的规则。有关应用的规则,请参阅 [算法](/guide/mocking#automocking-algorithm)。 ### vi.unmock @@ -275,24 +261,24 @@ export function increment(number) { ```ts import { increment } from './increment.js' -// increment is already mocked, because vi.mock is hoisted +// increment 已被模拟, 因为 vi.mock 已导入声明提升 increment(1) === 100 -// this is hoisted, and factory is called before the import on line 1 +// 此处存在函数提升,工厂函数会在第 1 行 import 之前被调用 vi.mock('./increment.js', () => ({ increment: () => 100 })) -// all calls are mocked, and `increment` always returns 100 +// 所有调用均被模拟,并且 `increment` 始终返回 100 increment(1) === 100 increment(30) === 100 -// this is not hoisted, so other import will return unmocked module +// 此处不存在函数提升,因为其它导入操作返回未模拟的模块 vi.doUnmock('./increment.js') -// this STILL returns 100, because `vi.doUnmock` doesn't reevaluate a module +// 此处仍会返回 100,因 `vi.doUnmock` 不会重新评估模块 increment(1) === 100 increment(30) === 100 -// the next import is unmocked, now `increment` is the original function that returns count + 1 +// 下一次导入时解除模拟,此时 `increment` 恢复为原始函数(返回 count + 1) const { increment: unmockedIncrement } = await import('./increment.js') unmockedIncrement(1) === 2 @@ -308,20 +294,20 @@ unmockedIncrement(30) === 31 ```ts import { vi } from 'vitest' -import { data } from './data.js' // Will not get reevaluated beforeEach test +import { data } from './data.js' // 每次测试前不会重新计算 beforeEach(() => { vi.resetModules() }) test('change state', async () => { - const mod = await import('./some/path.js') // Will get reevaluated + const mod = await import('./some/path.js') // 将会被重新计算 mod.changeLocalState('new value') expect(mod.getLocalState()).toBe('new value') }) test('module has old state', async () => { - const mod = await import('./some/path.js') // Will get reevaluated + const mod = await import('./some/path.js') // 将会被重新计算 expect(mod.getLocalState()).toBe('old value') }) ``` @@ -337,7 +323,7 @@ test('module has old state', async () => { ```ts import { expect, test } from 'vitest' -// cannot track import because Promise is not returned +// 无法追踪导入操作,因未返回 Promise function renderComponent() { import('./component.js').then(({ render }) => { render() @@ -357,16 +343,15 @@ test('operations are resolved', async () => { 该方法还将在导入解析后等待下一个 `setTimeout` 跟他挂钩,因此所有同步操作都应在解析时完成。 ::: -## 模拟函数和对象 +## 模拟函数和对象 {#mocking-functions-and-objects} -本节介绍如何使用 [method mock](/api/mock) 替换环境变量和全局变量。 +本节介绍如何使用 [模拟方法](/api/mock) 以及替换环境变量和全局变量。 ### vi.fn - **类型:** `(fn?: Function) => Mock` -创建函数的监视程序,但也可以不创建监视程序。每次调用函数时,它都会存储调用参数、返回值和实例。此外,我们还可以使用 [methods](/api/mock) 操纵它的行为。 -如果没有给出函数,调用 mock 时将返回 `undefined`。 +创建一个函数监视器(可无需初始函数)。每次调用函数时,它都会存储调用参数、返回值和实例。此外,我们还可以使用 [方法](/api/mock) 操纵它的行为。若未提供初始函数,调用 mock 时将返回 `undefined`。 ```ts twoslash import { expect, vi } from 'vitest' @@ -407,7 +392,7 @@ expect(getApples).toHaveNthReturnedWith(2, 5) - **类型:** `(object: T, method: K, accessType?: 'get' | 'set') => MockInstance` -创建与 [`vi.fn()`](/#vi-fn) 类似的对象的方法或 getter/setter 的监听(spy) 。它会返回一个 [mock 函数](/api/mock) 。 +创建与 [`vi.fn()`](/#vi-fn) 类似的对象的方法或 getter/setter 的监听(spy) 。它会返回一个 [模拟函数](/api/mock) 。 ```ts twoslash import { expect, vi } from 'vitest' @@ -427,7 +412,7 @@ expect(spy).toHaveReturnedWith(1) ``` ::: tip -你可以在 [`afterEach`](/api/#aftereach)(或启用 [`test.restoreMocks`](/config/#restoreMocks) )中调用 [`vi.restoreAllMocks`](#vi-restoreallmocks) ,将所有方法还原为原始实现。这将还原原始的 [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) ,因此无法更改方法的实现: +你可以在 [`afterEach`](/api/#aftereach)(或启用 [`test.restoreMocks`](/config/#restoreMocks) )中调用 [`vi.restoreAllMocks`](#vi-restoreallmocks) ,将所有方法还原为原始实现。这将还原原始的 [对象描述符](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) ,因此无法更改方法的实现: ```ts const cart = { @@ -440,7 +425,7 @@ console.log(cart.getApples()) // 10 vi.restoreAllMocks() console.log(cart.getApples()) // 42 spy.mockReturnValue(10) -console.log(cart.getApples()) // still 42! +console.log(cart.getApples()) // 仍然为 42! ``` ::: @@ -454,14 +439,14 @@ console.log(cart.getApples()) // still 42! ```ts import { vi } from 'vitest' -// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV` -// are "development" before calling "vi.stubEnv" +// `process.env.NODE_ENV` 和 `import.meta.env.NODE_ENV` +// 在调用 "vi.stubEnv" 之前是 "development" vi.stubEnv('NODE_ENV', 'production') process.env.NODE_ENV === 'production' import.meta.env.NODE_ENV === 'production' -// doesn't change other envs +// 不会改变其他env import.meta.env.MODE === 'development' ``` @@ -483,8 +468,8 @@ import.meta.env.MODE = 'test' ```ts import { vi } from 'vitest' -// `process.env.NODE_ENV` and `import.meta.env.NODE_ENV` -// are "development" before calling stubEnv +// `process.env.NODE_ENV` 和 `import.meta.env.NODE_ENV` +// 在调用 stubEnv 之前是 "development" vi.stubEnv('NODE_ENV', 'production') @@ -498,7 +483,7 @@ import.meta.env.NODE_ENV === 'staging' vi.unstubAllEnvs() -// restores to the value that were stored before the first "stubEnv" call +// 还原到在第一次 “stubEnv” 调用之前存储的值 process.env.NODE_ENV === 'development' import.meta.env.NODE_ENV === 'development' ``` @@ -512,13 +497,13 @@ import.meta.env.NODE_ENV === 'development' ```ts twoslash import { vi } from 'vitest' -// `innerWidth` is "0" before calling stubGlobal +// 在调用 stubGlobal 之前 `innerWidth` 是 "0" vi.stubGlobal('innerWidth', 100) innerWidth === 100 globalThis.innerWidth === 100 -// if you are using jsdom or happy-dom +// 如果你正在使用 jsdom 或 happy-dom window.innerWidth === 100 ``` @@ -527,7 +512,7 @@ window.innerWidth === 100 ```ts globalThis.innerWidth = 100 -// if you are using jsdom or happy-dom +// 如果你正在使用 jsdom 或 happy-dom window.innerWidth = 100 ``` @@ -544,21 +529,21 @@ import { vi } from 'vitest' const Mock = vi.fn() -// IntersectionObserver is "undefined" before calling "stubGlobal" +// 在调用 "stubGlobal" 之前 IntersectionObserver 是 "undefined" vi.stubGlobal('IntersectionObserver', Mock) IntersectionObserver === Mock global.IntersectionObserver === Mock globalThis.IntersectionObserver === Mock -// if you are using jsdom or happy-dom +// 如果你正在使用 jsdom 或 happy-dom window.IntersectionObserver === Mock vi.unstubAllGlobals() globalThis.IntersectionObserver === undefined 'IntersectionObserver' in globalThis === false -// throws ReferenceError, because it's not defined +// 抛出 ReferenceError,因为变量未定义 IntersectionObserver === undefined ``` @@ -580,9 +565,9 @@ setInterval(() => console.log(++i), 50) vi.advanceTimersByTime(150) -// log: 1 -// log: 2 -// log: 3 +// 输出: 1 +// 输出: 2 +// 输出: 3 ``` ### vi.advanceTimersByTimeAsync @@ -599,9 +584,9 @@ setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) await vi.advanceTimersByTimeAsync(150) -// log: 1 -// log: 2 -// log: 3 +// 输出: 1 +// 输出: 2 +// 输出: 3 ``` ### vi.advanceTimersToNextTimer @@ -616,9 +601,9 @@ import { vi } from 'vitest' let i = 0 setInterval(() => console.log(++i), 50) -vi.advanceTimersToNextTimer() // log: 1 - .advanceTimersToNextTimer() // log: 2 - .advanceTimersToNextTimer() // log: 3 +vi.advanceTimersToNextTimer() // 输出: 1 + .advanceTimersToNextTimer() // 输出: 2 + .advanceTimersToNextTimer() // 输出: 3 ``` ### vi.advanceTimersToNextTimerAsync @@ -633,11 +618,11 @@ import { expect, vi } from 'vitest' let i = 0 setInterval(() => Promise.resolve().then(() => console.log(++i)), 50) -await vi.advanceTimersToNextTimerAsync() // log: 1 +await vi.advanceTimersToNextTimerAsync() // 输出: 1 expect(console.log).toHaveBeenCalledWith(1) -await vi.advanceTimersToNextTimerAsync() // log: 2 -await vi.advanceTimersToNextTimerAsync() // log: 3 +await vi.advanceTimersToNextTimerAsync() // 输出: 2 +await vi.advanceTimersToNextTimerAsync() // 输出: 3 ``` ### vi.getTimerCount @@ -688,9 +673,9 @@ const interval = setInterval(() => { vi.runAllTimers() -// log: 1 -// log: 2 -// log: 3 +// 输出: 1 +// 输出: 2 +// 输出: 3 ``` ### vi.runAllTimersAsync @@ -709,7 +694,7 @@ setTimeout(async () => { await vi.runAllTimersAsync() -// log: result +// 输出: result ``` ### vi.runOnlyPendingTimers @@ -726,7 +711,7 @@ setInterval(() => console.log(++i), 50) vi.runOnlyPendingTimers() -// log: 1 +// 输出: 1 ``` ### vi.runOnlyPendingTimersAsync @@ -752,10 +737,10 @@ setTimeout(() => { await vi.runOnlyPendingTimersAsync() -// log: 2 -// log: 3 -// log: 3 -// log: 1 +// 输出: 2 +// 输出: 3 +// 输出: 3 +// 输出: 1 ``` ### vi.setSystemTime @@ -764,7 +749,7 @@ await vi.runOnlyPendingTimersAsync() 如果启用了伪计时器,此方法将模拟用户更改系统时钟(将影响与日期相关的 API,如 `hrtime` 、`performance.now` 或 `new Date()` ),但不会触发任何计时器。如果未启用假定时器,该方法将仅模拟 `Date.*` 调用。 -如果我们需要测试任何依赖于当前日期的内容 -- 例如在代码中调用 [luxon](https://github.com/moment/luxon/) --则非常有用。 +当您需要测试依赖当前日期的逻辑时(例如代码中的 [Luxon](https://github.com/moment/luxon/)() 调用)非常实用。 ```ts twoslash import { expect, vi } from 'vitest' @@ -806,9 +791,9 @@ vi.useRealTimers() 定时器用完后,可以调用此方法将模拟的定时器返回到其原始实现。之前调度的所有计时器都将被丢弃。 -## Miscellaneous +## 杂项 {#miscellaneous} -Vitest 提供的一组有用的辅助函数。 +Vitest 提供的一组有用的工具函数。 ### vi.waitFor {#vi-waitfor} @@ -834,8 +819,8 @@ test('Server started successfully', async () => { console.log('Server started') }, { - timeout: 500, // default is 1000 - interval: 20, // default is 50 + timeout: 500, // 默认为 1000 + interval: 20, // 默认为 50 } ) expect(server.isReady).toBe(true) @@ -851,20 +836,20 @@ import { expect, test, vi } from 'vitest' import { getDOMElementAsync, populateDOMAsync } from './dom.js' test('Element exists in a DOM', async () => { - // start populating DOM + // 开始填充 DOM populateDOMAsync() const element = await vi.waitFor( async () => { - // try to get the element until it exists + // 尝试获取元素直到其存在 const element = (await getDOMElementAsync()) as HTMLElement | null expect(element).toBeTruthy() expect(element.dataset.initialized).toBeTruthy() return element }, { - timeout: 500, // default is 1000 - interval: 20, // default is 50 + timeout: 500, // 默认为 1000 + interval: 20, // 默认为 50 } ) expect(element).toBeInstanceOf(HTMLElement) @@ -886,16 +871,16 @@ import { expect, test, vi } from 'vitest' test('Element render correctly', async () => { const element = await vi.waitUntil(() => document.querySelector('.element'), { - timeout: 500, // default is 1000 - interval: 20, // default is 50 + timeout: 500, // 默认为 1000 + interval: 20, // 默认为 50 }) - // do something with the element + // 对元素进行某些操作 expect(element.querySelector('.element-child')).toBeTruthy() }) ``` -### vi.hoisted {#vi-hoisted} +### vi.hoisted - **类型**: `(factory: () => T) => T` @@ -937,7 +922,7 @@ mockedMethod.mockReturnValue(100) expect(originalMethod()).toBe(100) ``` -请注意,即使我们的环境不支持顶级等待,也可以异步调用此方法: +请注意,即使您的运行环境不支持顶层 await,此方法仍可异步调用: ```ts const promised = await vi.hoisted(async () => { @@ -961,12 +946,12 @@ vi.setConfig({ restoreMocks: true, fakeTimers: { now: new Date(2021, 11, 19), - // supports the whole object + // 支持完整对象 }, maxConcurrency: 10, sequence: { hooks: 'stack', - // supports only "sequence.hooks" + // 仅支持 "sequence.hooks" }, }) ``` diff --git a/config/index.md b/config/index.md index 711d0c2f..5df89a19 100644 --- a/config/index.md +++ b/config/index.md @@ -501,7 +501,6 @@ Vitest 中的默认测试环境是一个 Node.js 环境。如果你正在构建 你可以通过在文件顶部添加包含 `@vitest-environment` 的文档块或注释,为某个测试文件中的所有测试指定环境: - 文档块格式: ```js @@ -1689,7 +1688,7 @@ export interface BrowserProvider { getSupportedBrowsers: () => readonly string[] initialize: ( ctx: Vitest, - options: { browser: string, options?: BrowserProviderOptions } + options: { browser: string; options?: BrowserProviderOptions } ) => Awaitable openPage: (url: string) => Awaitable close: () => Awaitable @@ -2532,4 +2531,3 @@ export interface SnapshotEnvironment { - **默认值:** `false` 调用任何`console`方法时始终打印控制台跟踪。这对于调试很有用。 - diff --git a/eslint.config.js b/eslint.config.js index 406c82fd..2143715c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,4 +1,4 @@ -import antfu from '@antfu/eslint-config' +import antfu, { GLOB_SRC } from '@antfu/eslint-config' export default antfu({ stylistic: true, @@ -14,9 +14,44 @@ export default antfu({ '*.d.ts', 'coverage', '!.vitepress', + 'guide/examples/*.md', ], rules: { - 'no-restricted-globals': 'off', + // prefer global Buffer to not initialize the whole module + 'node/prefer-global/buffer': 'off', + 'node/prefer-global/process': 'off', 'no-empty-pattern': 'off', + 'antfu/indent-binary-ops': 'off', + 'unused-imports/no-unused-imports': 'error', + 'style/member-delimiter-style': [ + 'error', + { + multiline: { delimiter: 'none' }, + singleline: { delimiter: 'semi' }, + }, + ], + + 'ts/no-invalid-this': 'off', + + // TODO: migrate and turn it back on + 'ts/ban-types': 'off', + + 'no-restricted-imports': [ + 'error', + { + paths: ['path'], + }, + ], + + 'import/no-named-as-default': 'off', + }, +}, { + files: [`**/*.md`, `**/*.md/${GLOB_SRC}`], + rules: { + 'style/max-statements-per-line': 'off', + 'import/newline-after-import': 'off', + 'import/first': 'off', + 'no-restricted-globals': 'off', + 'unused-imports/no-unused-imports': 'off', }, }) diff --git a/guide/browser.md b/guide/browser.md new file mode 100644 index 00000000..e880b347 --- /dev/null +++ b/guide/browser.md @@ -0,0 +1,128 @@ +--- +title: 浏览器模式 | 指南 +--- + +# 浏览器模式 实验性 {#browser-mode} + +此页面提供有关 Vitest API 中实验性浏览器模式功能的信息,该功能允许你在浏览器中本地运行测试,提供对 window 和 document 等浏览器全局变量的访问。此功能目前正在开发中,API 未来可能会更改。 + +## 浏览器兼容性 {#browser-compatibility} + +Vitest 使用 [Vite 开发服务器](https://cn.vitejs.dev/guide/#browser-support) 来运行您的测试,因此我们只支持 [`esbuild.target`](https://cn.vitejs.dev/config/shared-options#esbuild)选项(默认为 `esnext`)中指定的功能。 + +默认情况下,Vite 的目标浏览器支持本地 [ES Modules](https://caniuse.com/es6-module)、原生 [ESM dynamic import](https://caniuse.com/es6-module-dynamic-import) 和 [`import.meta`](https://caniuse.com/mdn-javascript_operators_import_meta)。此外,我们还利用 [`BroadcastChannel`](https://caniuse.com/?search=BroadcastChannel)在 iframe 之间进行通信: + +- Chrome >=87 +- Firefox >=78 +- Safari >=15.4 +- Edge >=88 + +## 动机 {#motivation} + +我们开发了 Vitest 浏览器模式功能,以帮助改进测试工作流程并实现更准确、可靠的测试结果。这个实验性的测试 API 增加了在本地浏览器环境中运行测试的功能。在本节中,我们将探讨这个功能背后的动机以及它对测试的好处。 + +### 不同的测试方式 {#different-ways-of-testing} + +有不同的方法来测试 JavaScript 代码。一些测试框架在 Node.js 中模拟浏览器环境,而其他框架则在真实浏览器中运行测试。在这种情况下,[jsdom](https://www.npmjs.com/package/jsdom) 是一个模拟浏览器环境的规范实现,可以与 Jest 或 Vitest 等测试运行器一起使用,而其他测试工具,如 [WebdriverIO](https://webdriver.io/) 或 [Cypress](https://www.cypress.io/) 则允许开发者在真实浏览器中测试他们的应用,或者在 [Playwright](https://playwright.dev/) 的情况下提供一个浏览器引擎。 + +### 模拟警告 {#the-simulation-caveat} + +在模拟环境(如 jsdom 或 happy-dom)中测试 JavaScript 程序简化了测试设置并提供了易于使用的 API,使它们适用于许多项目并增加了对测试结果的信心。然而,需要牢记的是,这些工具仅模拟浏览器环境而不是实际浏览器,这可能导致模拟环境和真实环境之间存在一些差异。因此,测试结果可能会出现误报或漏报。 + +为了确保测试结果的可靠性,在真实浏览器环境中进行测试非常重要。这就是为什么我们开发了 Vitest 的浏览器模式功能,允许开发者在浏览器中本地运行测试,并获得更准确、可靠的测试结果。通过浏览器级别的测试,开发者可以更加自信地确保他们的应用在真实场景中能够按照预期工作。 + +## 缺点 {#drawbacks} + +使用 Vitest 浏览器时,重要的是要考虑以下缺点: + +### 早期发展 {#early-development} + +Vitest 的浏览器模式功能目前仍处于早期开发阶段,因此可能尚未完全优化,也可能存在一些未解决的错误或问题。为了获得更好的测试结果,我们建议用户使用独立的浏览器端测试运行程序(如 WebdriverIO、Cypress 或 Playwright)来增强他们的 Vitest 浏览器体验。 + +### 更长的初始化时间 {#longer-initialization} + +Vitest 浏览器在初始化过程中需要启动提供程序和浏览器,这可能需要一些时间。与其他测试模式相比,这可能导致更长的初始化时间。 + +## 配置 {#configuration} + +要在 Vitest 配置中启用浏览器模式,你可以使用 `--browser` 标志或在你的 Vitest 配置文件中将 `browser.enabled` 字段设置为 `true`。这是使用浏览器字段的示例配置: + +```ts +export default defineConfig({ + test: { + browser: { + enabled: true, + name: 'chrome', // 浏览器名称必填 + }, + }, +}) +``` + +## 浏览器选项类型 {#browser-option-types} + +Vitest 中的浏览器选项取决于提供者。如果你传递 `--browser` 并且未在配置文件中指定其名称,Vitest 将失败。可用选项: + +- `webdriverio` (默认) 支持以下浏览器: + - `firefox` + - `chrome` + - `edge` + - `safari` +- `playwright` 支持以下浏览器: + - `firefox` + - `webkit` + - `chromium` + +## 跨浏览器测试 {#cross-browser-testing} + +当你在浏览器选项中指定浏览器名称时,Vitest 将默认尝试使用 [WebdriverIO](https://webdriver.io/) 运行指定的浏览器,然后在那里运行测试。此功能使跨浏览器测试易于在 CI 等环境中使用和配置。如果不想使用 WebdriverIO,可以使用 `browser.provider` 选项配置自定义浏览器提供程序。 + +要使用 CLI 指定浏览器,请使用 `--browser` 标志后跟浏览器名称,如下所示: + +```sh +npx vitest --browser=chrome +``` + +或者你可以使用点符号向 CLI 提供浏览器选项: + +```sh +npx vitest --browser.name=chrome --browser.headless +``` + +::: tip NOTE +当使用带有 WebdriverIO 的 Safari 浏览器选项时,需要通过在你的设备上运行 `sudo safaridriver --enable` 来激活`safaridriver`。 + +此外,在运行测试时,Vitest 将尝试安装一些驱动程序用于兼容 `safaridriver`。 +::: + +## 无头模式 {#headless} + +无头模式是浏览器模式下可用的另一个选项。在无头模式下,浏览器在没有用户界面的情况下在后台运行,这对于运行自动化测试非常有用。Vitest 中的 headless 选项可以设置为布尔值以启用或禁用无头模式。 + +这是启用无头模式的示例配置: + +```ts +export default defineConfig({ + test: { + browser: { + enabled: true, + headless: true, + }, + }, +}) +``` + +你还可以在 CLI 中使用 `--browser.headless` 标志设置无头模式,如下所示: + +```sh +npx vitest --browser.name=chrome --browser.headless +``` + +在这种情况下,Vitest 将使用 Chrome 浏览器以 headless 模式运行。 + +## 限制 {#limitations} + +### 线程阻塞对话框 {#thread-blocking-dialogs} + +使用 Vitest 浏览器时,需要注意的是像 `alert` 或 `confirm` 这样的线程阻塞对话框不能在本地使用。这是因为它们阻塞了网页,这意味着 Vitest 无法继续与该页面通信,导致执行挂起。 + +在这种情况下,Vitest 为这些 API 提供默认模拟和默认返回值。这确保如果用户不小心使用了同步弹出式 Web API,执行不会挂起。但是,仍然建议用户模拟这些 Web API 以获得更好的体验。在 [模拟](/guide/mocking) 中阅读更多内容。 diff --git a/guide/browser/assertion-api.md b/guide/browser/assertion-api.md deleted file mode 100644 index 790bc38d..00000000 --- a/guide/browser/assertion-api.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Assertion API | Browser Mode ---- - -# Assertion API - -Vitest 捆绑了 [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom)库,以提供各种开箱即用的 DOM 断言。有关详细文档,请阅读 `jest-dom` readme: - -- [`toBeDisabled`](https://github.com/testing-library/jest-dom#toBeDisabled) -- [`toBeEnabled`](https://github.com/testing-library/jest-dom#toBeEnabled) -- [`toBeEmptyDOMElement`](https://github.com/testing-library/jest-dom#toBeEmptyDOMElement) -- [`toBeInTheDocument`](https://github.com/testing-library/jest-dom#toBeInTheDocument) -- [`toBeInvalid`](https://github.com/testing-library/jest-dom#toBeInvalid) -- [`toBeRequired`](https://github.com/testing-library/jest-dom#toBeRequired) -- [`toBeValid`](https://github.com/testing-library/jest-dom#toBeValid) -- [`toBeVisible`](https://github.com/testing-library/jest-dom#toBeVisible) -- [`toContainElement`](https://github.com/testing-library/jest-dom#toContainElement) -- [`toContainHTML`](https://github.com/testing-library/jest-dom#toContainHTML) -- [`toHaveAccessibleDescription`](https://github.com/testing-library/jest-dom#toHaveAccessibleDescription) -- [`toHaveAccessibleErrorMessage`](https://github.com/testing-library/jest-dom#toHaveAccessibleErrorMessage) -- [`toHaveAccessibleName`](https://github.com/testing-library/jest-dom#toHaveAccessibleName) -- [`toHaveAttribute`](https://github.com/testing-library/jest-dom#toHaveAttribute) -- [`toHaveClass`](https://github.com/testing-library/jest-dom#toHaveClass) -- [`toHaveFocus`](https://github.com/testing-library/jest-dom#toHaveFocus) -- [`toHaveFormValues`](https://github.com/testing-library/jest-dom#toHaveFormValues) -- [`toHaveStyle`](https://github.com/testing-library/jest-dom#toHaveStyle) -- [`toHaveTextContent`](https://github.com/testing-library/jest-dom#toHaveTextContent) -- [`toHaveValue`](https://github.com/testing-library/jest-dom#toHaveValue) -- [`toHaveDisplayValue`](https://github.com/testing-library/jest-dom#toHaveDisplayValue) -- [`toBeChecked`](https://github.com/testing-library/jest-dom#toBeChecked) -- [`toBePartiallyChecked`](https://github.com/testing-library/jest-dom#toBePartiallyChecked) -- [`toHaveRole`](https://github.com/testing-library/jest-dom#toHaveRole) -- [`toHaveErrorMessage`](https://github.com/testing-library/jest-dom#toHaveErrorMessage) - -如果使用 TypeScript 或希望在 `expect` 中获得正确的类型提示,请确保根据使用的提供程序,在 `tsconfig` 中指定了 `@vitest/browser/providers/playwright` 或 `@vitest/browser/providers/webdriverio`。如果使用默认的 `preview` 提供程序,则可指定 `@vitest/browser/matchers` 代替。 - -::: code-group -```json [preview] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/matchers" - ] - } -} -``` -```json [playwright] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/playwright" - ] - } -} -``` -```json [webdriverio] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/webdriverio" - ] - } -} -``` -::: diff --git a/guide/browser/commands.md b/guide/browser/commands.md deleted file mode 100644 index 01ced276..00000000 --- a/guide/browser/commands.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -title: Commands | Browser Mode -outline: deep ---- - -# Commands - -命令是一个函数,它调用服务器上的另一个函数并将结果传递回浏览器。Vitest 公开了几个可以在浏览器测试中使用的内置命令。 - -## 内置命令 - -### 文件处理 - -你可以使用 `readFile` 、`writeFile` 和 `removeFile` API 来处理浏览器测试中的文件。所有路径都是相对于测试文件解析的,即使它们是在位于另一个文件中的辅助函数中调用的。 - -默认情况下,Vitest 使用 `utf-8` 编码,但你可以使用选项覆盖它。 - -::: tip -此 API 遵循 [`server.fs`](https://vitejs.dev/config/server-options.html#server-fs-allow) 出于安全原因的限制。 -::: - -```ts -import { server } from '@vitest/browser/context' - -const { readFile, writeFile, removeFile } = server.commands - -it('handles files', async () => { - const file = './test.txt' - - await writeFile(file, 'hello world') - const content = await readFile(file) - - expect(content).toBe('hello world') - - await removeFile(file) -}) -``` - -## CDP Session - -Vitest 通过 `@vitest/browser/context` 中导出的 `cdp` 方法访问原始 Chrome Devtools 协议。它主要用于库作者在其基础上构建工具。 - -```ts -import { cdp } from '@vitest/browser/context' - -const input = document.createElement('input') -document.body.appendChild(input) -input.focus() - -await cdp().send('Input.dispatchKeyEvent', { - type: 'keyDown', - text: 'a', -}) - -expect(input).toHaveValue('a') -``` - -::: warning -CDP session仅适用于 `playwright` provider,并且仅在使用 `chromium` 浏览器时有效。有关详细信息,请参阅 playwright 的 [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession)文档。 -::: - -## Custom Commands - -你也可以通过 [`browser.commands`](/config/#browser-commands) 配置选项添加自己的命令。如果你开发了一个库,你可以通过插件内的 `config` 钩子来提供它们: - -```ts -import type { Plugin } from 'vitest/config' -import type { BrowserCommand } from 'vitest/node' - -const myCustomCommand: BrowserCommand<[arg1: string, arg2: string]> = ({ - testPath, - provider -}, arg1, arg2) => { - if (provider.name === 'playwright') { - console.log(testPath, arg1, arg2) - return { someValue: true } - } - - throw new Error(`provider ${provider.name} is not supported`) -} - -export default function BrowserCommands(): Plugin { - return { - name: 'vitest:custom-commands', - config() { - return { - test: { - browser: { - commands: { - myCustomCommand, - } - } - } - } - } - } -} -``` - -然后,你可以通过从 `@vitest/brower/context` 导入它,在测试中调用它: - -```ts -import { commands } from '@vitest/browser/context' -import { expect, test } from 'vitest' - -test('custom command works correctly', async () => { - const result = await commands.myCustomCommand('test1', 'test2') - expect(result).toEqual({ someValue: true }) -}) - -// if you are using TypeScript, you can augment the module -declare module '@vitest/browser/context' { - interface BrowserCommands { - myCustomCommand: (arg1: string, arg2: string) => Promise<{ - someValue: true - }> - } -} -``` - -::: warning -如果自定义命令具有相同的名称,则它们将覆盖内置命令。 -::: - -### 自定义命令 `playwright` - -Vitest 在命令上下文中公开了几个`playwright`特定属性。 - -- `page`引用包含测试 iframe 的完整页面。这是协调器 HTML,为避免出现问题,最好不要碰它。 -- `frame` 是一个异步方法,用于解析测试器 [`Frame`](https://playwright.dev/docs/api/class-frame)。它的 API 与 `page` 类似,但不支持某些方法。如果您需要查询元素,应优先使用 `context.iframe` 代替,因为它更稳定、更快速。 -- `iframe` 是一个 [`FrameLocator`](https://playwright.dev/docs/api/class-framelocator),用于查询页面上的其他元素。 -- `context` 是指唯一的[BrowserContext](https://playwright.dev/docs/api/class-browsercontext)。 - -```ts -import { defineCommand } from '@vitest/browser' - -export const myCommand = defineCommand(async (ctx, arg1, arg2) => { - if (ctx.provider.name === 'playwright') { - const element = await ctx.iframe.findByRole('alert') - const screenshot = await element.screenshot() - // do something with the screenshot - return difference - } -}) -``` - -::: tip -如果您使用的是 TypeScript,请不要忘记将 `@vitest/browser/providers/playwright` 添加到您的 `tsconfig` "compilerOptions.types" 字段,以便在配置中以及 `userEvent` 和 `page` 选项中获得自动完成功能: - -```json -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/playwright" - ] - } -} -``` -::: - -### 自定义命令 `webdriverio` - -Vitest 在上下文对象上公开了一些 `webdriverio` 特有属性。 - -- `browser` 是 `WebdriverIO.Browser` API. - -Vitest 通过在调用命令前调用 `browser.switchToFrame` 自动将 `webdriver` 上下文切换到测试 iframe,因此 `$` 和 `$` 方法将引用 iframe 内的元素,而不是 orchestrator 中的元素,但非 Webdriver API 仍将引用 parent frame 上下文。 - -::: tip -如果您使用的是 TypeScript,请不要忘记将 `@vitest/browser/providers/webdriverio` 添加到您的 `tsconfig` "compilerOptions.types" 字段,以获得自动完成功能: - -```json -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/webdriverio" - ] - } -} -``` -::: diff --git a/guide/browser/context.md b/guide/browser/context.md deleted file mode 100644 index cd429990..00000000 --- a/guide/browser/context.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Context | Browser Mode ---- - -# 上下文 - -Vitest 通过 `@vitest/browser/context` 入口点公开上下文模块。从 2.0 开始,它公开了一小部分实用程序,这些实用程序可能在测试中对你有用。 - -```ts -export const server: { - /** - * Platform the Vitest server is running on. - * The same as calling `process.platform` on the server. - */ - platform: Platform - /** - * Runtime version of the Vitest server. - * The same as calling `process.version` on the server. - */ - version: string - /** - * Name of the browser provider. - */ - provider: string - /** - * Name of the current browser. - */ - browser: string - /** - * Available commands for the browser. - */ - commands: BrowserCommands -} - -/** - * Handler for user interactions. The support is implemented by the browser provider (`playwright` or `webdriverio`). - * If used with `preview` provider, fallbacks to simulated events via `@testing-library/user-event`. - * @experimental - */ -export const userEvent: { - setup: () => UserEvent - click: (element: Element, options?: UserEventClickOptions) => Promise - dblClick: (element: Element, options?: UserEventDoubleClickOptions) => Promise - tripleClick: (element: Element, options?: UserEventTripleClickOptions) => Promise - selectOptions: ( - element: Element, - values: HTMLElement | HTMLElement[] | string | string[], - options?: UserEventSelectOptions, - ) => Promise - keyboard: (text: string) => Promise - type: (element: Element, text: string, options?: UserEventTypeOptions) => Promise - clear: (element: Element) => Promise - tab: (options?: UserEventTabOptions) => Promise - hover: (element: Element, options?: UserEventHoverOptions) => Promise - unhover: (element: Element, options?: UserEventHoverOptions) => Promise - fill: (element: Element, text: string, options?: UserEventFillOptions) => Promise - dragAndDrop: (source: Element, target: Element, options?: UserEventDragAndDropOptions) => Promise -} - -/** - * Available commands for the browser. - * A shortcut to `server.commands`. - */ -export const commands: BrowserCommands - -export const page: { - /** - * Serialized test config. - */ - config: ResolvedConfig - /** - * Change the size of iframe's viewport. - */ - viewport(width: number, height: number): Promise - /** - * Make a screenshot of the test iframe or a specific element. - * @returns Path to the screenshot file or path and base64. - */ - screenshot(options: Omit & { base64: true }): Promise<{ - path: string - base64: string - }> - screenshot(options?: ScreenshotOptions): Promise -} - -export const cdp: () => CDPSession -``` diff --git a/guide/browser/examples.md b/guide/browser/examples.md deleted file mode 100644 index 7952764d..00000000 --- a/guide/browser/examples.md +++ /dev/null @@ -1,158 +0,0 @@ ---- -title: Examples | Browser Mode ---- - -# Examples - -浏览器模式与框架无关,因此不提供任何渲染组件的方法。不过,你应该可以使用框架的测试工具包。 - -我们建议根据您的框架使用 `testing-library` packages: - -- [`@testing-library/dom`](https://testing-library.com/docs/dom-testing-library/intro) if you don't use a framework -- [`@testing-library/vue`](https://testing-library.com/docs/vue-testing-library/intro) to render [vue](https://vuejs.org) components -- [`@testing-library/svelte`](https://testing-library.com/docs/svelte-testing-library/intro) to render [svelte](https://svelte.dev) components -- [`@testing-library/react`](https://testing-library.com/docs/react-testing-library/intro) to render [react](https://react.dev) components -- [`@testing-library/preact`](https://testing-library.com/docs/preact-testing-library/intro) to render [preact](https://preactjs.com) components -- [`solid-testing-library`](https://testing-library.com/docs/solid-testing-library/intro) to render [solid](https://www.solidjs.com) components -- [`@marko/testing-library`](https://testing-library.com/docs/marko-testing-library/intro) to render [marko](https://markojs.com) components - -::: warning -`testing-library` 提供了一个包`@testing-library/user-event`。我们不建议直接使用它,因为它会模拟事件而非实际触发事件--相反,请使用从 `@vitest/browser/context`导入的 [`userEvent`](#interactivity-api),它使用 Chrome DevTools 协议或 Webdriver(取决于provider)。 -::: - -::: code-group -```ts [vue] -// based on @testing-library/vue example -// https://testing-library.com/docs/vue-testing-library/examples - -import { userEvent } from '@vitest/browser/context' -import { render, screen } from '@testing-library/vue' -import Component from './Component.vue' - -test('properly handles v-model', async () => { - render(Component) - - // Asserts initial state. - expect(screen.getByText('Hi, my name is Alice')).toBeInTheDocument() - - // Get the input DOM node by querying the associated label. - const usernameInput = await screen.findByLabelText(/username/i) - - // Type the name into the input. This already validates that the input - // is filled correctly, no need to check the value manually. - await userEvent.fill(usernameInput, 'Bob') - - expect(screen.getByText('Hi, my name is Alice')).toBeInTheDocument() -}) -``` -```ts [svelte] -// based on @testing-library/svelte -// https://testing-library.com/docs/svelte-testing-library/example - -import { render, screen } from '@testing-library/svelte' -import { userEvent } from '@vitest/browser/context' -import { expect, test } from 'vitest' - -import Greeter from './greeter.svelte' - -test('greeting appears on click', async () => { - const user = userEvent.setup() - render(Greeter, { name: 'World' }) - - const button = screen.getByRole('button') - await user.click(button) - const greeting = await screen.findByText(/hello world/iu) - - expect(greeting).toBeInTheDocument() -}) -``` -```tsx [react] -// based on @testing-library/react example -// https://testing-library.com/docs/react-testing-library/example-intro - -import { userEvent } from '@vitest/browser/context' -import { render, screen } from '@testing-library/react' -import Fetch from './fetch' - -test('loads and displays greeting', async () => { - // Render a React element into the DOM - render() - - await userEvent.click(screen.getByText('Load Greeting')) - // wait before throwing an error if it cannot find an element - const heading = await screen.findByRole('heading') - - // assert that the alert message is correct - expect(heading).toHaveTextContent('hello there') - expect(screen.getByRole('button')).toBeDisabled() -}) -``` -```tsx [preact] -// based on @testing-library/preact example -// https://testing-library.com/docs/preact-testing-library/example - -import { h } from 'preact' -import { userEvent } from '@vitest/browser/context' -import { render } from '@testing-library/preact' - -import HiddenMessage from '../hidden-message' - -test('shows the children when the checkbox is checked', async () => { - const testMessage = 'Test Message' - - const { queryByText, getByLabelText, getByText } = render( - {testMessage}, - ) - - // query* functions will return the element or null if it cannot be found. - // get* functions will return the element or throw an error if it cannot be found. - expect(queryByText(testMessage)).not.toBeInTheDocument() - - // The queries can accept a regex to make your selectors more - // resilient to content tweaks and changes. - await userEvent.click(getByLabelText(/show/i)) - - expect(getByText(testMessage)).toBeInTheDocument() -}) -``` -```tsx [solid] -// baed on @testing-library/solid API -// https://testing-library.com/docs/solid-testing-library/api - -import { render } from '@testing-library/solid' - -it('uses params', async () => { - const App = () => ( - <> - ( -

- Id: - {useParams()?.id} -

- )} - /> -

Start

} /> - - ) - const { findByText } = render(() => , { location: 'ids/1234' }) - expect(await findByText('Id: 1234')).toBeInTheDocument() -}) -``` -```ts [marko] -// baed on @testing-library/marko API -// https://testing-library.com/docs/marko-testing-library/api - -import { render, screen } from '@marko/testing-library' -import Greeting from './greeting.marko' - -test('renders a message', async () => { - const { container } = await render(Greeting, { name: 'Marko' }) - expect(screen.getByText(/Marko/)).toBeInTheDocument() - expect(container.firstChild).toMatchInlineSnapshot(` -

Hello, Marko!

- `) -}) -``` -::: diff --git a/guide/browser/index.md b/guide/browser/index.md deleted file mode 100644 index 4496afc7..00000000 --- a/guide/browser/index.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -title: Browser Mode | Guide -outline: deep ---- - -# 浏览器模式 实验性 {#browser-mode} - -此页面提供有关 Vitest API 中实验性浏览器模式功能的信息,该功能允许你在浏览器中本地运行测试,提供对窗口和文档等浏览器全局变量的访问。此功能目前正在开发中,API 未来可能会更改。 - -## 安装 - -为方便设置,可使用 `vitest init browser` 命令安装所需的依赖项并创建浏览器配置。 - -::: code-group -```bash [npm] -npx vitest init browser -``` -```bash [yarn] -yarn exec vitest init browser -``` -```bash [pnpm] -pnpx vitest init browser -``` -```bash [bun] -bunx vitest init browser -``` -::: - -### 手动安装 - -您也可以手动安装软件包。默认情况下,浏览器模式不需要任何额外的 E2E provider 就能在本地运行测试,因为它会复用你现有的浏览器。 - -::: code-group -```bash [npm] -npm install -D vitest @vitest/browser -``` -```bash [yarn] -yarn add -D vitest @vitest/browser -``` -```bash [pnpm] -pnpm add -D vitest @vitest/browser -``` -```bash [bun] -bun add -D vitest @vitest/browser -``` -::: - -::: warning -不过,要在 CI 中运行测试,您需要安装 [`playwright`](https://npmjs.com/package/playwright) 或 [`webdriverio`](https://www.npmjs.com/package/webdriverio) 。我们还建议在本地测试时切换到这两个选项中的一个,而不是使用默认的 `preview` 提供程序,因为它依赖于模拟事件而不是使用 Chrome DevTools 协议。 -::: - -### 使用 Playwright - -::: code-group -```bash [npm] -npm install -D vitest @vitest/browser playwright -``` -```bash [yarn] -yarn add -D vitest @vitest/browser playwright -``` -```bash [pnpm] -pnpm add -D vitest @vitest/browser playwright -``` -```bash [bun] -bun add -D vitest @vitest/browser playwright -``` -::: - -### Using Webdriverio - -::: code-group -```bash [npm] -npm install -D vitest @vitest/browser webdriverio -``` -```bash [yarn] -yarn add -D vitest @vitest/browser webdriverio -``` -```bash [pnpm] -pnpm add -D vitest @vitest/browser webdriverio -``` -```bash [bun] -bun add -D vitest @vitest/browser webdriverio -``` -::: - -## 配置 - -要在 Vitest 配置中激活浏览器模式,可以使用 `--browser` 标志,或在 Vitest 配置文件中将 `browser.enabled` 字段设为 `true`。下面是一个使用浏览器字段的配置示例: - -```ts -export default defineConfig({ - test: { - browser: { - provider: 'playwright', // or 'webdriverio' - enabled: true, - name: 'chrome', // browser name is required - }, - } -}) -``` - -如果之前未使用过 Vite,请确保已安装框架插件并在配置中指定。有些框架可能需要额外配置才能运行,请查看其 Vite 相关文档以确定。 - -::: code-group -```ts [vue] -import { defineConfig } from 'vitest/config' -import vue from '@vitejs/plugin-vue' - -export default defineConfig({ - plugins: [vue()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -```ts [svelte] -import { defineConfig } from 'vitest/config' -import { svelte } from '@sveltejs/vite-plugin-svelte' - -export default defineConfig({ - plugins: [svelte()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -```ts [solid] -import { defineConfig } from 'vitest/config' -import solidPlugin from 'vite-plugin-solid' - -export default defineConfig({ - plugins: [solidPlugin()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -```ts [marko] -import { defineConfig } from 'vitest/config' -import marko from '@marko/vite' - -export default defineConfig({ - plugins: [marko()], - test: { - browser: { - enabled: true, - provider: 'playwright', - name: 'chrome', - } - } -}) -``` -::: - -::: tip -`react` 不需要插件就能工作,但 `preact` 需要 [extra configuration](https://preactjs.com/guide/v10/getting-started/#create-a-vite-powered-preact-app) 才能使用别名 -::: - -如果需要使用基于 Node 的运行程序运行某些测试,可以定义一个 [workspace](/guide/workspace) 文件,为不同的测试策略分别配置: - -```ts -// vitest.workspace.ts -import { defineWorkspace } from 'vitest/config' - -export default defineWorkspace([ - { - test: { - // an example of file based convention, - // you don't have to follow it - include: [ - 'tests/unit/**/*.{test,spec}.ts', - 'tests/**/*.unit.{test,spec}.ts', - ], - name: 'unit', - environment: 'node', - }, - }, - { - test: { - // an example of file based convention, - // you don't have to follow it - include: [ - 'tests/browser/**/*.{test,spec}.ts', - 'tests/**/*.browser.{test,spec}.ts', - ], - name: 'browser', - browser: { - enabled: true, - name: 'chrome', - }, - }, - }, -]) -``` - -## 浏览器选项类型 - -Vitest 中的浏览器选项取决于provider。如果在配置文件中传递 `--browser` 且未指定其名称,则 Vitest 将失败。可用选项: -- `webdriverio` 支持这些浏览器: - - `firefox` - - `chrome` - - `edge` - - `safari` -- `playwright` 支持这些浏览器: - - `firefox` - - `webkit` - - `chromium` - -## 浏览器兼容性 - -Vitest 使用 [Vite dev server](https://cn.vitejs.dev/guide/#browser-support) 来运行您的测试,因此我们只支持 [`esbuild.target`](https://cn.vitejs.dev/config/shared-options#esbuild)选项(默认为 `esnext`)中指定的功能。 - -默认情况下,Vite 的目标浏览器支持本地 [ES Modules](https://caniuse.com/es6-module)、本地 [ESM dynamic import](https://caniuse.com/es6-module-dynamic-import) 和 [`import.meta`](https://caniuse.com/mdn-javascript_operators_import_meta)。此外,我们还利用 [`BroadcastChannel`](https://caniuse.com/?search=BroadcastChannel)在 iframe 之间进行通信: - -- Chrome >=87 -- Firefox >=78 -- Safari >=15.4 -- Edge >=88 - -## 动机 - -我们开发了 Vitest 浏览器模式功能,以帮助改进测试工作流程并实现更准确、可靠的测试结果。这个实验性的测试 API 增加了在本地浏览器环境中运行测试的功能。在本节中,我们将探讨这个功能背后的动机以及它对测试的好处。 - - -### 不同的测试方式 - -有不同的方法来测试 JavaScript 代码。一些测试框架在 Node.js 中模拟浏览器环境,而其他框架则在真实浏览器中运行测试。在这种情况下,[jsdom](https://www.npmjs.com/package/jsdom) 是一个模拟浏览器环境的规范实现,可以与 Jest 或 Vitest 等测试运行器一起使用,而其他测试工具,如 [WebdriverIO](https://webdriver.io/) 或 [Cypress](https://www.cypress.io/) 则允许开发者在真实浏览器中测试他们的应用,或者在 [Playwright](https://playwright.dev/) 的情况下提供一个浏览器引擎。 - -### 模拟警告 - -在模拟环境(如 jsdom 或 happy-dom)中测试 JavaScript 程序简化了测试设置并提供了易于使用的 API,使它们适用于许多项目并增加了对测试结果的信心。然而,需要牢记的是,这些工具仅模拟浏览器环境而不是实际浏览器,这可能导致模拟环境和真实环境之间存在一些差异。因此,测试结果可能会出现误报或漏报。 - -为了在测试中获得最高的水平,测试在真实浏览器环境中进行非常重要。这就是为什么我们开发了 Vitest 的浏览器模式功能,允许开发者在浏览器中本地运行测试,并获得更准确、可靠的测试结果。通过浏览器级别的测试,开发者可以更加自信地确保他们的应用在真实场景中能够按照预期工作。t that their application will work as intended in a real-world scenario. - -## 缺点 - -使用 Vitest 浏览器时,重要的是要考虑以下缺点: - -### 更长的初始化时间 - -Vitest 浏览器在初始化过程中需要启动提供程序和浏览器,这可能需要一些时间。与其他测试模式相比,这可能导致更长的初始化时间。 - -## 跨浏览器测试 - -当你在浏览器选项中指定浏览器名称时,Vitest 将默认尝试使用 [WebdriverIO](https://webdriver.io/) 运行指定的浏览器,然后在那里运行测试。此功能使跨浏览器测试易于在 CI 等环境中使用和配置。如果不想使用 WebdriverIO,可以使用 `browser.provider` 选项配置自定义浏览器提供程序。 - -要使用 CLI 指定浏览器,请使用 `--browser` 标志后跟浏览器名称,如下所示: - -```sh -npx vitest --browser=chrome -``` - -或者你可以使用点符号向 CLI 提供浏览器选项: - -```sh -npx vitest --browser.name=chrome --browser.headless -``` - -## Headless - -headless 模式是浏览器模式下可用的另一个选项。在 headless 模式下,浏览器在没有用户界面的情况下在后台运行,这对于运行自动化测试非常有用。Vitest 中的 headless 选项可以设置为布尔值以启用或禁用 headless 模式。 - -这是启用 headless 模式的示例配置: - - -```ts -export default defineConfig({ - test: { - browser: { - provider: 'playwright', - enabled: true, - headless: true, - }, - } -}) -``` - -你还可以在 CLI 中使用 `--browser.headless` 标志设置 headless 模式,如下所示: - -```sh -npx vitest --browser.name=chrome --browser.headless -``` - -在这种情况下,Vitest 将使用 Chrome 浏览器以 headless 模式运行。 - -::: warning -默认情况下Headless模式不可用。您需要使用 [`playwright`](https://npmjs.com/package/playwright) 或 [`webdriverio`](https://www.npmjs.com/package/webdriverio) 提供程序来启用此功能。 -::: - -## 限制 - -### 线程阻塞对话框 - -使用 Vitest 浏览器时,需要注意的是像 `alert` 或 `confirm` 这样的线程阻塞对话框不能在本地使用。这是因为它们阻塞了网页,这意味着 Vitest 无法继续与该页面通信,导致执行挂起。 - -在这种情况下,Vitest 为这些 API 提供默认模拟和默认返回值。这确保如果用户不小心使用了同步弹出式 Web API,执行不会挂起。但是,仍然建议用户模拟这些 Web API 以获得更好的体验。在 [Mocking](/guide/mocking) 中阅读更多内容。 diff --git a/guide/browser/interactivity-api.md b/guide/browser/interactivity-api.md deleted file mode 100644 index d1709c91..00000000 --- a/guide/browser/interactivity-api.md +++ /dev/null @@ -1,388 +0,0 @@ ---- -title: Interactivity API | Browser Mode ---- - -# Interactivity API - -Vitest 使用 [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) 或 [webdriver](https://www.w3.org/TR/webdriver/) API 实现了 [`@testing-library/user-event`](https://testing-library.com/docs/user-event)应用程序接口的子集,而不是伪造事件,这使得浏览器行为更加可靠和一致。 - -几乎每个 `userEvent` 方法都继承了其provider选项。要在集成开发环境中查看所有可用选项,请在 `tsconfig.json` 文件中添加 `webdriver` 或 `playwright` 类型: - -::: code-group -```json [playwright] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/playwright" - ] - } -} -``` -```json [webdriverio] -{ - "compilerOptions": { - "types": [ - "@vitest/browser/providers/webdriverio" - ] - } -} -``` -::: - -## userEvent.click - -- **Type:** `(element: Element, options?: UserEventClickOptions) => Promise` - -点击元素。继承 provider 的选项。有关此方法如何工作的详细说明,请参阅 provider 的文档。 - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('clicks on an element', async () => { - const logo = screen.getByRole('img', { name: /logo/ }) - - await userEvent.click(logo) -}) -``` - -References: - -- [Playwright `locator.click` API](https://playwright.dev/docs/api/class-locator#locator-click) -- [WebdriverIO `element.click` API](https://webdriver.io/docs/api/element/click/) -- [testing-library `click` API](https://testing-library.com/docs/user-event/convenience/#click) - -## userEvent.dblClick - -- **Type:** `(element: Element, options?: UserEventDoubleClickOptions) => Promise` - -触发元素的双击事件 - -请参阅你的 provider 的文档以获取有关此方法如何工作的详细说明。 - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('triggers a double click on an element', async () => { - const logo = screen.getByRole('img', { name: /logo/ }) - - await userEvent.dblClick(logo) -}) -``` - -References: - -- [Playwright `locator.dblclick` API](https://playwright.dev/docs/api/class-locator#locator-dblclick) -- [WebdriverIO `element.doubleClick` API](https://webdriver.io/docs/api/element/doubleClick/) -- [testing-library `dblClick` API](https://testing-library.com/docs/user-event/convenience/#dblClick) - -## userEvent.tripleClick - -- **Type:** `(element: Element, options?: UserEventTripleClickOptions) => Promise` - -Triggers a triple click event on an element. Since there is no `tripleclick` in browser api, this method will fire three click events in a row, and so you must check [click event detail](https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#usage_notes) to filter the event: `evt.detail === 3`. - -Please refer to your provider's documentation for detailed explanation about how this method works. - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('triggers a triple click on an element', async () => { - const logo = screen.getByRole('img', { name: /logo/ }) - let tripleClickFired = false - logo.addEventListener('click', (evt) => { - if (evt.detail === 3) { - tripleClickFired = true - } - }) - - await userEvent.tripleClick(logo) - expect(tripleClickFired).toBe(true) -}) -``` - -References: - -- [Playwright `locator.click` API](https://playwright.dev/docs/api/class-locator#locator-click): implemented via `click` with `clickCount: 3` . -- [WebdriverIO `browser.action` API](https://webdriver.io/docs/api/browser/action/): implemented via actions api with `move` plus three `down + up + pause` events in a row -- [testing-library `tripleClick` API](https://testing-library.com/docs/user-event/convenience/#tripleClick) - -## userEvent.fill - -- **Type:** `(element: Element, text: string) => Promise` - -用文本填充 input/textarea/conteneditable。这将在输入新值之前移除输入框中的任何现有文本。 - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('update input', async () => { - const input = screen.getByRole('input') - - await userEvent.fill(input, 'foo') // input.value == foo - await userEvent.fill(input, '{{a[[') // input.value == {{a[[ - await userEvent.fill(input, '{Shift}') // input.value == {Shift} -}) -``` - -::: tip -该 API 比使用 [`userEvent.type`](#userevent-type) 或 [`userEvent.keyboard`](#userevent-keyboard) 更快,但**不支持** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (例如,`{Shift}{selectall}`)。 - -在不需要输入特殊字符的情况下,我们建议使用此 API 而不是 [`userEvent.type`](#userevent-type)。 -::: - -References: - -- [Playwright `locator.fill` API](https://playwright.dev/docs/api/class-locator#locator-fill) -- [WebdriverIO `element.setValue` API](https://webdriver.io/docs/api/element/setValue) -- [testing-library `type` API](https://testing-library.com/docs/user-event/utility/#type) - -## userEvent.keyboard - -- **Type:** `(text: string) => Promise` - -通过 `userEvent.keyboard` 可以触发键盘输入。如果任何输入有焦点,它就会在该输入中键入字符。否则,它将触发当前焦点元素(如果没有焦点元素,则为 `document.body`)上的键盘事件。 - -This API supports [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard). - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('trigger keystrokes', async () => { - await userEvent.keyboard('foo') // translates to: f, o, o - await userEvent.keyboard('{{a[[') // translates to: {, a, [ - await userEvent.keyboard('{Shift}{f}{o}{o}') // translates to: Shift, f, o, o - await userEvent.keyboard('{a>5}') // press a without releasing it and trigger 5 keydown - await userEvent.keyboard('{a>5/}') // press a for 5 keydown and then release it -}) -``` - -References: - -- [Playwright `locator.press` API](https://playwright.dev/docs/api/class-locator#locator-press) -- [WebdriverIO `action('key')` API](https://webdriver.io/docs/api/browser/action#key-input-source) -- [testing-library `type` API](https://testing-library.com/docs/user-event/utility/#type) - -## userEvent.tab - -- **Type:** `(options?: UserEventTabOptions) => Promise` - -发送一个 `Tab` 键事件。这是`userEvent.keyboard('{tab}')`的简写。 - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('tab works', async () => { - const [input1, input2] = screen.getAllByRole('input') - - expect(input1).toHaveFocus() - - await userEvent.tab() - - expect(input2).toHaveFocus() - - await userEvent.tab({ shift: true }) - - expect(input1).toHaveFocus() -}) -``` - -References: - -- [Playwright `locator.press` API](https://playwright.dev/docs/api/class-locator#locator-press) -- [WebdriverIO `action('key')` API](https://webdriver.io/docs/api/browser/action#key-input-source) -- [testing-library `tab` API](https://testing-library.com/docs/user-event/convenience/#tab) - -## userEvent.type - -- **Type:** `(element: Element, text: string, options?: UserEventTypeOptions) => Promise` - -::: warning -如果不依赖 [special characters](https://testing-library.com/docs/user-event/keyboard)(例如,`{shift}` 或 `{selectall}`),建议使用 [`userEvent.fill`](#userevent-fill)。 -::: - -`type` 方法在 [`keyboard`](https://testing-library.com/docs/user-event/keyboard) API 的基础上实现了 `@testing-library/user-event` 的 [`type`](https://testing-library.com/docs/user-event/utility/#type) 工具。 - -该函数允许您在 input/textarea/conteneditable 中键入字符。它支持 [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard)。 - -如果只需按下字符而无需输入,请使用 [`userEvent.keyboard`](#userevent-keyboard) API。 - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('update input', async () => { - const input = screen.getByRole('input') - - await userEvent.type(input, 'foo') // input.value == foo - await userEvent.type(input, '{{a[[') // input.value == foo{a[ - await userEvent.type(input, '{Shift}') // input.value == foo{a[ -}) -``` - -References: - -- [Playwright `locator.press` API](https://playwright.dev/docs/api/class-locator#locator-press) -- [WebdriverIO `action('key')` API](https://webdriver.io/docs/api/browser/action#key-input-source) -- [testing-library `type` API](https://testing-library.com/docs/user-event/utility/#type) - -## userEvent.clear - -- **Type:** `(element: Element) => Promise` - -此方法会清除输入元素的内容。 - -```ts -import { userEvent } from '@vitest/browser/context' -import { screen } from '@testing-library/dom' - -test('clears input', async () => { - const input = screen.getByRole('input') - - await userEvent.fill(input, 'foo') - expect(input).toHaveValue('foo') - - await userEvent.clear(input) - expect(input).toHaveValue('') -}) -``` - -References: - -- [Playwright `locator.clear` API](https://playwright.dev/docs/api/class-locator#locator-clear) -- [WebdriverIO `element.clearValue` API](https://webdriver.io/docs/api/element/clearValue) -- [testing-library `clear` API](https://testing-library.com/docs/user-event/utility/#clear) - -## userEvent.selectOptions - -- **Type:** `(element: Element, values: HTMLElement | HTMLElement[] | string | string[], options?: UserEventSelectOptions) => Promise` - -The `userEvent.selectOptions` allows selecting a value in a `